一、代理模式概述
1.生活中代理案例
- 房屋中介代理
- 客户手里面没有房源信息,找一个中介
- 商品代购
- 这些代购者可以去拿到比较低成本的商品,拥有自己的渠道
2.为什么要使用代理
- 对于消费者而言,可以减少成本,只需要关心自己需要的商品,不需要去寻找渠道或者是找房源。
3.代理模式在 Java 中的应用
- 统一异常处理
- Mybatis 使用了代理
- Spring aop实现原理
- 日志框架
4.概述
- 代理模式(Proxy Pattern):是23种设计模式中的一种,属于结构型的模式。指一个对象本身不做实际的操作,而是通过其它对象来得到自己想得到的结果。
- 意义:目标对象只需要关心自己的实现细节,通过代理对象来实现功能的增强,可以扩展目标对象的功能。
- 体现了非常重要的编程思想:不能随便修改源码,如果需要修改源码,通过修改代理的方式来实现功能的拓展。
5.生活中代理图示
-
图示
二、代理的实现方式
1.Java 中代理图示
-
图示
-
元素组成
- 接口,定义行为和规范的
- 被代理类,是目标对象
- 代理类,做功能增强的
2.静态代理
2.1案例
- 通过代理模式实现事务操作
2.2实现案例
-
创建 domain 对象
@Data public class Student { private String name; private int age; }
-
创建service 接口定义规范
public interface IStudentService { /** * 添加学生 */ void save(); /** * 查询学生信息 * @param id * @return */ Student query(Long id); }
-
创建实现类,被代理类
public class StudentServiceImpl implements IStudentService { public void save() { System.out.println("保存学生信息"); } public Student query(Long id) { Student student = new Student(); student.setName("sy"); student.setAge(18); return student; } }
-
创建事务类对象
public class DaoTransaction { public void before(){ System.out.println("开启事务操作"); } public void after(){ System.out.println("关闭事务"); } }
-
创建代理类对象
public class ProxyStudent implements IStudentService { //目标类对象 private StudentServiceImpl studentService; //需要做的增强对象 private DaoTransaction transaction; //通过构造器获取目标类和增强类对象 public ProxyStudent(StudentServiceImpl studentService,DaoTransaction daoTransaction){ this.studentService = studentService; this.transaction = daoTransaction; } public void save() { //开启事务操作 transaction.before(); //目标类的操作 studentService.save(); //关闭事务操作 transaction.after(); } public Student query(Long id) { return studentService.query(id); } }
-
测试代理类对象
public class TestStudent { @Test public void testSave(){ DaoTransaction transaction = new DaoTransaction(); StudentServiceImpl studentService = new StudentServiceImpl(); //获取代理类对象 //包含了目标对象以及前置通知和后置通知 ProxyStudent proxyStudent = new ProxyStudent(studentService, transaction); proxyStudent.save(); proxyStudent.query(1L); } }
-
运行结果
2.3静态代理存在问题
- 不利于代码拓展,比如接口中新添加一个抽象方法时,所有实现类都需要重新实现,否则报错
- 代理对象需要创建很多,这种设计很不方便和麻烦
三、动态代理
1. 概述
- 概述:在不改变原有功能代码的前提下,能够动态的实现方法的增强
- 动态代理技术,是在内存中生成代理对象的一种技术。也就是整个代理过程在内存中进行,我们不需要手写代理类的代码,也不会存在代理类编译的过程,而是直接在运行期,在JVM中“凭空”造出一个代理类对象供我们使用。一般使用的动态代理技术有以下两种:
2. 基于JDK(接口)的动态代理:
JDK自带的动态代理技术,需要使用一个静态方法来创建代理对象。
它要求被代理对象,也就是目标类,必须实现接口。
生成的代理对象和原对象都实现相同的接口,是兄弟关系。
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
主要关注参数列表:
ClassLoader loader:固定写法,指定目标类对象的类加载器即可。用于加载目标类及其接口的字节码文件
通常,使用目标类的字节码对象调用getClassLoader()方法即可得到
Class<?>[] interfaces:固定写法,指定目标类的实现的所有接口的字节码对象的数组
通常,使用目标类的字节码对象调用getInterfaces()方法即可得到
InvocationHandler h:
这个参数是一个接口,主要关注它里面唯一一个方法,invoke方法。它会在代理类对象调用方法时执行,也就是说,我们在代理类对象中调用任何接口中的方法时,都会执行到invoke中。所以,我们在此方法中完成对增强或者扩展代码逻辑的编写。
Object invoke(Object proxy, Method method, Object[] args)
proxy:就是代理类对象的一个引用,也就是Proxy.newProxyInstance的返回值,此引用几乎不回用到,忽略即可。
method:对应的是触发invoke执行的方法的Method对象。假如我们调用了xxx方法,该方法触发了invoke的执行,那么,method就是xxx方法对应的反射对象(Method对象)
args:代理对象调用方法时,传递的实际参数
总结:
基于接口的动态代理,实际上是在内存中生成了一个对象,该对象实现了指定的目标类对象拥有的接口。所以代理类对象和目标类对象是兄弟关系。
兄弟关系:并列的关系,不能互相转换,包容性比较差。在后续会学习Spring框架,如果配置JDK的动态代理方式,一定要用接口类型接收代理类。
2.2基础准备
-
创建 service 接口
public interface IStudentService { /** * 添加学生 */ void save(); /** * 查询学生信息 * @param id * @return */ Student query(Long id); }
-
创建service实现类(需要代理的类)
public class StudentServiceImpl implements IStudentService { public void save() { System.out.println("保存学生信息"); } public Student query(Long id) { System.out.println("查询操作"); Student student = new Student(); student.setName("sy"); student.setAge(18); return student; } }
-
增强类
public class DaoTransaction { public void before(){ System.out.println("开启事务操作"); } public void after(){ System.out.println("关闭事务"); } }
2.2实现InvocationHandler 接口
- InvocationHandler 接口,用来做方法拦截
nvocationHandler接口:
InvocationHandler是Java反射机制中的一个接口,它用于代理实例的方法调用。当你通过代理对象调用一个方法时,这个调用会被转发到InvocationHandler的invoke方法。
public Object invoke (Object proxy,Method method ,Object [] args) throws Throwable
三个参数的含义:
Object proxy:我们要代理的目标对象
Method method: 代理对象中实现了接口中方法的实例(这里有点抽象,其实就是代理对象实现了接口中的方法,这个方法也会调用目标对象(委托类)中的同一个方法)
Object [] args: 调用目标对象(委托类)中方法的参数
返回结果:目标对象类中的方法执行后返回的结果
-
实现接口
public class TransactionHandler implements InvocationHandler { //增强类对象 private DaoTransaction transaction; //需要代理的目标对象 private Object obj; public TransactionHandler(DaoTransaction transaction,Object obj){ this.transaction=transaction; this.obj=obj; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object ret = null; //判断当前方法是否是save,是才做事务操作 if("save".equals(method.getName())){ transaction.before(); ret = method.invoke(obj,args); transaction.after(); }else{ ret = method.invoke(obj,args); } return ret; } }
-
分别介绍里面的参数
-
Proxy:代理实例,可以通过newProxyInstance创建代理实例
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
- ClassLoader 类加载器,直接通过需要代理的类获取就行
- Class[]: 目标类所实现的所有接口
- InvocationHandler:方法拦截处理器,可以在里面实现方法的增强
-
Method:执行目标方法的,invoke 方法执行
-
args:参数数组
-
2.3测试
-
方法
@Test public void testSave(){ //增强类对象 DaoTransaction transaction = new DaoTransaction(); //目标执行类 IStudentService service = new StudentServiceImpl(); //方法拦截处理器 TransactionHandler handler = new TransactionHandler(transaction, service); //获取代理实例对象 IStudentService proxyStudentService =(IStudentService) Proxy.newProxyInstance(StudentServiceImpl.class.getClassLoader(), StudentServiceImpl.class.getInterfaces(), handler); proxyStudentService.save(); Student query = proxyStudentService.query(1L); }
2.4底层运行原理
-
生成代理类的字节码来学习
private void saveProxyClass(String path){ byte[] $proxy1s = ProxyGenerator.generateProxyClass("$Proxy1", StudentServiceImpl.class.getInterfaces()); FileOutputStream out = null; try { out = new FileOutputStream(new File(path + "$Proxy1.class")); out.write($proxy1s); } catch (Exception e) { e.printStackTrace(); }finally { if(out !=null){ try { out.flush(); out.close(); } catch (IOException e) { e.printStackTrace(); } } } }
-
生成字节码反编译结果
public final class $Proxy1 extends Proxy implements IStudentService { private static Method m1; private static Method m4; private static Method m2; private static Method m0; private static Method m3; public $Proxy1(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final Student query(Long var1) throws { try { return (Student)super.h.invoke(this, m4, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final void save(Student var1) throws { try { super.h.invoke(this, m3, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m4 = Class.forName("cn.sycoder.service.IStudentService").getMethod("query", Class.forName("java.lang.Long")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); m3 = Class.forName("cn.sycoder.service.IStudentService").getMethod("save", Class.forName("cn.sycoder.domain.Student")); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
-
执行原理图
- 文字说明:
- 通过实现接口,获取到接口里面的所有方法
- 通过Proxy 创建代理类实例
- 通过反射机制,获取到一个个的方法对象
- 调用InvocationHandler 接口中的invoke 方法,从而实现业务的增强
- 文字说明:
3. CGLIB动态代理
3.0、基于CGLIB(父类)的动态代理
第三方CGLIB的动态代理技术,也是可以使用一个静态方法来创建代理对象。
它不要求目标类实现接口,但是要求目标类不能是最终类,也就是不能被final修饰。
因为CGLIB是基于目标类生成改类的一个子类作为代理类,所以目标类必须可被继承。
Enhancer.create(Class type, Callback callback)
主要参数列表:
Class type:指定我们要代理的目标类的字节码对象,也就是指定目标类的类型
Callback callback:此单词的意思叫做回调,意思就是我们提供一个方法,它会在合适的时候帮我们调用它。回来调用的意思。
Callback是一个接口,由于该接口只是一个名称定义的作用,并不包含方法的声明。所以我们使用时通常使用它的一个子接口MethodInterceptor,此单词的意思叫做方法拦截器。
MethodInterceptor接口中也只有一个方法,叫做intercept
Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)
proxy:就是代理类对象的一个引用,也就是Enhancer.create的返回值,此引用几乎不回用到,忽略即可。
method:对应的是触发intercept执行的方法的Method对象。假如我们调用了xxx方法,该方法触发了intercept的执行,那么,method就是xxx方法对应的反射对象(Method对象)
args:代理对象调用方法时,传递的实际参数
methodProxy:方法的代理对象,一般也不作处理,可以暂时忽略
总结:
基于父类的动态代理,是在内存中生成了一个对象,该对象继承了原对象(目标类对象)。所以代理类对象实际上是目标类对象的儿子。
父子关系:父子关系,代理类对象是可以用父类的引用接收的。
-
JDK动态代理有一个前提,需要代理的类必须实现接口,如果没有实现接口,只能通过CGLIB来实现,其实就是对于JDK动态代理的一个补充
-
注意:
-
类不能被 final 修饰
-
方法不能被 final 修饰
-
3.1基础准备
-
导包
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2.2</version> </dependency>
-
准备需要代理目标类
public class StudentServiceImpl implements IStudentService { public void save(Student student) { System.out.println("保存学生信息"); } public Student query(Long id) { System.out.println("查询操作"); Student student = new Student(); student.setName("sy"); student.setAge(18); return student; } }
public interface IStudentService { /** * 添加学生 */ void save(Student student); /** * 查询学生信息 * @param id * @return */ Student query(Long id); }
3.2实现方法拦截MethodInterceptor
-
实现方法拦截
public class CglibInterceptor implements MethodInterceptor { DaoTransaction transaction; public CglibInterceptor(DaoTransaction transaction){ this.transaction = transaction; } public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { //事务增强 transaction.before(); Object ret = methodProxy.invokeSuper(o, objects); //事务增强 transaction.after(); return ret; } }
3.3测试
-
测试代码
@Test public void testSave() { //生成目标代理类 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"F:\\03-Spring\\Design Mode\\proxy_pattern\\homework\\cglib-proxy-03\\src\\"); //得到方法拦截器 CglibInterceptor interceptor = new CglibInterceptor(new DaoTransaction()); //使用CGLIB框架生成目标类的子类(代理类)实现增强 Enhancer enhancer = new Enhancer(); //设置父类字节码 enhancer.setSuperclass(StudentServiceImpl.class); //设置拦截处理 enhancer.setCallback(interceptor); IStudentService service = (IStudentService) enhancer.create(); service.save(new Student()); }
3.4底层运行原理
-
运行原理图
-
文字说明:
- 通过继承的方式去获取到目标对象的方法
- 通过传递方法拦截器 MethodInterceptor 实现方法拦截,在这里面做具体的增强
- 调用生成的代理类对象具体执行重写的 save 方法,直接去调用方法拦截器里面的 intercept 方法
- 前后加上了增强操作,从而实现了不修改目标代码实现业务增强
四、总结
代理类型 | 实现机制 | 回调方式 | 使用场景 | 效率 |
---|---|---|---|---|
JDK动态代理 | 通过实现接口,通过反射机制获取到接口里面的方法,并且自定义InvocationHandler 接口,实现方法拦截 | 调用 invoke 方法实现增强 | 目标类有接口实现 | 1.8高于CGLIB |
CGLIB动态代理 | 继承机制,通过继承重写目标方法,使用MethodInterceptor 调用父类的目标方法从而实现代理 | 调用interceptor方法 | 不能使用final修饰的类和方法 | 第一次调用生成字节码比较耗时间,多次调用性能还行 |
1. 代理模式在Java开发中是广泛应用的,特别是在框架中底层原理经常涉及到代理模式(尤其是动态代理)
2. 静态代理和动态代理,实际使用时还是动态代理使用的比较多。原因就是静态代理需要自行手写代码,维护、修改非常繁琐,会额外引入很多工作量。也不能很好的使用配置完成逻辑的指定,所以使用较少。
3. 基于JDK和基于CGLIB,实际使用时两个都会用到。
1. 在spring中,默认情况下它就支持了两种动态代理方式。如果你指定的目标类实现了接口,spring就会自动选择jdk的动态代理。而如果目标类没有实现接口,则spring会使用CGLIB。
2. 我们在开发时,由于基于JDK的动态代理要求比较多,更不容易实现,所以很多人习惯于统一配置为使用CGLIB进行代理。也就是CGLIB更通用。
3. 如果使用dubbo+zookeeper,底层进行代理时,最好配置定死使用CGLIB的方式进行代理。因为dubbo会使用基于包名的扫描方式进行类的处理,而JDK的代理类生成的包名类似于com.sun.proxy....格式。我们实际需要让代理类和目标类保持一样的包名,所以只有CGLIB能保持原包名不变生成代理类。