spring AOP概念及代理模式
spring AOP
代理模式的英文Proxy或Surrogate,中文都可译为”代理“,代理的含义,就是邀请一个人或者一个机构代表另一个人或者另一个机构采取行动。
在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
JAVA中的动态代理和静态代理:
- 静态代理:编译时将增强代码植入class文件,因为是编译期进行的增强,所以代码运行时效率比动态代理高。使用Aspect可以实现静态代理。
- 动态代理:运行时生成代理类并加载,效率比静态代理要低,spring中使用了上文中的两种动态代理的方式来实现代理类的生成。
- 静态代理
由我们程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
1 package com.lhy.ssms.proxy.demo5; 2 3 public interface PersonDao { 4 void addPerson(); 5 }
1 public class PersonDaoImpl implements PersonDao { 2 3 @Override 4 public void addPerson() { 5 System.out.println("add person to my Family"); 6 } 7 }
1 public class Transaction { 2 3 void beginTransaction(){ 4 System.out.println("开始事务 begin Transaction"); 5 } 6 7 void commit(){ 8 System.out.println(" 提交 commit"); 9 } 10 }
好的,我们开始编写静态代理类了 -- 实现PersonDao接口
1 /** 2 * 这个是静态代理类 4 */ 5 public class PersonDaoProxy implements PersonDao{ 6 7 PersonDao personDao; 8 Transaction transaction; 9 10 public PersonDaoProxy(PersonDao personDao, Transaction transaction) { 11 this.personDao = personDao; 12 this.transaction = transaction; 13 } 14 15 @Override 16 public void addPerson() { 17 this.transaction.beginTransaction(); 18 this.personDao.addPerson(); 19 this.transaction.commit(); 20 } 21 }
最后,我们需要 测试
/** * 测试我们前面写的静态代理*/ public class TestPersonProxy { @Test public void testAdd(){ PersonDao personDao = new PersonDaoImpl(); Transaction transaction = new Transaction(); PersonDaoProxy proxy = new PersonDaoProxy(personDao, transaction); proxy.AddPerson(); } }
总结:
1、静态代理模式并没有做到事务的重用
2、假设dao有100个类,100个proxy,接口中有多少方法,在proxy层就得实现多少方法,有多少方法就要开启和提交多少事务
3、如果一个proxy实现了多个接口,如果其中的一个接口发生变化(添加了一个方法),那么proxy也要做相应改动。
- JDK动态代理
采用java内置的代理API实现
动态代理类:在程序运行时,运用反射机制动态创建而成。
JDK的动态代理必须具备四个条件:1、目标接口 2、目标类 3、拦截器 4、代理类
1 import java.lang.reflect.InvocationHandler; 2 import java.lang.reflect.Method; 3 4 /** 5 * 拦截器 6 * 1、目标类导入进来 7 * 2、事物导入进来 8 * 3、invoke完成:开启事务、调用目标对象的方法、事务提交 9 */ 10 public class Interceptor implements InvocationHandler { 11 12 private Object target; // 目标类 13 private Transaction transaction; 14 15 public Interceptor(Object target, Transaction transaction) { 16 this.target = target; 17 this.transaction = transaction; 18 } 19 20 /** 21 * @param proxy 目标对象的代理类实例 22 * @param method 对应于在代理实例上调用接口方法的Method实例 23 * @param args 传入到代理实例上方法参数值的对象数组 24 * @return 方法的返回值,没有返回值是null 25 * @throws Throwable 26 */ 27 public Object invoke(Object proxy, Method method, Object[] args) 28 throws Throwable { 29 String methodName = method.getName(); 30 if ("addPerson".equals(methodName) 31 || "deletePerson".equals(methodName) 32 || "updatePerson".equals(methodName)) { 33 34 this.transaction.beginTransaction(); // 开启事务 35 method.invoke(target); // 调用目标方法 36 this.transaction.commit(); // 提交事务 37 38 } else { 39 method.invoke(target); 40 } 41 return null; 42 } 43 }
进入测试:
/** * 测试jdk动态代理 */ public class TestJDKProxy { @Test public void testAdd(){ /** * 1、创建一个目标对象 * 2、创建一个事务 * 3、创建一个拦截器 * 4、动态产生一个代理对象 */ Object target = new PersonDaoImpl(); Transaction transaction = new Transaction(); Interceptor interceptor = new Interceptor(target, transaction); /** * 参数一:设置代码使用的类加载器,一般采用跟目标类相同的类加载器 * 参数二:设置代理类实现的接口,跟目标类使用相同的接口 * 参数三:设置回调对象,当代理对象的方法被调用时,会调用该参数指定对象的invoke方法 */ PersonDao personDao = (PersonDao) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), interceptor); personDao.savePerson(); } }
总结:
1、因为利用JDKProxy生成的代理类实现了接口,所以目标类中所有的方法在代理类中都有。
2、生成的代理类的所有的方法都拦截了目标类的所有的方法。而拦截器中invoke方法的内容正好就是代理类的各个方法的组成体。
3、利用JDKProxy方式必须有接口的存在。
4、invoke方法中的三个参数可以访问目标类的被调用方法的API、被调用方法的参数、被调用方法的返回类型。
缺点:
1、在拦截器中除了能调用目标对象的目标方法以外,功能是比较单一的,在这个例子中只能处理事务
2、拦截器中的invoke方法的if判断语句在真实的开发环境下是不靠谱的,因为一旦方法很多if语句需要写很多
- CGLIB动态代理:采用第三方API实现
JDK中的代理模式必须要有接口,所以就出现了CGLIB。
使用上个例子的PersonDaoImpl类和Transaction类(不用接口)
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; /** * CGLIB代理 拦截器 */ public class Interceptor implements MethodInterceptor { private Object target; // 代理的目标类 private Transaction transaction; public Interceptor(Object target, Transaction transaction) { this.target = target; this.transaction = transaction; } /** * 创建目标对象的代理对象 * * @return */ public Object createProxy() { // 代码增强 Enhancer enhancer = new Enhancer(); // 该类用于生成代理对象 enhancer.setCallback(this); // 参数为拦截器 enhancer.setSuperclass(target.getClass());// 设置父类 return enhancer.create(); // 创建代理对象 } /** * @param obj 目标对象代理类的实例 * @param method 代理实例上 调用父类方法的Method实例 * @param args 传入到代理实例上方法参数值的对象数组 * @param methodProxy 使用它调用父类的方法 * @return * @throws Throwable */ public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { this.transaction.beginTransaction(); method.invoke(target); this.transaction.commit(); return null; } }
/** * 测试cglib动态代理 * 通过cglib产生的代理对象,代理类是目标类的子类 */ public class TestCglibProxy { @Test public void testAdd(){ Object target = new PersonDaoImpl(); Transaction transaction = new Transaction(); Interceptor interceptor = new Interceptor(target, transaction); PersonDaoImpl personDaoImpl = (PersonDaoImpl) interceptor.createProxy(); personDaoImpl.addPerson(); } }
总结:
1、CGlib是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。
2、用CGlib生成代理类是目标类的子类。
3、用CGlib生成 代理类不需要接口
4、用CGLib生成的代理类重写了父类的各个方法。
5、拦截器中的intercept方法内容正好就是代理类中的方法体
CGLIB和JDK动态代理区别:
JDK:
目标类和代理类实现了共同的接口
拦截器必须实现InvocationHandler接口,而这个接口中invoke方法体的内容就是代理对象方法体的内容
CGLIB:
目标类 是代理类的父类
拦截器必须实现MethodInterceptor接口,而接口中的intercept方法就是代理类的方法体,使用字节码增强机制创建代理对象的.
什么是AOP?
AOP(面向切面编程):
面向切面编程,是一种通过预编译方式运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术.
spring AOP代理机制:
1、若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。
优点:因为有接口,所以使系统更加松耦合
缺点:为每一个目标类创建接口
2、若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。
优点:因为代理类与目标类是继承关系,所以不需要有接口的存在。
缺点:因为没有使用接口,所以系统的耦合性没有使用JDK的动态代理好。
每一种代理都有各自的优缺点,根据需求挑选合适的方法。