一、为什么要使用AOP
当我们需要为多个不具有继承关系的对象引入一个公共行为,例如日志、权限验证、事务等功能时,只能在每个对象里引用公共行为,这样做不便于维护,而且有大量重复代码。AOP就是为了解决上述问题的。
用一个例子来介绍下:
需求:进行增加和删除业务时,我们需要执行时添加事务开启。
1、使用静态代理
(1) 代理模式:为其他对象提供一种代理以控制对这个对象的访问。没有代理商的话,手机厂家自己卖手机,现在有了淘宝的代理商,那么卖手机的业务就交给手机淘宝代理商来完成
(2) 静态代理模式:知道代理的存在,程序在运行前就已经知道这个代理类是存在的,然后才能访问。、
使用静态代理的代码如下:
第一步:接口IUserDao
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.justnow.aop; 2 3 public interface IUserDao { 4 void save(); 5 void find(); 6 }
第二步:目标对象,即实现接口
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.justnow.aop; 2 3 public class UserDao implements IUserDao{ 4 public void save() { 5 System.out.println("模拟保存用户"); 6 } 7 8 public void find() { 9 System.out.println("模拟查询用户"); 10 } 11 }
第三步:代理类。代理对象,要实现与目标对象一样的接口。目标对象必须要实现接口
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.justnow.aop; 2 3 public class UserDaoProxy implements IUserDao{ 4 5 private IUserDao target = new UserDao(); 6 public void save() { 7 System.out.println("开启事务"); 8 target.save(); 9 System.out.println("提交事务"); 10 } 11 12 public void find() { 13 target.find(); 14 } 15 }
第四步:测试代理
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.justnow.aop; 2 3 public class Main { 4 public static void main(String[] args) { 5 IUserDao proxy = new UserDaoProxy(); 6 proxy.save(); 7 proxy.find(); 8 } 9 }
结果如下:
静态代理可以很好的完成这个需求,但是存在两个问题
第一:一个代理对应一个接口。系统中不可能只有user表需要增删改查,还有订单表,学生表等。难道要为每个业务的对象都用代理模式?这就又回到本章开头的问题上了
第二:如果一个对象需要增加新的方法,代理对象也要跟着增加方法。
所以,有了我们的第二种方法,动态代理方法。
2、使用动态代理
JAVA面试50讲之9:动态代理的原理是什么? - 编码前线的文章 - 知乎 https://zhuanlan.zhihu.com/p/54733692
代理对象在程序运行时创建的代理方式,被成为动态代理。我们知道,静态代理中,代理类是自己定义好的,在程序运行之前就已经编译完成的。然而动态代理
代理类并不是在java代码中定义的,而是在运行时根据我们在java代码中的“指示”动态生成的。
spring有两种动态代理:JDK动态代理和cglib字节码增强
JDK动态代理:JDK动态代理只能对实现了接口的类生成代理,而不能针对类。
cglib字节码增强:是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。因为是继承,所以该类或方法最好不要声明final。
JDK动态代理实现步骤:
(1) 创建一个实现接口InvocationHandler的类,它必须实现invoke()方法
(2) 创建需要被代理的接口和实现类
(3) 通过Proxy的静态方法newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理
(4) 通过代理调用方法
(5) 生成的动态代理类会调用InvocationHandler实现类中的invoke方法
(6) 在invoke方法中,可以选择执行被代理实现类的方法,并在其前后进行处理。比如说,不同的方法前后执行的内容不一样。
提供一个新的类:MethodInterceptor类,只有方法为save时完成开启事务和提交事务的行为。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.justnow.aop; 2 3 import java.lang.reflect.InvocationHandler; 4 import java.lang.reflect.Method; 5 6 /** 7 * 调用处理器实现类 8 * 每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象 9 */ 10 public class MethodInterceptor implements InvocationHandler { 11 //需要代理的目标类 12 private Object target; 13 14 /** 15 * 构造方法,初始化需要代理的目标类 16 * @param target 17 */ 18 public MethodInterceptor(Object target){ 19 this.target = target; 20 } 21 22 /** 23 * 该方法负责集中处理动态代理类上的所有方法调用。 24 * 调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行 25 * @param proxy 代理类实例 26 * @param method 被调用的方法对象 27 * @param args 调用参数 28 * @return 29 * @throws Throwable 30 */ 31 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 32 //获取当前执行的方法名字 33 String name = method.getName(); 34 /** 35 * 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用,save方法前需要开启事务,其后需要提交事务。 36 * method.invove(target, args)执行目标类中的方法。 37 */ 38 if("find".equals(name)){ 39 method.invoke(target, args); 40 }else if("save".equals(name)){ 41 System.out.println("开启事务"); 42 method.invoke(target, args); 43 System.out.println("提交事务"); 44 } 45 return null; 46 } 47 }
测试类:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.justnow.aop; 2 3 import java.lang.reflect.Proxy; 4 5 public class Main { 6 public static void main(String[] args) { 7 /* 8 IUserDao proxy = new UserDaoProxy(); 9 proxy.save(); 10 System.out.println("------------------------------"); 11 proxy.find(); 12 */ 13 IUserDao target = new UserDao(); 14 MethodInterceptor proxyObject = new MethodInterceptor(target); 15 16 /** 17 * 通过Proxy的静态方法newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)来创建一个代理 18 * 三个参数分别是目标类的类加载器、目标类的一组接口以及调用处理器生成动态代理类实例 19 */ 20 IUserDao userDao = (IUserDao) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), proxyObject); 21 userDao.save(); 22 System.out.println("------------------------------"); 23 userDao.find(); 24 25 } 26 }
结果如下:
动态代理,当我们目标类需要增加新的方法时,不需要在代理类中增加相应的方法;针对需要代理多个类的问题,只需要声明不同的目标类即可。
AOP的实现原理也是动态代理!
3、AOP的定义
定义:面向切面编程,核心原理是使用动态代理模式在方法执行前后或出现异常时,加入相关逻辑。
(1) AOP是基于动态代理模式的。
(2) AOP是在方法级别的。
(3) AOP可以让应用对象只关注于自己所针对的业务领域问题,而其他方面的问题有其他对象来处理,实现业务业务逻辑与关注点逻辑分离。
二、Spring中的AOP
1、AOP中的术语
Joinpoint(连接点):指那些被拦截到的点。在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点。
Pointcut(切入点):指已经被增强的连接点。
Advice(通知/增强) :所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。通知的类型分为前置通知、后置通知、异常通知、最终通知以及环绕通知。
Introduction(引介) :引介是一种特殊的通知,在不修改代码的前提下,Introduction可以在运行期为类动态地添加一个方法或者FIeld
Target(目标对象):代理的目标对象
Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。
Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
Aspect(切面):是切入点和通知(引介)的结合。
2、AOP联盟通知类型
AOP联盟为通知Advice定义了org.aopalliance.aop.Advice
Spring按照通知Advice在目标类方法的连接点位置,可以分为5类
前置通知 org.springframework.aop.MethodBeforeAdvice
•在目标方法执行前实施增强
后置通知 org.springframework.aop.AfterReturningAdvice
在目标方法执行后实施增强
环绕通知 org.aopalliance.intercept.MethodInterceptor
•在目标方法执行前后实施增强
异常抛出通知 org.springframework.aop.ThrowsAdvice
•在方法抛出异常后实施增强
引介通知 org.springframework.aop.IntroductionInterceptor
在目标类中添加一些新的方法和属性
3、Spring编写代理半自动
使用工厂Bean创建代理对象,从Spring容器中手动的获取代理对象。
第一步:目标类
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.justnow.service.impl; 2 3 import com.justnow.service.IUserService; 4 5 public class UserServiceImpl implements IUserService { 6 public void addUser() { 7 System.out.println("****添加用户****"); 8 } 9 10 public void deleteUser() { 11 System.out.println("****删除用户****"); 12 } 13 }
第二步:切面类
将需要的增强的方法与切入点结合起来
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.justnow.service; 2 3 import org.aopalliance.intercept.MethodInterceptor; 4 import org.aopalliance.intercept.MethodInvocation; 5 6 public class MyAspect implements MethodInterceptor { 7 public Object invoke(MethodInvocation methodInvocation) throws Throwable { 8 System.out.println("方法执行前"); 9 Object proceed = methodInvocation.proceed(); 10 System.out.println("方法执行后"); 11 return proceed; 12 } 13 }
第四步:Spring配置文件
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:context="http://www.springframework.org/schema/context" 5 xsi:schemaLocation="http://www.springframework.org/schema/beans 6 http://www.springframework.org/schema/beans/spring-beans.xsd 7 http://www.springframework.org/schema/context 8 http://www.springframework.org/schema/context/spring-context.xsd"> 9 10 <!--业务类--> 11 <bean id="userService" class="com.justnow.service.impl.UserServiceImpl" /> 12 13 <!--切面类--> 14 <bean id="myAspect" class="com.justnow.service.MyAspect" /> 15 16 <!--使用工厂bean的创建代理--> 17 <!-- 18 interfaces:确定接口,多个接口使用array,单个用value 19 target:确定目标 20 interceptorNames:切面类 21 默认使用jdk代理 22 底层机制: 23 如果目标类有接口,采用jdk动态代理 24 如果没有接口,采用cglib字节码增强 25 如果声明optimize=true,无论是否有接口,都采用cblib 26 --> 27 <bean id="proxyService" class="org.springframework.aop.framework.ProxyFactoryBean"> 28 <property name="interfaces" value="com.justnow.service.IUserService"/> 29 <property name="target" ref="userService"/> 30 <property name="interceptorNames" value="myAspect"/> 31 <!--<property name="optimize" value="true" />--> 32 </bean> 33 34 </beans>
第五步:测试
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.justnow.test; 2 3 import com.justnow.service.IUserService; 4 import org.junit.Test; 5 import org.springframework.context.support.ClassPathXmlApplicationContext; 6 7 public class Test01 { 8 @Test 9 public void test1(){ 10 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); 11 IUserService proxyService = (IUserService) context.getBean("proxyService"); 12 proxyService.addUser(); 13 proxyService.deleteUser(); 14 } 15 }
3、Spring AOP全自动编程
第一步:修改Spring的配置文件
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:p ="http://www.springframework.org/schema/p" 5 xmlns:context ="http://www.springframework.org/schema/context" 6 xmlns:aop ="http://www.springframework.org/schema/aop" 7 xsi:schemaLocation="http://www.springframework.org/schema/beans 8 http://www.springframework.org/schema/beans/spring-beans.xsd 9 http://www.springframework.org/schema/context 10 http://www.springframework.org/schema/context/spring-context.xsd 11 http://www.springframework.org/schema/aop 12 http://www.springframework.org/schema/aop/spring-aop.xsd"> 13 14 15 16 <!--业务类--> 17 <bean id="userService" class="com.justnow.service.impl.UserServiceImpl" /> 18 19 <!--切面类--> 20 <bean id="myAspect" class="com.justnow.service.MyAspect" /> 21 22 <!--使用工厂bean的创建代理--> 23 <!-- 24 interfaces:确定接口,多个接口使用array,单个用value 25 target:确定目标 26 interceptorNames:切面类 27 默认使用jdk代理 28 底层机制: 29 如果目标类有接口,采用jdk动态代理 30 如果没有接口,采用cglib字节码增强 31 如果声明optimize=true,无论是否有接口,都采用cblib 32 --> 33 <!-- 34 <bean id="proxyService" class="org.springframework.aop.framework.ProxyFactoryBean"> 35 <property name="interfaces" value="com.justnow.service.IUserService"/> 36 <property name="target" ref="userService"/> 37 <property name="interceptorNames" value="myAspect"/> 38 <!–<property name="optimize" value="true" />–> 39 </bean> 40 --> 41 <!--AOP实现步骤 42 1、导入命名空间 43 2、proxy-target-class="true" 使用cglib代理 44 3、aop:pointcut 切入点,从目标对象获取具体方法 45 切入点表达式: 46 execution(* com.justnow.service.impl.UserServiceImpl.*.*(..)) 47 选择方法 类型任意 方法名任意 参数任意 48 4、特殊的切入面 49 advice-ref 通知 50 pointcut-ref 切入点引用 51 --> 52 <aop:config proxy-target-class="false"> 53 <aop:pointcut id="myPointCut" expression="execution(* com.justnow.service.*.*(..))" /> 54 <aop:advisor advice-ref="myAspect" pointcut-ref="myPointCut" /> 55 </aop:config> 56 57 </beans>
第二步:测试。此时获得Bean是UserService,而不是代理对象
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 @Test 2 public void test2(){ 3 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); 4 IUserService userService = (IUserService) context.getBean("userService"); 5 userService.addUser(); 6 userService.deleteUser(); 7 }
结果为:
参考:
https://zhuanlan.zhihu.com/p/28097563
https://blog.csdn.net/weixin_39270764/article/details/79874190