代理模式
1. 介绍¶
代理
- 静态代理
- 动态代理
- JDK代理/接口代理
- 子代理
[GCLib代理]
,可在内存动态的创建对象,而不需要实现接口
2. 静态代理¶
2.1 概述¶
在不修改目标对象的功能前提下,对目标功能扩展。静态代理在使用的时候需要定义接口,被代理对象[目标对象]与代理对象一起实现相同的接口,然后通过调用相同的方法来调用目标对象的方法。
2.2 示例¶
ITeacherDao | |_____ TeacherDao |___________ TeacherDaoProxy Client
ITeacherDao.class
package com.cmz.proxy.staticproxy; public interface ITeacherDao { void teacher(); }
TeacherDao.class
package com.cmz.proxy.staticproxy; public class TeacherDao implements ITeacherDao { @Override public void teacher() { System.out.println("老师在讲课中"); } }
TeacherDaoProxy.class
package com.cmz.proxy.staticproxy; //静态代理 public class TeacherDaoProxy implements ITeacherDao { private ITeacherDao target;//目标对象,通过接口来聚合 // 构造器 public TeacherDaoProxy(ITeacherDao target) { this.target = target; } @Override public void teacher() { System.out.println("代理开始,完成某些操作"); target.teacher(); System.out.println("代理结束"); } }
Client.class
package com.cmz.proxy.staticproxy; public class Client { public static void main(String[] args) { // 创建目标对象[被代理对象] TeacherDao teacherDao = new TeacherDao(); //创建代理对象,同时将被代理对象传递给代理对象 TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao); //通过代理对象,调用被代理对象的方法 // 即:执行的是代理对象的方法,代理对象在去调用目标对象的方法 teacherDaoProxy.teacher(); } }
运行结果
代理开始,完成某些操作 老师在讲课中 代理结束
2.3 优缺点¶
优点: 在不修改目标对象的功能前提下,能通过代理对象目标功能扩展
缺点: 因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,一旦接口增加方法,目标对象与代理对象都要维护。
3. 动态代理¶
3.1 概述¶
- 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
- 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象
- 动态代理也叫:JDK代理、接口代理
JDK中生成代理对象的API
- 代理类所在的包: java.lang.reflect.Proxy
- JDK实现代理只需要使用newProxyInstance方法,但是改方法需要接受三个参数,完成的写法
static Object newProxyInstance(ClassLoader loader, Class<?>)
3.2 示例¶
接口 ITeacherDao.class
package com.cmz.proxy.dynamic; public interface ITeacherDao { void teach(); //授课方法 void sayHello(String name); }
接口实现 TeacherDao.class
package com.cmz.proxy.dynamic; public class TeacherDao implements ITeacherDao { @Override public void teach() { System.out.println("老师正在授课中"); } @Override public void sayHello(String name) { System.out.println(name+" 老师你好!"); } }
代理 ProxyFactory.class
package com.cmz.proxy.dynamic; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class ProxyFactory { //维护一个目标对象,object private Object target; //构造器,对target进行初始化 public ProxyFactory(Object target) { this.target = target; } //给目标对象,生成一个代理对象 public Object getProxyInstance(){ // 说明 /** * public static Object newProxyInstance(ClassLoader loader, * Class<?>[] interfaces, * InvocationHandler h) * 1. ClassLoader loader: 指定当前目标对象使用的类加载器,获取加载器的方法 【固定的】 * 2. Class<?>[] interfaces: 目标对象实现的接口类型,使用泛型方法确认类型 * 3. InvocationHandler h: 事情处理,执行目前对象的方法时候,会触发事情处理器方法,会把当前执行的目标对象方法作为参数传入。 * */ return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("JDK代理方法"); //反射机制调用目标对象的方法 Object return_invoke = method.invoke(target, args); System.out.println("JDK代理被提交"); return return_invoke; } }); } }
测试 Client
package com.cmz.proxy.dynamic; /** * @author summer * @create 2020-03-06 16:19 */ public class Client { public static void main(String[] args) { // 创建目标对象 ITeacherDao target = new TeacherDao(); // 给目标对象,创建代理对象,可以转成ITeacherDao ITeacherDao proxyInstance = (ITeacherDao)new ProxyFactory(target).getProxyInstance(); System.out.println("proxyInstance = "+ proxyInstance+"\n类型是 "+proxyInstance.getClass()); System.out.println("--------------"); // 通过代理对象调用目标对象的方法 proxyInstance.teach(); System.out.println("-----------------"); proxyInstance.sayHello("夏天"); } }
输出
JDK代理方法 JDK代理被提交 proxyInstance = com.cmz.proxy.dynamic.TeacherDao@4b67cf4d 类型是 class com.sun.proxy.$Proxy0 -------------- JDK代理方法 老师正在授课中 JDK代理被提交 ----------------- JDK代理方法 夏天 老师你好! JDK代理被提交
3.3 优缺点¶
- 动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。
- 动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成 。
- 简单来说,动态代理就是交给程序去自动生成代理类。
JDK动态代理实现步骤
- 创建被代理的类以及实现的接口;
- 创建一个实现接口InvocationHandler的类,它必须实现invoke方法;
- 调用Proxy的newProxyInstance静态方法,创建一个代理类。
- 通过代理对象调用目标方法。
4. CGLib 代理¶
4.1 概述¶
- 静态代理和JDK代理模式都是要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候使用目标对象子类来实现代理,这个就是CGLib代理
- CGLib代理也叫子类代理,它是内存中构建一个子类对象从而实现对目标对象功能扩展
- CGLib是一个强大的高性能的代理生成包,它可以运行期扩展java类与实现Java接口,它广泛的被许多AOP的框架使用,如Spring AOP,实现方法拦截。
- 在AOP编程中如何选择代理模式
- 目标对象需要实现接口,用JDK代理
- 目标对象不需要时间接口,用CGLib代理 5.CGLib包的底层是通过使用字节码处理ASM来转换字节码生成新的类
注意
- 在内存中动态构建子类,注意代理的类不能为final
- 目标对象的方法如果是final/static,那么就不会拦截,即不会执行目标对象额外的业务方法
4.2 示例¶
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.cmz</groupId> <artifactId>DesignPatterns</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!-- https://mvnrepository.com/artifact/org.ow2.asm/asm --> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm</artifactId> <version>7.3.1</version> </dependency> <!-- https://mvnrepository.com/artifact/cglib/cglib --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency> </dependencies> </project>
TeacherDao.class
package com.cmz.cglib; public class TeacherDao { public void teach() { System.out.println("老师正在授课中,我是cgblib代理。不需要实现接口"); } }
ProxyFactory.class
package com.cmz.cglib; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class ProxyFactory implements MethodInterceptor { // 维护一个目标对象 private Object target; //构造器,传入一个呗代理的对象 public ProxyFactory(Object target) { this.target = target; } // 返回一个代理对象 是一个target对象的代理对象 public Object getProxyInstance(){ //1. 创建一个工具类 Enhancer enhancer = new Enhancer(); //2. 设置父类 enhancer.setSuperclass(target.getClass()); //3. 设置回调函数 enhancer.setCallback(this); //4. 创建子类对象,即代理对象 return enhancer.create(); } // 调用目标对象的方法 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("cglib代理开始"); Object returnVal = method.invoke(target, objects); System.out.println("cglib代理提交"); return returnVal; } }
Client.class
package com.cmz.cglib; public class Client { public static void main(String[] args) { // 创建目标对象 TeacherDao target = new TeacherDao(); // 获取到代理对象,并且将目标对象传递给代理对象 TeacherDao proxyInstance = (TeacherDao)new ProxyFactory(target).getProxyInstance(); //执行代理对象的方法,除非intecept方法,从而实现对目标对象的调用 proxyInstance.teach(); } }