Spring AOP-->面向切面编程简单理解和简单使用

来源:互联网 发布:如何安装node sass 编辑:程序博客网 时间:2024/05/23 17:25

AOP

Aspect Oriented Programming(AOP) 英译中:面向切面(Aspect)编程

AOP主要功能

日志记录,性能统计,安全控制,事务处理,异常处理等等


.......................................................

.......................................................

关于AOP,一搜可以搜出一大堆相关资料和博文,都非常精彩,本篇我就不放过多的理论性文字了,直接结合代码一步步实现Spring 的AOP编程!


讲的时候,可能有些地方专业术语说的不是太专业、不太到位,但是对于一个初次接触AOP的我们来说,绝对是一个看了,你不会再问什么是AOP的小白了。


本篇案列立足于SSM整合后的web项目,以本地Maven仓库作为Jar包管理,开始之前先放两张图,文章最后,会把整个项目的环境上传到我的资源以供各位下载参考。


A:整个项目的目录节结构图:





B:Spring(AOP)依赖的相关的Jar包:


圈红钱的两个jar包,如果在你的项目中是手动导入的话,这两个需要单独下载!(我的统一由本地Maven管理,下载的事就交给配置好的Pom.xml文件了)

下载地址----->  我的资源(最少1分,没分的可以去官网下载)


准备开车了,请各位一定要坐稳了!


一、包



pojo    : 下面放类,什么时间类啊,日志类啊,结合本篇就是放大家都公用的部分的封装类,大家是谁,大家在本篇中

是包Impl下的各个实现类

service: 下面放我们自定义的接口,搞一些方法,交给Impl包下面的类来实现

Impl    :  下面放接口的实现类


二、定义一个接口,AOPTest,申明两个方法

AOPTest.java


package com.appleyk.service;public interface AOPTest {   void sayHello();   void doSomething();}


三、定义接口AOPTest的两个实现类,AOPTestImpl1 和 AOPTestImpl2

AOPTestImpl1 .java


