Spring 之 AOP
一.AOP 简介
AOP Aspect Oriented Programming ,面向切面编程, 通过预编译方式和运行期动态代理实现程序功能的横向多模块统一控制的一种技术,一种设计思想!
AOP 思想: 基于动态代理的实现,对原来的目标对象,根据字节码创建代理对象,在代理对象中,对原有的业务方法进行增强!
二.AOP 相关术语
- Joinpoint : 连接点, 就是指那些被拦截到的点,在Spring中就是方法,因为Spring 支持方法类型的连接点.
- Pointcut : 切入点 ,就是指我们要对具体的Jointpoint 进行拦截的配置定义!
- Advice :通知 ,就是在拦截到Joinpont之后所要做的事情! 通知分为: 前置通知,后置通知,异常通知,最终通知,环绕通知
- Introduction :引介是一种特殊的通知在不修改代码的前提下,Introduction可以在运行期为类动态地添加一些方法或字段
- Target : 代理的目标对象
- Weaving : 植入, 是指把增强应用到目标类对象来创建新的代理的过程!Spring 采用动态代理植入,而AspectJ 采用编译期植入
- Proxy : 一个类被AOP 植入增强后,产生的一个结果代理类!
- Aspect : 切面, 是切入点和通知的结合
三. AOP 底层实现机制
Spring AOP 的底层实现机制为: JDK 的动态代理 和CGLIB 的动态代理
3.1 JDK 的动态代理
要点:
1. 必须对接口生成代理
2.采用Proxy 对象,通过newProxyInstance 方法为目标对象创建代理对象,该方法需要,
目标类的类加载器,目标接口,回调函数对象 InvocationHandler
3.实现InvocationHandler 接口中Invoke 方法,对目标对象每个方法调用时,都会执行invoke方法
- 目标:
public interface UserDao { void save(); }
public class UserDaoImpl implements UserDao{ @Override public void save() { System.out.println(" 保存用户"); } }
- 通知 Advice
public class Advice { public void beforeAdvice(Method method){ System.out.println("before invoke method is "+method.getName()); } public void afterAdvice(Method method){ System.out.println(" after invoke method is "+method.getName()); } }
- 工具类(把UserDao 接口对象生成代理)
public class JdkProxyFactory implements InvocationHandler{ //目标对象 private Object target; //通知 private Advice advice; public JdkProxyFactory(Object target,Advice advice){ this.target=target; this.advice=advice; } // 生成代理 public Object getProxy(){ return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this ); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object retVal=null; advice.beforeAdvice(method); retVal=method.invoke(target,args); advice.afterAdvice(method); return retVal; } }
- 测试
public class ProxyTest { @Test public void testJdkProxy(){ //获取target 目标对象 UserDao target=new UserDaoImpl(); //获取advice Advice advice = new Advice(); //获取target 目标对象的 proxy 对象 JdkProxyFactory factory = new JdkProxyFactory(target, advice); UserDao proxy= (UserDao) factory.getProxy(); //执行代理的方 proxy.save(); } }
- 原理分析
执行代理对象的所有方法,就是执行InvocationHandler中invoke 方法,调用目标对象真实业务方法(缺点是,jdk 的动态代理 代理的目标对象,必须要实现业务接口)
3.2 CGLIB 动态代理
CGLIB code generation library ,可以直接对目标对象(可以没有接口) 生成代理对象,直接对目标类 创建子类对象!
- 目标
public class PersonDao { public void list(){ System.out.println("查看所有人的信息!!"); } }
- 工具类
public class CglibProxyFactory implements MethodInterceptor { //目标对象 private Object target; //构造器传入目标对象 public CglibProxyFactory(Object target){ this.target=target; } //生成目标对象的代理对象 public Object createProxy(){ //创建Enhancer 对象 Enhancer enhancer=new Enhancer(); //cglib 对目标类建立子类,传入目标对象 enhancer.setSuperclass(target.getClass()); //传入 回调函数 enhancer.setCallback(this); //创建代理对象,并返回 return enhancer.create(); } /** * * @param proxy CGLIB 根据目标父类对象生成的子类代理对象 * @param method 拦截的方法 * @param args 拦截的参数 * @param methodProxy 方法的代理对象,用于执行父类的方法 * @return * @throws Throwable */ @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("权限控制...."); return methodProxy.invokeSuper(proxy,args); // return method.invoke(target,args); } }回调对象中提供了intercept 函数,和JDK 代理的invoke 函数类似,Intercept 多了 MethodProxy 参数,用于调用父类的方法!
- 测试
public class CglibTest { public static void main(String[] args) { PersonDao target=new PersonDao(); CglibProxyFactory factory = new CglibProxyFactory(target); PersonDao proxy = (PersonDao) factory.createProxy(); proxy.list(); } }图解:
3.3 Spring Aop 代理小结
1. 如果目标对象有接口,Spring 优先使用JDK 动态代理
2.如果目标对象没有接口,Spring 采用Cglib 动态代理
3. 被代理增强的方法,不能为final 修饰
3.4 AOP 开发所需依赖
除了,spring 核心jar以外,还需如下:
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.4</version> </dependency>
四.AspectJ 切点
AspectJ 是第三方 AOP 框架,Spring 2.0 之后,开始支持AspectJ 的语法和配置!
AspectJ 是一个面向切面的框架,它扩展了Java 语言.它定义了AOP 语法所以它有个专门的编译器来生成遵守java 字节编码规范的Class
4.1 AspectJ 切入点语法格式
* 表示任意字符;
..表示子包或任意参数
+ 表示子类或实现类
4.1.1 execution
语法为: execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)
execution( public * *.*(..)) 匹配spring 所管理bean的所有public 方法
execution(* com.zhgboy.myspring.aop.service.HelloService.sava(..)) 匹配HelloService下的save 方法
execution(* com.zhgboy.myspring.aop.service..*.*(..)) 匹配该路径service下(包括子包)所有Bean方法
execution(* com.zhgboy.myspring.aop.dao.BaseDaop+.*(..))匹配BaseDao子类或接口实现类所有方法 4.1.2 within
within(com.zhgboy.myspring.aop.service.. *) 匹配该路径下所有的类(包含子包
4.1.3 bean
bean(helloService)
匹配Baen name 为helloService的所有方法
4.1.4
target 具体类型
target(com.zhgboy.myspring.aop.dao.userDao)
匹配UserDao 类所有的方法
4.1.5 args
args(java.io.Serizlizable) 匹配所有参数为Serializable 类型的方法
4.2 AOP 简单实现
① 创建目标
public interface CustomerService { /** 保存客户 */ void save(); /** 修改客户 */ void update(); /** * 删除客户 */ void delete(); /** 查询客户 */ void search(); }
public class CustomerServiceImpl implements CustomerService { /**保存客户 */ @Override public void save() { System.out.println("保存客户"); } /** 修改客户*/ @Override public void update() { System.out.println("修改客户"); } /** 删除客户 */ @Override public void delete() { System.out.println("删除客户"); } /** 查询客户 */ @Override public void search() { System.out.println("查询客户"); } }② Advic
import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; /** * @version 3.0 * @Author :History.GreatMan.Mao * @Description: * @Date Created in 15:39 on 26/01/2018. */ public class TimeMonitorAdvice implements MethodInterceptor { @Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { long start=System.currentTimeMillis(); Object retVal = methodInvocation.proceed(); long end =System.currentTimeMillis(); System.out.println(methodInvocation.getMethod().getName()+ "方法运行时长为 "+ (end-start)); return retVal; } }③ 配置 xml 文件
<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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd "> <!-- 1 目标 --> <bean id="customerService" class="com.zhgboy.mysprng.aop.service.impl.CustomerServiceImpl"/> <!--2. 通知 --> <bean id="timeMonitorAdvice" class="com.zhgboy.mysprng.aop.advice.TimeMonitorAdvice"/> <!--3. 配置切面--> <aop:config> <!-- aop:pointcut 切入点 aop:advisor 配置 JDK 的切面,只能一个切入点和一个通知 aop:aspect 配置 AspectJ 的切面,可以包含多个切入点和多个通知 --> <aop:pointcut id="mypoint1" expression="bean(customerService)"/> <aop:pointcut id="mypoint2" expression="execution(* com.zhgboy.mysprng.aop.service.CustomerService+.*(..))"/> <aop:advisor advice-ref="timeMonitorAdvice" pointcut-ref="mypoint1"></aop:advisor> </aop:config> </beans>
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
支持 xml 配置文件的 <aop:config> 的schma!
另外,当配置AOP 切面后,Spring 在构造对象是,是基于后处理前BeanPostProcessor 自动为对象创建代理,
如果有接口,就默认采用JDk 的代理,如果没有 就采用CGLIB 代理!
如果:目标实现了接口,但是还想使用Cglib 做代理,需要如下配置即可
<!--3. 配置切面 proxy-target-class="false" 优先对接口代理; proxy-target-class="true" 优先使用Cglib 代理; --> <aop:config proxy-target-class="true">
4.3 AspectJ 支持 Advice 类型(xml 配置文件)
① before : 在目标方法运行前增强 --- 前置通知
② afterReturning: 在目标方法运行后增强 -- 后置通知
③ around : 在目标方法运行前后增强 --- 环绕通知
④ afterThrowing : 在目标方法抛出异常后进行增强, -- 抛出通知
⑤After : 不官是否发生异常,该通知都会被执行!
⑥ declareParents :引介通知 -- 对类进行增强
AspectJ 的每种通知,都可以接收 JoinPoint 类型对象(链接信息点)
4.3.1 Before前置通知
前置通知: 在方法前进行增强,主要应用于 日志记录以及权限控制
<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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd "> <!-- 1 目标 --> <bean id="customerService" class="com.zhgboy.mysprng.aop.service.impl.CustomerServiceImpl"/> <!--2. 通知 --> <bean id="myAdvice" class="com.zhgboy.mysprng.aop.advice.MyAspectJ"/> <!--3. 配置切面 --> <aop:config> <!--ref 引用切面类 Advice类 id --> <aop:aspect ref="myAdvice"> <!-- 切入点 --> <aop:pointcut id="mypointcut" expression="target(com.zhgboy.mysprng.aop.service.impl.CustomerServiceImpl)"/> <!-- 前置通知 --> <aop:before method="before" pointcut-ref="mypointcut"/> </aop:aspect> </aop:config>
通知为:
public class MyAspectJ { /**
* 前置通知 * @param joinPoint 被拦截的方法的对象! */ public void before(JoinPoint joinPoint){ System.out.println("前置通知 -- 访问目标类的: "+joinPoint.getTarget().getClass().getName()); System.out.println("前置通知 -- 访问的方法: "+joinPoint.getSignature().getName()); }}
4.3.2 AfterReturning 后置通知
后置通知: 在目标方法执行后,主要是针对 方法的返回值进行 增强的逻辑!
主要的应用场景: 自动发送邮件,短信等
<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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd "> <!-- 1 目标 --> <bean id="customerService" class="com.zhgboy.mysprng.aop.service.impl.CustomerServiceImpl"/> <!--2. 通知 --> <bean id="myAdvice" class="com.zhgboy.mysprng.aop.advice.MyAspectJ"/> <!--3. 配置切面 --> <aop:config> <!--ref 引用切面类 Advice类 id --> <aop:aspect ref="myAdvice"> <!-- 切入点 --> <aop:pointcut id="mypointcut" expression="target(com.zhgboy.mysprng.aop.service.impl.CustomerServiceImpl)"/> <!-- 前置通知 --> <!--<aop:before method="before" pointcut-ref="mypointcut"/>--> <!-- 后置通知 --> <aop:after-returning method="afterReturning" pointcut-ref="mypointcut" returning="retVal"/> </aop:aspect> </aop:config> </beans>Advice
/** * 后置通知 * @param joinPoint 连接点信息,可以从中获取哪个类的哪个方法,以及方法的参数等 * @param retVal 方法的返回值,参数的名称需要配置文件一致 */ public void afterReturning(JoinPoint joinPoint,Object retVal){ System.out.println("afterReturning >> retVal "+retVal); }
4.3.3 Around 环绕通知
环绕通知: 在目标方法执行前后,都可以进行增强(控制目标方法的执行)
主要的应用场景: 日志,缓存,权限,性能监控,事务管理
<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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd "> <!-- 1 目标 --> <bean id="customerService" class="com.zhgboy.mysprng.aop.service.impl.CustomerServiceImpl"/> <!--2. 通知 --> <bean id="myAdvice" class="com.zhgboy.mysprng.aop.advice.MyAspectJ"/> <!--3. 配置切面 --> <aop:config> <!--ref 引用切面类 Advice类 id --> <aop:aspect ref="myAdvice"> <!-- 切入点 --> <aop:pointcut id="mypointcut" expression="target(com.zhgboy.mysprng.aop.service.impl.CustomerServiceImpl)"/> <!-- 前置通知 --> <!--<aop:before method="before" pointcut-ref="mypointcut"/>--> <!-- 后置通知 --> <!-- <aop:after-returning method="afterReturning" pointcut-ref="mypointcut" returning="retVal"/>--> <!-- 环绕通知 --> <aop:around method="around" pointcut-ref="mypointcut"/> </aop:aspect> </aop:config>Advice
接收参数: Around 通知接收的参数不是 JoinPoint 而是ProceedingJoinPoint(可执行连接点)
同时也需要抛出异常 Throwable
/** * 环绕通知 * @param proceedingJoinPoint 可执行的连接点 * @return * @throws Throwable */ public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{ System.out.println("开启事务.."); //执行 目标对象方法 Object retVal = proceedingJoinPoint.proceed(); System.out.println("提交事务,关闭链接... "); return retVal; }
4.3.4 AfterThrowing 抛出通知
抛出通知: 在目标方法抛出异常时,执行的通知
主要的应用场景: 异常处理,异常转换,以及记录日志等
<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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd "> <!-- 1 目标 --> <bean id="customerService" class="com.zhgboy.mysprng.aop.service.impl.CustomerServiceImpl"/> <!--2. 通知 --> <bean id="myAdvice" class="com.zhgboy.mysprng.aop.advice.MyAspectJ"/> <!--3. 配置切面 --> <aop:config> <!--ref 引用切面类 Advice类 id --> <aop:aspect ref="myAdvice"> <!-- 切入点 --> <aop:pointcut id="mypointcut" expression="target(com.zhgboy.mysprng.aop.service.impl.CustomerServiceImpl)"/> <!-- 前置通知 --> <!--<aop:before method="before" pointcut-ref="mypointcut"/>--> <!-- 后置通知 --> <!-- <aop:after-returning method="afterReturning" pointcut-ref="mypointcut" returning="retVal"/>--> <!-- 环绕通知 --> <!-- <aop:around method="around" pointcut-ref="mypointcut"/>--> <!-- 抛出通知 throwing 用来配置方法接收异常对象的名称 --> <aop:after-throwing method="afterThrowing" pointcut-ref="mypointcut" throwing="exception" /> </aop:aspect> </aop:config> </beans>Advice
/** * 抛出通知 * @param joinPoint 连接点信息,可以从中获取哪个类的哪个方法,以及方法的参数等 * @param exception 代表目标方法抛出的异常,参数名称需要在配置文件中配置 */ public void afterThrowing(JoinPoint joinPoint,Throwable exception){ System.out.println("执行"+joinPoint.getTarget().getClass().getSimpleName()+" 类的 "+joinPoint.getSignature().getName()+"方法 发生异常 >"+exception.getMessage()); }
4.4 AspectJ 支持 Advice 类型(@Aspect注解开发)
支持@AspectJ 的注解开发,配置文件需要引如aop 名称空间,并且开启AspectJ 注解自动代理
<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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd ">
<!-- 注解自动代理开启--> <aop:aspectj-autoproxy />Advice 类型种类
① @Before 前置通知
② @AfterReturning 后置通知
③ @Around 环绕通知
④AfterThrowing 抛出通知
⑤ @After 最终通知
⑥ @DeclareParents 引介通知
@Aspect //通知需要使用 该注解 声明为切面 @Component public class MyAnnoAsepectJ { @Before("target(com.zhgboy.mysprng.aop.service.person.impl.PersonServiceImpl)") public void before(){ System.out.println("注解式 前置方法增强..."); } @Before("target(com.zhgboy.mysprng.aop.service.person.impl.PersonServiceImpl)") public void before(JoinPoint joinPoint){ System.out.println("注解式 前置方法增强 >>>"+ joinPoint.getSignature().getName()); } @AfterReturning(value = "target(com.zhgboy.mysprng.aop.service.person.impl.PersonServiceImpl)",returning = "retVal") public void afterReturning(JoinPoint joinPoint,Object retVal){ System.out.println("注解式 后置方法增强 >>>"+ joinPoint.getSignature().getName()); System.out.println("注解式 后置方法增强 后 的返回值 >>>"+ retVal ); } @Around("execution(* com.zhgboy.mysprng.aop.service.. *.*(..))") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{ System.out.println("注解式 环绕通知 开启事务 ,,,"); Object retVal = proceedingJoinPoint.proceed(); System.out.println("注解式 环绕通知 提交事务,并关闭连接 ,,,"); return retVal; } @AfterThrowing(value = "within(com.zhgboy.mysprng.aop.service..*)",throwing = "e") public void afterThrowing(JoinPoint joinPoint,Throwable e){ System.out.println("注解式 抛出通知 ,异常的原因 : "+e.getMessage()); } @After("this(com.zhgboy.mysprng.aop.service.person.impl.PersonServiceImpl)") public void after(JoinPoint joinPoint){ System.out.println("最终通知,释放资源 .... "); } }
4.5 AspectJ 的各种Advice,接收参数说明
4.5.1 参数
① Before 前置通知 ,接收的参数为 JoinPoint ;
② AfterReturning 后置通知, 可以接收的参数为 JoinPont 和Object (目标方法的返回值);
③ Around 环绕通知, 可以接收的参数为 ,ProceedingJoinPont 可以执行的连接点! (需要由Object 返回值,和 throws Throwable 异常);
④ AfterThrowing 抛出通知,可以接收的参数为 JoinPoint,Throwable(目标方法抛出的异常)
⑤ After 最终通知: 可以接收的参数为JointPoint
以上,参数可以写,也可以不写!
4.5.2 JoinPoint 常用API
JoinPoint 连接点,可以通过 JoinPoint 获取目标方法执行信息,调用的那个类,调用那个方法,传递的什么参数!
① 获取类 jointPoint.getTarget().getClass();
② 获取方法 jointPi0nt.getSignature().getName();
③ 获取参数 joinPoint.getArgs();