-
- AOP简介
AOP(Aspect Orient Programming),面向切面编程,是面向对象编程(OOP)的一种补充,面向对象编程是从静态角度考虑程序的结构,而面向切面编程是从动态角度考虑程序运行过程。
AOP底层,就是采用动态代理模式实现的。采用了两种代理:JDK的动态代理,与CGLIB的动态代理。
面向切面编程,就是将交叉 业务逻辑封装称切面,利用AOP容器的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、事务、日志等。
若不使用AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样,会使主业务逻辑变的混杂不清。
-
-
- AOP编程的术语
-
- 切面(Aspect)
切面泛指交叉业务逻辑。常用的切面有通知与顾问。实际就是对主业务逻辑的一种增强。
- 织入(Weaving)
织入就是指将切面代码插入到目标对象的过程。
- 连接点(JoinPoint)
通常业务接口中的方法均为连接点
- 切入点(Pointcut)
切入点指切面具体织入的方法。在StudentServiceImpl类中,若doSome()将被增强,而doOther()不被增强,则doSome()为切入点,而doOther()仅为连接点。
被标记为final的方法不可作为切入点。
- 目标对象(Target)
指将要被增强的对象,即包含主业务逻辑的类的对象。
- 通知(Advice)
通知是切面的一种实现,可以完成简单织入的功能(织入功能就是在这里完成的)。换个角度说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。
切入点定义切入的位置,通知定义切入的时间。
- 顾问(Advisor)
切面的另一种实现,是将通知包装为更复杂切面的装配器。
1.2 通知详解
1.2.1 前置通知MethodBeforeAdvice
定义前置通知,需要实现MethodBeforeAdvice接口,该接口中有一个方法before(),会在目标方法执行之前执行。前置通知的特点:
- 在目标方法执行之前执行
- 不改变目标方法的执行流程,前置通知代码不能阻止目标方法执行
- 不改变目标方法执行的结果
1.2.2 后置通知AfterReturningAdvice
定义后置通知,需要实现借楼AfterReturningAdvice。该接口中有一个方法afterReturning(),会在目标方法执行之后执行。后置通知的特点:
- 在目标方法执行之后执行
- 不改变目标方法执行的流程,后置通知代码不能阻止目标方法的执行
- 不改变目标方法执行的结果
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
//后置通知:可以获取到目标方法的返回值,但无法改变目标方法的结果
public class MyAfterReturningAdvice implements AfterReturningAdvice {
//returnValue:目标方法的返回值
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("目标方法执行之后执行。。。。");
if(returnValue!=null){
returnValue = ((String)returnValue).toUpperCase();
}
System.out.println("returnValue = "+returnValue);
}
}
1.2.3 环绕通知 MethodInterceptor
定义环绕通知,需要实现MethodInterceptor接口。环绕通知也叫方法拦截器,可以在目标方法执行之前之后做处理,可以改变目标方法的的返回值,也可以改变程序执行流程。
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
//环绕通知
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("执行环绕通知:目标方法执行之前");
//执行目标方法,result为目标方法的返回值
Object result = invocation.proceed();
System.out.println("执行环绕通知:目标方法执行之后");
return result;
}
}
1.2.4 异常通知 ThrowsAdvice
package edu.sdut.service;
import org.springframework.aop.ThrowsAdvice;
//异常通知
public class MyThrowsAdvice implements ThrowsAdvice {
/**
* 当目标方法抛出与指定类型的异常具有is-a关系的异常时,
* 执行当前方法
* @param ex
*/
public void afterThrowing(Exception ex){
System.out.println("执行异常通知方法。。。");
}
}
1.3顾问 Advisor
通知(Advice)是Spring提供的一种切面(Aspect)。但其功能过于简单:只能将切面织入到目标类的所有目标方法中,无法完成将切面织入到指定的目标方法中。
顾问(Advisor)是Spring提供的另一种切面,其可以完成更为复杂的切面织入功能。
PointcutAdvisor是顾问的一种,可以指定具体的切入点。顾问将通知进行了包装,会根据不同的通知类型,在不同的时间点,将切面织入到不同的切入点。
PointcutAdvisor接口有两个较为常用的实现类:
- NameMatchMethodPointcutAdvisor名称匹配方法切入点顾问。
- RegexpMethodPointcutAdvisor正则表达式匹配方法切入点顾问。
1.3.1名称匹配方法切入点顾问
NameMatchMethodPointcutAdvisor,即名称匹配方法切入点顾问。容器可根据配置文件中指定的方法名来设置切入点。
代码不同修改,只在配置文件中注册一个顾问,然后使用通知属性advice与切入点的方法名mapperName对其进行配置。代理中的切面,使用这个给顾问即可。
以上代码存在的两个问题:
- 若存在多个目标对象,就需要使用多次ProxyFactoryBean来创建多个代理对象,这会使配置文件变得臃肿,不便于管理
- 用户真正想调用的是目标对象,而真正可以调用的却是代理对象,这不符合逻辑。
以上连个原因是由于ProxyFactoryBean功能太简单,解决办法为使用自动代理生成器。
1.4 自动代理生成器
前面代码中所使用的代理对象,均是由ProxyFactoryBean代理工具类生成的。而该代理工具类存在如下缺点:
- 一个代理对象只能代理一个Bean,即如果有两个Bean同时要织入同一个切面,这时,不仅要配置这两个Bean,即两个目标对象,同时还要配置两个代理对象。
- 在客户类中获取Bean时,使用的是代理类的id,而非我们定义的目标对象Bean的id。我们正真想要执行的应该是目标对象。
Spring提供了自动代理生成器,用于解决PeroxyFactoryBean的问题。常用的自动代理生成器有两个:
- 默认advisor自动代理生成器
存在的问题:
(2)Bean名称自动代理生成器
- 不能选择目标对象
- 不能选择切面类型,切面只能时Advisor
- 不能选择advisor,所以advisor均将被作为切面织入到目标方法
需要注意的是,自动代理生成器均继承自Bean后处理器BeanPostProcessor。容器中所有Bean在初始化时均会自动执行Bean后处理器中的方法,故其无需id属性。所以自动代理生成器的Bean也没有id属性,客户类直接使用目标对象Bean的id。\
1.5 AspectJ对AOP的实现
对于AOP这种编程思想,跟读框架都进行了实现。Spring就是其中之一,可以完成面向切面编程。然而,AspectJ也实现了AOP的功能,且其实现方法更为简捷,使用更为方便,而且还支持注解式开发,所以,Spring又将AspectJ的对于AOP的实现也引入到了自己的框架中。
在Spring中使用AOP开发时,一般使用AspectJ的实现方式。
1.5.1 AspectJ的通知类型
AspectJ中常用的五种通知类型:
- 前置通知
- 后置通知
- 环绕通知
- 异常通知
- 最终通知
其中最终通知是指,无论程序执行是否正常,该通知都会执行。类似于try..catch中的finally代码块。
1.5.2 AspectJ的切入点表达式
execution([modifiers-pattern] 访问权限类型
ret-type-pattern 返回值类型
[declaring-type-parrern] 全限定性类名
Name-pattern(param-pattern) 方法名参数()
[throws-pattrtn] 抛出异常类型)
切入点表达式要匹配的对象就是目标方法的方法名。所以,execution表达式中明显就是方法的签名。注意,表达式中加[]的部分表示可以省略部分,各部分间用空格分开。在其中可以使用一下符号:
符号 |
意义 |
* |
零至多个任意字符 |
.. |
用在方法参数中,表示任意多个参数 用在包名后,表示当前包及其子包路径 |
+ |
用在类名后,表示当前类及其子类 用在接口后,表示当前接口及其实现类 |
1.5.3 AspectJ的开发环境
(1)增加4个Jar包:aop,AspectJ,AspectJ和Spring整合的jar包,Aop联盟的jar包
(2)在配置文件中添加aop的约束,因为要用到aop的标签
基于注解实现:
/**
* 使用AspectJ,实现切面编程
*/
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect //表示当前类为注册
public class MyAspect {
/**
* 执行前置通知
* @param jp
*/
@Before("execution(* *..ISomeService.doFirst(..))")
public void myBefore(JoinPoint jp){
//jp表示切入点表达式
System.out.println("执行前置通知方法jp = " + jp);
}
/**
* 执行后置通知
*/
@AfterReturning(value="execution(* *..ISomeService.doSecond(..))",returning="result")
public void myAfterReturening(Object result){
//result为返回结果
System.out.println("执行后置通知方法 result = " + result);
}
/**
* 执行环绕通知
* @throws Throwable
*/
@Around("execution(* *..ISomeService.doSecond(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("执行环绕通知方法,目标方法执行之前");
//执行目标方法
Object result = pjp.proceed();
System.out.println("执行环绕通知方法,目标方法执行之后");
//因为该通知可以返回值,所以可在在这里对结果及进行修改
if(result!=null){
result = ((String)result).toUpperCase();
}
return result;
}
/**
* 执行异常通知
*/
@AfterThrowing("execution(* *..ISomeService.doThird(..))")
public void myAfterThrowing(){
System.out.println("执行异常通知");
}
/**
* 执行自定义异常,并捕获信息
* @param ex
*/
@AfterThrowing(value="execution(* *..ISomeService.doThird(..))",throwing="ex")
public void myAfterThrowing(Exception ex){
System.out.println("执行异常通知方法 ex = " + ex.getMessage());
}
/**
* 执行最终通知
*/
@After("doThirdPointcut()")
public void myAfter(){
System.out.println("执行最终通知");
}
/**
* 定义了一个切入点
* 叫doThirdPointcut()
*/
@Pointcut("execution(* *..ISomeService.doThird(..))")
public void doThirdPointcut(){}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here -->
<!-- 注册切面 -->
<bean id="myAspect" class="edu.sdut.service.MyAspect"/>
<bean id="someService" class="edu.sdut.service.SomeServiceIMPL"></bean>
<!-- 注册aspectj的自动代理 -->
<aop:aspectj-autoproxy/>
</beans>
基于XML实现:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here -->
<!-- 注册切面 -->
<bean id="myAspect" class="edu.sdut.service.MyAspect"/>
<bean id="someService" class="edu.sdut.service.SomeServiceIMPL"></bean>
<!-- AOP配置 -->
<aop:config>
<aop:pointcut expression="execution(* *..ISomeService.doFirst(..))" id="doFirstPointCut"/>
<aop:pointcut expression="execution(* *..ISomeService.doSecond(..))" id="doSecondPointCut"/>
<aop:aspect ref="myAspect">
<aop:before method="myBefore" pointcut="execution(* *..ISomeService.doFirst(..))"/>
<aop:after-returning method="myAfterReturening" pointcut-ref="doFirstPointCut" returning="result"/>
<aop:around method="myAround" pointcut-ref="doSecondPointCut"/>
<aop:after-throwing method="myAfterThrowing(java.lang.Exception)" pointcut-ref="doSecondPointCut" throwing="ex"/>
<aop:after method="myAfter" pointcut-ref="doSecondPointCut"/>
</aop:aspect>
</aop:config>
</beans>
/**
* 使用AspectJ,实现切面编程
*/
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
public class MyAspect {
/**
* 执行前置通知
* @param jp
*/
public void myBefore(JoinPoint jp){
//jp表示切入点表达式
System.out.println("执行前置通知方法jp = " + jp);
}
/**
* 执行后置通知
*/
public void myAfterReturening(Object result){
//result为返回结果
System.out.println("执行后置通知方法 result = " + result);
}
/**
* 执行环绕通知
* @throws Throwable
*/
public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("执行环绕通知方法,目标方法执行之前");
//执行目标方法
Object result = pjp.proceed();
System.out.println("执行环绕通知方法,目标方法执行之后");
//因为该通知可以返回值,所以可在在这里对结果及进行修改
if(result!=null){
result = ((String)result).toUpperCase();
}
return result;
}
/**
* 执行异常通知
*/
public void myAfterThrowing(){
System.out.println("执行异常通知");
}
/**
* 执行自定义异常,并捕获信息
* @param ex
*/
public void myAfterThrowing(Exception ex){
System.out.println("执行异常通知方法 ex = " + ex.getMessage());
}
/**
* 执行最终通知
*/
public void myAfter(){
System.out.println("执行最终通知");
}
}