package com.appleyk.service.Impl;import com.appleyk.service.AOPTest;public class AOPTestImpl1 implements AOPTest{@Overridepublic void sayHello() {TimeBefor();System.out.println("实现类1-->实现sayHello !");TimeAfter();}@Overridepublic void doSomething() {TimeBefor();System.out.println("实现类1-->实现doSomething !");TimeAfter();}  private void TimeBefor(){//获得自1970-1-01 00:00:00.000 到当前时刻的时间距离,类型为longSystem.out.println("前置时间:"+System.currentTimeMillis());}private void TimeAfter(){//获得自1970-1-01 00:00:00.000 到当前时刻的时间距离,类型为longSystem.out.println("后置时间:"+System.currentTimeMillis());}}


AOPTestImpl2 .java(和实现类1的代码一样)


package com.appleyk.service.Impl;import com.appleyk.service.AOPTest;public class AOPTestImpl2 implements AOPTest{@Overridepublic void sayHello() {TimeBefor();System.out.println("实现类2-->实现sayHello !");TimeAfter();}@Overridepublic void doSomething() {TimeBefor();System.out.println("实现类2-->实现doSomething !");TimeAfter();}private void TimeBefor(){//获得自1970-1-01 00:00:00.000 到当前时刻的时间距离,类型为longSystem.out.println("前置时间:"+System.currentTimeMillis());}private void TimeAfter(){//获得自1970-1-01 00:00:00.000 到当前时刻的时间距离,类型为longSystem.out.println("后置时间:"+System.currentTimeMillis());}  }


为什么会在两个实现类里面定义时间的前置打印和后置打印方法,而且这两个部分还是重复的功能(注意,目前这些功能都是人为手动的增加的)。

这里我想插一句,这种在目标方法上加扩展功能的方式类似于Python的装饰函数,有兴趣的朋友可以看下我的Python学习笔记,里面有讲到这种用法,当然,原理千差万别,但是效果几乎是翻版!

(1)目的是为了在目标方法执行前后打印时间戳,根据前后时间戳,我们可以一目了然的看出来目标方法的执行时间效率

(2)这种方式显然有些冗余,类似于下面的一张图




二三步骤完成后,结构图如下:





四、applicationContext.xml配置(暂时还未涉及到切面编程,纯手动实现

applicationContext.xml


<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsdhttp://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd"><bean id="aopTestImpl1" class="com.appleyk.service.Impl.AOPTestImpl1"></bean><bean id="aopTestImpl2" class="com.appleyk.service.Impl.AOPTestImpl2"></bean></beans>

后面,测试的时候会利用Spring上下文对象(xxxxContext),拿到Spring ICO容器中的bean(方法:getbean,参数:

bean id),根据bean id获得对应的代理类(一个bean id 对应一个实现类),接口是没有实例的,但是转为为代理类

后可以有实例,这之间涉及到一个类型转化。


五、方法测试



TestAOP.java


package com.appleyk.aop.test;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import com.appleyk.service.AOPTest;public class TestAOP {@Testpublic void Test1(){try{ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/applicationContext.xml");//通过spring上下文对象拿到spring容器中的bean,根据id获得相应的接口的实现类的代理类,注意需要类型转换AOPTest t1 = (AOPTest)context.getBean("aopTestImpl1");//这里填bean ID        AOPTest t2 = (AOPTest)context.getBean("aopTestImpl2");        System.out.println("实现类Impl1效果如下:");        System.out.println("--------------");        t1.sayHello();        System.out.println("--------------");        t1.doSomething();                System.out.println("");        System.out.println("实现类Impl2效果如下:");        System.out.println("--------------");        t2.sayHello();        System.out.println("--------------");        t2.doSomething();                 }catch (Exception e) {e.printStackTrace();}}}


因为我们给实现类1 和 实现类2 的两个目标方法都加了执行时间记录功能,因此,执行测试方法的时候,会看到如下

效果:



六、使用AOP编程,实现目标方法的前置和后置时间打印


上述的实现过程太过繁琐,项目的耦合度太高,为了解耦,我们想把实现类1的时间打印方法和实现类2中的时间打

印方法一刀切掉,重新整一个,反正功能都一样,占着地方,不如搞成一个,看着也舒服,维护起来也方便。



为了对比,我们重新再定义两个实现类,AOPTestImplA 和 AOPTestImplB

AOPTestImplA .java


package com.appleyk.service.Impl;import com.appleyk.service.AOPTest;public class AOPTestImplA implements AOPTest{@Overridepublic void sayHello() {System.out.println("实现类A-->实现sayHello !");}@Overridepublic void doSomething() {System.out.println("实现类A-->实现doSomething !");}  }

AOPTestImplB的和A一样,demo就不贴出来了!


有了这两个实现类,我们还差一个封装类(暂且先别考虑,切面编程怎么会和下面的封装类扯到一块

PrintTime.java


package com.appleyk.pojo;public class PrintTime {    //前置通知  --> 目标方法执行前 打印内容public void TimeBefore(){   System.out.println("前置时间:"+System.currentTimeMillis());   }//后置通知  --> 目标方法执行后 打印内容   public void TimeAfter(){   System.out.println("后置时间:"+System.currentTimeMillis());   }}


这个PrintTime类中的方法,完全是照搬之前的写法,一点都没有动!




下面,我们基于XML的配置,来实现AOP,让方法doSomething在执行的时候,打印前后时间记录,而方法sayHello

在执行的时候则没有该项功能。


重新配置applicationContext.xml如下:

applicationContext.xml


<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsdhttp://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd"><bean id="aopTestImpl1" class="com.appleyk.service.Impl.AOPTestImplA"/><bean id="aopTestImpl2" class="com.appleyk.service.Impl.AOPTestImplB"/><bean id="printTime" class="com.appleyk.pojo.PrintTime"/><aop:config>  <aop:aspect id="time" ref="printTime">   <aop:pointcut expression="execution(* com.appleyk.service.AOPTest.do*(..))" id="CutDoMethod"/>      <!-- 前置通知 -->    <aop:before method="TimeBefore" pointcut-ref="CutDoMethod"/>    <!-- 后置通知 -->    <aop:after  method="TimeAfter" pointcut-ref="CutDoMethod"/>  </aop:aspect></aop:config></beans>



备注:上图中,后置通知,应该是<aop:after/>,因为图是改不了了,特此说明!


我们先来演示一下,AOP的前置和后置通知,稍后再演示环绕通知,在原有的测试类中增加一个方法Test2如下:

TestAOP.java


package com.appleyk.aop.test;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import com.appleyk.service.AOPTest;public class TestAOP {//Test1这里就不贴出来了@Testpublic void Test2(){try{ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/applicationContext.xml");//通过spring上下文对象拿到spring容器中的bean,根据id获得相应的接口的实现类的代理类,注意需要类型转换AOPTest t1 = (AOPTest)context.getBean("aopTestImpl1");//这里填bean ID        AOPTest t2 = (AOPTest)context.getBean("aopTestImpl2");        System.out.println("实现类ImplA效果如下:");        System.out.println("--------------");        t1.sayHello();        System.out.println("--------------");        t1.doSomething();                System.out.println("");        System.out.println("实现类ImplB效果如下:");        System.out.println("--------------");        t2.sayHello();        System.out.println("--------------");        t2.doSomething();                 }catch (Exception e) {  e.printStackTrace();}       }}

执行Test2测试方法,看到的效果如下:


如果,被作为切入点的方法是一个查询语句,那么我们就可以在这个方法执行后,看到这个查询方法的耗时情况

如果,既想切sayHello 又想 切doSomething两个方法,只需要将XML中的切入点的表达式改成如下即可:

 <aop:pointcut expression="execution(* com.appleyk.service.AOPTest.*(..))" id="CutAllMethod"/>


效果就不演示了,我们接下来看一下什么是环绕通知


演示AOP环绕通知,需要改两个地方,一个是对应的封装类PrintTime,一个就是我们的XML配置文件

PrintTime.java


package com.appleyk.pojo;import org.aspectj.lang.ProceedingJoinPoint;public class PrintTime {    //前置通知  --> 目标方法执行前 打印内容    public void TimeBefore(){   System.out.println("前置时间:"+System.currentTimeMillis());   }    //后置通知  --> 目标方法执行后 打印内容    public void TimeAfter(){   System.out.println("后置时间:"+System.currentTimeMillis());   }       //环绕通知    public void TimeAround(ProceedingJoinPoint joinPoint) throws Throwable{   System.out.println("开始时间:"+System.currentTimeMillis());   joinPoint.proceed();//注意这个,很关键,没有这个,目标函数不会执行,是的,不会执行!你没看错!   System.out.println("结束时间:"+System.currentTimeMillis());   }}

applicationContext.xml

<aop:config>  <aop:aspect id="time" ref="printTime">   <aop:pointcut expression="execution(* com.appleyk.service.AOPTest.*(..))" id="CutAllMethod"/>      <!-- 前置通知 -->    <!--  <aop:before method="TimeBefore" pointcut-ref="CutDoMethod"/>-->    <!-- 后置通知 -->    <!--<aop:after method="TimeAfter" pointcut-ref="CutDoMethod"/>-->    <!--环绕通知 -->    <aop:around method="TimeAround" pointcut-ref="CutAllMethod"/>  </aop:aspect></aop:config>


测试类TestAOP不用改,我们直接执行测试方法Test2,效果如下:




如果我们在PrintTime.java中注释掉下面这行代码,会怎么样呢?

joinPoint.proceed();


注释的就不贴了,直接上最后的执行结果:




joinPoint:连接点。在Spring 中,就是被拦截到的方法,调用一下proceed方法,目标函数才能被执行!当然,

连接点还可以是字段或者构造函数



当然,我们还可以再增加一个横切关注点<aop:aspect id="log" ref="WriteLog">,日志记录


添加的方式和打印时间一样,为了省时间,这里我直接贴出代码和xml配置文件,就不在做过多的说明了


WriteLog.java


package com.appleyk.pojo;public class WriteLog {//前置通知  --> 目标方法执行前 打印内容public void LogBefore(){ System.out.println("日志记录开始:xxxxx"); }//后置通知  --> 目标方法执行后 打印内容        public void LogAfter(){ System.out.println("日志记录结束:ooooo");   }}


applicationContext.xml


<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsdhttp://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd"><bean id="aopTestImpl1" class="com.appleyk.service.Impl.AOPTestImplA"/><bean id="aopTestImpl2" class="com.appleyk.service.Impl.AOPTestImplB"/><bean id="printTime" class="com.appleyk.pojo.PrintTime"/><bean id="writeLog"  class="com.appleyk.pojo.WriteLog"/><aop:config>  <aop:aspect id="time" ref="printTime" order="2">   <aop:pointcut expression="execution(* com.appleyk.service.AOPTest.*(..))" id="CutAllMethod"/>      <!-- 前置通知 -->    <!--  <aop:before method="TimeBefore" pointcut-ref="CutDoMethod"/>-->    <!-- 后置通知 -->    <!--<aop:after method="TimeAfter" pointcut-ref="CutDoMethod"/>-->    <!--环绕通知 -->    <aop:around method="TimeAround" pointcut-ref="CutAllMethod"/>  </aop:aspect>    <!-- order 横切关注点的 切入顺序,显然日志记录要比时间打印切入(显示)的要早 ,结束的要晚-->  <aop:aspect id="log" ref="writeLog" order="1">  <aop:pointcut expression="execution(* com.appleyk.service.AOPTest.say*(..))" id="CutSayMethod"/>      <!-- 前置通知 -->     <aop:before method="LogBefore" pointcut-ref="CutSayMethod"/>    <!-- 后置通知 -->    <aop:after method="LogAfter" pointcut-ref="CutSayMethod"/>      </aop:aspect>  </aop:config></beans>


测试类TestAOP不用动,注意,上面xml增加的第二个横切关注点(日志打印),切的是say*开头的方法,因此,最终

看到的结果如下:





本篇只是简单的基于XML的配置方式,浅显的实现AOP编程(深入的,我还在学习中,只能先这样了)!


理论性的文字读起来不好理解,不如亲自敲一遍代码,简单的一实现,就豁然开朗了。



最后附上整个项目的环境---->AOP编程简单实例演示


如果你下载下来整个项目的话,想要跑起来,你要:

(1)设置项目中的本地Maven的路径指向

(2)安装ssm-parent到本地仓库

(3)删除ssm-web-test(指向的Location可能和你的电脑磁盘路径不一致)

(4)重新导入import 已经存在的  Maven Project,找到ssm-web-test(这个在资源里的ssm-web下面)
















原创粉丝点击