AOP
一、 什么是AOP
AOP是面向切面编程的简称,将程序运行过程分解成各个切面,可以在不修改源码的情况下给程序方法动态地添加功能,其底层实现是使用了动态代理模式;
二、 为什么要用AOP
分离系统中的各种关注点,将核心关注点和横切关注点分离开来(例如主业务程序和一些校验、日志、安全类的程序分离),实现业务逻辑和切面逻辑的解耦;
三、 实现效果
可以在方法的前后加入其它操作,比如:每次进行一次数据库查询操作之后,要添加一条日志,而添加日志的操作我们不用写在数据库查询这个方法内,而是交由AOP框架来做;使用动态代理后,每当我们执行这个数据库查询方法时,都会被AOP框架截获,从而进行方法增强,在方法的前后添加一些其它操作,我们自己调用的方法(例如这个数据库查询方法),称之为连接点(JoinPoint);
四、 使用注解的配置方式(使用 AspectJ 类库实现)
0、jar包
aspectiweaver.jar aopaliance-.jar asperctjrt.jar
- 基本配置
(0)IOC容器的注解方式配置照旧
(1)首先要把一个类声明成切面,就需要先把该类放到IOC容器里去(配置它的bean),再声明为一个切面,即在类上面加上@Aspect和@Component这两个注解
(2)在xml配置文件中加入aop命名空间,并在beans标签中写入:
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
表示自动为匹配的方法生成代理对象;
- 前置通知(在目标方法执行前做的操作)
(1) 在切面类中加入一个方法,并在该方法前加入注解:
@Before(“execution(匹配的目标方法)”)
括号里的值称为切入点表达式,可以使用模糊匹配(比如*./正则)
表达式格式为:访问权限 返回值 方法的完整限定名(参数类型)
(2) 该前置通知方法可以有类型为JoinPoint参数
通过该参数可以获取目标方法的各种参数,例如方法名、参数值列表、类…
- 后置通知(在目标方法执行后做的操作)
(1) 使用@After注解,与前置通知功能相对应;
(2) 即使是目标方法发生了异常,后置通知照样执行;
(3) 前置通知和后置通知都无法访问目标方法的返回结果;
- 返回通知
(1) 在方法正常结束之后执行的方法;
(2) 在方法前使用:
@AfterReturning(value=” execution(匹配的目标方法)”,returning=”变量名”)
(3) 在方法的参数中加入 Object 变量名(该变量名与注解中的变量名对应),即可在返回通知方法内访问到目标方法的返回值;
- 异常通知
(1) 在目标方法发生异常的时候使用;
(2) 在方法前加入:
@AfterThrowing(value=” execution(匹配的目标方法)”,throwing=”变量名”)
(3) 在方法的参数中加入 异常类型 变量名(该变量名与注解中的变量名对应),即可在异常通知方法内访问到目标方法抛出的异常
- 上述四个通知的执行顺序
前置通知->返回|异常->后置通知;
- 环绕通知
(1) 环绕通知相当于动态代理的全过程,包含了以上四个通知
(2) 在方法前加入@Around(” execution(匹配的目标方法)”)
(3) 在方法的参数中 加入ProceedingJoinPoint类型参数,使用该类型参数的.proceed();方法可以控制目标方法的执行,并且该方法的返回值就是目标方法的返回值;
(4) 同样,ProceedingJoinPoint类型参数也可以获得目标方法的方法名和参数之类的值;
(5) 环绕通知方法必须得有返回值;
- 切面优先级
当有多个切面类时,使用@order(整数值),该注解写在切面类上,值越小,优先级越高;
- 重用切入点表达式
(0) 我们在每个通知方法的注解里都要写入切入点表达式executtion(….),里面的值很可能都是一样的,所以将其封装起来,以便重用;
(1) 写一个空方法(即方法里没有写任何代码的方法);
(2) 在方法前加入@Pointcut(” execution(匹配的目标方法)”);
(3) 而后,在其它通知的注解里,就可以将” execution(匹配的目标方法)”替换为该方法名;
(4) 跨类、跨包引用需要加上相应的类、包前缀;
五、 使用XML文件的配置方式(使用 AspectJ 类库实现)
- (1)切面类首先得是一个bean,所以在xml配置文件里,得先把切面类配置成一个bean;
(2)切面类、切面类里的通知方法代码都和注解配置时的代码一致;
- AOP基本配置
(1)先写<aop:config></aop:config>标签,而后所有的aop配置都写在该标签里面;
(2)配置切入点表达式:
<aop:pointcut expression=”execution(匹配的方法名)” id=”该表达式ID”>
该表达式指定了目标方法,该方法执行时,就会被引用了该表达式的通知截获;
(3)配置切面:
<aop:aspect ref=”切面类的bean” order=”优先级整数值”></aop:aspect>
而后,属于该切面的通知都写在该标签内;
(4)配置通知:
<aop:before method=”切面类里的方法名” pointcut-ref=”切入点表达式ID”>
返回、后置、异常、环绕通知配置类似;注意异常通知和返回通知的变量名称要和方法参数的变量名一致;
(5)<aop:config>标签内可以有多个切面配置标签,而切面标签内可以有多个通知;
(6)要使用目标bean时,就直接获取目标对象的bean,AOP会自动生成代理,自动织入;
- 需要将通知各自写成一个类并继承相应的接口:
- 配置
六、 使用XML文件的配置方式(使用 spring框架自身aop实现)
前置通知:MethodBeforeAdvice 环绕通知:MethodInterceptor
后置通知:AfterReturningAdvice 异常通知:ThrowsAdvice
(1) 将目标对象、各个通知方法(称为切面)都配置成bean
(2) 将代理对象也配置成bean ,id随意,
class=” org.springframework.aop.framework.ProxyFactoryBean”
分别有三个参数:
interceptorNames,值是切面的list(<list><value>通知/顾问beanID</value>…)
target,值为目标对象<ref=”目标对象beanID”>
interfaces,是目标对象实现的接口list(<list><value>接口路径</value>…)
(3) 要使用目标对象时,获取代理对象的beanID;
- 使用 名称匹配方法切入点 顾问(advisor)
(0) 顾问包装通知,让通知更精细地织入
(1) 没有使用顾问时,默认地,会为目标对象的全部方法实现通知操作,使用顾问后,就可以更精细地指定拦截哪些方法;
顾问也要先配置成bean,id随意
class=”org.springframework.aop.support.NameMatchMethodPointcutAdvisor”
(2) 该顾问有两三个参数:advice,值为引用类型,引用通知的beanID(即想让某个通知精确地作用某个某个/些方法);mappedName/ mappedNames,第一个为value类型,指定目标方法名,第二个为array类型,指定多个方法名(第二个的值也可以写value里,各方法名用逗号隔开);
(3) mappedNames的值可以用*号去匹配,比如do*,指定以do开头的所有方法
(4) 顾问也是切面,所以要使用顾问时,将其像通知一样放入代理对象bean里的interceptorNames参数
- 使用 正则表达式匹配方法切入点 顾问(advisor)
(0) 与”名称匹配方法切入点”写法类似;
(1) class要换成org.springframework.aop.support.RegexpMethodPointcutAdvisor
(2) 该顾问也要三个参数:advice,使用方法同上;
pattern/patterns:其value值为正则表达式(其实多个表达式都可以写在pattern里,用|隔开);
(3) 常用的正则表达式运算符有 . 表示任意单个字符,
* 表示前一个字符出现0次或多次 + 表示前一个字符出现1次或多次;
(4) 正则表达式匹配的是方法的全限定名(包.类.方法名);
(5) 例: .*do.* 全限定名中表示包含do的方法;
- 默认Advisor自动代理器
(0) 没使用自动代理器时,若有多个目标对象,则需要手动配置多个代理对象,造成代码冗余且耗费时间;
(1) 默认Advisor自动代理器底层使用了bean的后置处理器(BeanPostProcessor),在bean初始化之后,会自动地帮配置文件中的所有目标对象生成代理对象,并自动绑定配置文件中的顾问,而需要使用目标对象时,只需要获取目标对象的beanID就可以了;
(2) 配置:目标对象、通知、顾问的bean配置都照旧,只需加多以下bean:
<bean class=”org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator”>
(3) 弊端:该自动代理的切面只能是Advisor;不能指定目标对象;
- Bean名称自动代理生成器
(0) 可以指定需要代理的目标对象;需要手动指定切面(可以是通知也可以是顾问);
(1) 配置:目标对象、通知、顾问的bean配置都照旧,只需加多以下bean:
<bean class=”org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator”>
(2) 该bean需要使用两个参数:beanNames,value值为目标对象的beanID,当有多个beanID时使用逗号隔开; interceptorNames,value值为切面beanID,当有多个beanID时使用逗号隔开;
- CGLIB代理
(0) 以上所说的代理皆为JDK动态代理,在代理对象配置中需要指定目标对象的接口;
(1) 而当目标对象没有接口时,则Spring AOP会自动使用CGLIB代理(当然在代理对象配置中也不用指定接口了);
(2) 当目标对象有接口时,也可以指定使用CGLIB代理:在代理对象配置中,加上ProxyTargetClass参数,该参数value值为true|false,为true时,强制使用CGLIB代理;
也可以使用optimize参数,value值也是true|false,为true时,指定了接口则使用jdk代理,无接口则使用CGLIB代理;
(3) JDK代理和CGLIB代理区别:JDK创建代理对象快,但执行慢;CGLIB则相反;
(4) 注:JDK动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
ps.请另外查阅开发过程中需要使用的jar包