1 代理模式
-
代理模式提供了对目标对象额外的访问方式,即通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。简言之,代理模式就是设置一个中间代理来控制访问原目标对象,以达到增强原对象的功能和简化访问方式。但是对于真正的调用来说, 实际上并不关心这个代理对象, 只要能够实现相应的业务逻辑就好。
-
举个例子,我们生活中经常到火车站去买车票,但是人一多的话,就会非常拥挤,于是就有了代售点,我们能从代售点买车票了,这就是代理模式的体现,代售点代理了火车站售票对象,提供购买车票的方法。
1.1 代理模式
1.1.1 定义
(1)UML类图
-
Subject --- 抽象主题类,定义了代理对象和真实对象的共同接口方法,可以理解为定义了某一种业务需求的实现规范。既可以是接口也可以是抽象类,其中声明了需要被实现的方法。
-
RealSubject --- 真实主题类,该类可以被称为被委托类或被代理类,该类定义了代理对象所表示的真实对象,实现了Subject接口方法。
-
Proxy --- 代理类,该类也被称为委托类或代理类,该类中持有一个真实主题类的引用,同样实现了Subject接口。在其实现的接口方法中调用代理类中相应的接口方法,在此基础上附加其他动作。Client端通过代理类间接调用的真实主题类中的方法,由其执行真正的业务逻辑。
-
Client --- 客户端。
要求:代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。
例如:电影公司委托电影院播放电影,电影院想要在播放电影的时候,加一些广告或服务项目赚取收
益。(使用代码来模拟)
(1)静态代理服务接口: MovieService
-
通用的接口是代理模式实现的基础。 MovieService接口代表电影播放服务
(2)静态代理服务实现类: MovieServiceImpl
-
MovieService 接口的实现类,可以当作电影公司,作为接口实现类,要求必须定义接口中声明的所有方法。
(3)服务的代理类: MovieProxy
-
MovieService 服务的代理类,就是电影服务的代理对象,当作电影院,代理了电影公司提供的电影播放服务。方法实现时,主要调用服务实现类提供的方法,并在其执行前后添加附加功能。
1.1.2相关案例
案例1:静态代理-实操
静态代理服务接口MovieService
package com.StaticProxy; /** * 定义一个通用的接口 MovieService,代表电影播放服务 **/ public interface MovieService { void play(); }
静态代理服务实现类 MovieServiceImpl,
package com.StaticProxy; /** * 静态代理服务实现类 MovieServiceImpl, **/ public class MovieServiceImpl implements MovieService{ /** * 定义MovieService接口的类 */ // @Override public void play() { System.out.println("Showing a movie:Tom and Jerry...."); } }
服务的代理类 MovieProxy
package com.StaticProxy; /** * 服务的代理类 MovieProxy **/ public class MovieProxy implements MovieService{ MovieServiceImpl movieService; /*public static void main(String[] args) { test1(); }*/ /* public static void test1(){ movieService.play(); }*/ public MovieProxy(MovieServiceImpl movieService) { super(); this.movieService = movieService; } /** * 重写 play() 方法,调用MovieServiceImpl的play() 方法前后,附加广告提示 */ // @Override public void play() { advertise(true); movieService.play(); advertise(false); } /** * 宣传方法 * @param isStart */ public void advertise(boolean isStart) { if(isStart == true){ System.out.println("The movie starts."); }else{ System.out.println("The movie is over."); } } }
测试代码
package com.StaticProxy; /** * 测试代码 **/ public class Demo { public static void main(String[] args) { new MovieProxy(new MovieServiceImpl()).play(); } }
输出结果
案例2
package com.StaticProxy1; /** * 静态代理 设计模式 * 1、真实角色 * 2、代理角色: 持有真实角色的引用 * 3、二者 实现相同的接口 * * @author Administrator * */ public class Demo01 { /** * @param args */ public static void main(String[] args) { //创建真实角色 Marry you =new You(); //创建代理角色 +真实角色的引用 WeddingCompany company =new WeddingCompany(you); //执行任务 company.marry(); } } //接口 interface Marry{ public abstract void marry(); } //真实角色 class You implements Marry{ public void marry() { System.out.println("you and 嫦娥结婚了...."); } } //代理角色 class WeddingCompany implements Marry{ private Marry you; public WeddingCompany() { } public WeddingCompany(Marry you) { this.you = you; } private void before(){ System.out.println("布置猪窝...."); } private void after(){ System.out.println("闹玉兔...."); } public void marry() { before(); you.marry(); after(); } }
输出结果
1.1.3 静态代理缺陷
-
代理复杂,难于管理
-
当需要代理的目标类数量较多时,需要为每个目标类手动编写一个代理类,这会导致代码量增加,且难以统一管理和维护。
-
-
代理类依赖目标类
-
静态代理的代理类与目标类紧密耦合,当目标类发生变化时(如增加、删除或修改方法),代理类也需要进行相应的修改,这增加了维护的复杂性。
-
-
代理类过多
-
对于每个目标类,都需要一个与之对应的代理类。当目标类数量较多时,会导致代理类数量过多,增加了系统的复杂度。
-
-
不灵活
-
静态代理在编译时期就确定了代理对象和目标对象,无法动态地改变代理对象。如果需要改变代理对象,需要重新编译和部署代码。
-
-
代码冗余
-
由于每个目标对象都需要对应一个代理类,这会导致代码的冗余。特别是当有多个目标对象时,会增加代码量和维护成本。
-
-
维护困难
-
当目标对象发生变化时,代理类也需要同步更新,维护起来相对困难。特别是在有大量代理类时,更新代理类会带来一定的工作量。
-
-
性能考虑
-
虽然静态代理在编译时期就确定了代理对象和目标对象,因此性能相对较高,但相对于直接调用目标对象的方法,静态代理仍然增加了一层调用开销。
-
静态代理 vs 动态代理:
-
静态代理需要手动编写代码让代理类实现某接口。而动态代理则可以让程序在运行时自动在内存中创建一个实现某接口的代理,而不需要去手动定义代理类。
-
静态代理在程序运行前,代理类的class文件(类字节码)就已经存在了。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
1.2 动态代理
-
与静态代理相比,多了InvocationHandler角色和一个Proxy角色,InvocationHandler是java提供的一个接口,我们需要定义一个类实现InvocationHandler接口,这里就叫DynamicProxy角色;Proxy是java提供用于动态生成ProxySubject的一个类,它需要ProxySubject继承。
-
我们看到DynamicProxy在ProxySubject和RealSubject之前起到了中间人的角色,ProxySubject会把事情委托给DynamicProxy来做,而DynamicProxy最终把事情委托给RealSubject来做
动态代理的实现方式有很多种,比如 JDK 动态代理、CGLIB 动态代理等等。
1.2.1 场景实现-JDK动态代理
JDK提供了java.lang.reflect.InvocationHandler接口和java.lang.reflect.Proxy类。
java.lang.reflect.InvocationHandler 接口中仅声明了一个方法invoke(),第一个参数 proxy一般是指代理类,method是被代理的方法,args为方法中声明的形参。
public interface InvocationHandler{ public Object invode{ Object proxy,Method method, Object[] args } }
JDK 动态代理常用的API:
-
java.lang.reflect.Proxy 动态代理类中提供的 getProxyClass() 静态方法可以用来获取一个代理Class对象,其接受的参数为类加载器和目标类实现的接口。
-
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)Proxy类提供的另一个静态方法 newProxyInstance() 可以直接获取代理实例对象,连创建代理类对象的过程都封装起来了。
-
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandlerh)loader是类加载器,interfaces 是代理要实现的服务接口,h是一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用。
关注 Proxy.newProxyInstance() 方法
(1)判断传入的InvocationHandler实例对象是否为null,并对传入的接口进行克隆权限校验。获取系统安全接口(安全管理器),如果不为空,检查创建代理类所需的权限
(2)查找或生成指定的代理类对象,要求调用此函数之前必须调用checkProxyAccess方法执行权限检查。首先判断接口数量,过多则抛出异常。随后调用 proxyClassCache.get(loader, interfaces) 方法,参数接收类加载器和接口数组,从 proxyClassCache 缓存中获取代理类,如果找不到,则通过ProxyClassFactory创建代理类。
方法二:添加系统参数,将JDK动态代理生成的class文件保存到本地,默认保存路径为
com.sun.proxy
System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
随后反编译类文件即可。
JDK动态代理 – 源码浅析:
-
继承了Proxy类,实现了代理的接口,由于java不能多继承,这里已经继承了Proxy类了,不能再继承其他的类,所以 JDK的动态代理不支持对实现类的代理,只支持接口的代理。
-
提供了一个使用InvocationHandler作为参数的构造方法。
-
重写了Object类的equals、hashCode、toString,它们都只是简单的调用了InvocationHandler的invoke方法。
-
生成静态代码块来初始化接口中方法的Method对象,以及Object类的equals、hashCode、toString方法。代理类实现代理接口的play()方法中,只是简单的调用了InvocationHandler的invoke方法,我们可以在invoke方法中进行一些特殊操作,甚至不调用实现的方法,直接返回。
1.2.2 场景实现-CGLib
CGLib(Code Generation Library)是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象,从而实现对目标对象功能的扩展。
CGLIB作为一个开源项目,其代码托管在github,地址为:GitHub - cglib/cglib: cglib - Byte Code Generation Library is high level API to generate and transform Java byte code. It is used by AOP, testing, data access frameworks to generate dynamic proxy objects and intercept field access.
CGLib vs JDK动态代理:
-
与动态代理不同的是,动态代理是基于Java 反射机制实现的,必须实现接口的业务类才能使用这种办法生成代理对象。而 CGLib 则是基于 ASM机制实现,通过生成业务类的子类作为代理类,它允许我们在运行时对字节码进行修改和动态生成。
-
与动态代理相比,JDK动态代理限制了只能基于接口设计,对于没有接口的情况,JDK方式无法解决,而 CGLib则可以解决这一问题,可以通过继承方式实现代理。
CGLib – 常用API:
-
net.sf.cglib.proxy.Enhancer 类,(字节码)增强器,可以类比于JDK中的Proxy类,与之不同的是,Enhancer既能够代理普通的java类,也能够代理接口。 Enhancer.setSuperclass() 用来设置被代理的类。 Enhancer.create()方法是用来创建增强对象。
-
Callback,即回调,它是一个标识接口(空接口,没有任何方法),它的回调时机是生成的代理类的方法被调用的时候,即生成的代理类的方法被调用的时候,Callback的实现逻辑就会被调用。Enhancer通过 setCallback() 和setCallbacks() 设置Callback,设置了多个Callback实例将会按照设置的顺序进行回调。
-
net.sf.cglib.proxy.MethodInterceptor 接口,即方法拦截器,设置了MethodInterceptor后,代理类的所有方法调用都会转而执行这个接口中的intercept方法而不是原方法。如果需要在intercept方法中执行原方法可以使用参数method基于代理实例obj进行反射调用。
例如1:
(1) 创建服务实现类:MovieServiceImpl即需要被代理的类,不需要实现顶层接口。
package com.CGLibTest; /** * 需要被代理的服务类 MovieServiceImpl,不需要实现顶层接口 **/ public class MovieServiceImpl{ /** * 定义服务方法 */ public void startMovie() { System.out.println("Start the movie...."); } public void endMovie() { System.out.println("The movie is over"); } public final void playFinal() { System.out.println("this is final!"); } }
(2) 创建拦截器:MyInterceptor,当我们调用被代理类中的某个方法时,实际首先调用的是拦截器的intercept方法,如果需要执行原来的方法,则调用 method.invoke(s, args);
package com.CGLibTest; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * 拦截器 **/ public class MyInterceptor implements MethodInterceptor { private MovieServiceImpl service; public MyInterceptor(MovieServiceImpl service){ this.service = service; } public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("=====================interceptor====================="); return method.invoke(service, args); } }
(3)测试类
package com.CGLibTest; import com.CGLibTest.fianlTest.FinalInterceptor; import com.CGLibTest.fianlTest.FinalServiceImpl; import net.sf.cglib.proxy.Enhancer; /** * 测试方法 **/ public class Demo { public static void test1(){ // 通过CGLIB动态代理获取代理对象 Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(MovieServiceImpl.class); enhancer.setCallback(new MyInterceptor(new MovieServiceImpl())); MovieServiceImpl helper = (MovieServiceImpl) enhancer.create(); helper.startMovie(); helper.endMovie(); System.out.println(); helper.playFinal(); } public static void main(String[] args) { test1(); } }
每次在执行原来的方法之前,都会先执行拦截器中的扩展代码。被final修饰的方法没有被拦截,没有
执行扩展代码,仅仅执行了原来的方法。
输出结果:、
例如2:
(1)用final修饰需要被代理的服务类
package com.CGLibTest.fianlTest; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * 拦截器 **/ public class FinalInterceptor implements MethodInterceptor { private FinalServiceImpl service; public FinalInterceptor(FinalServiceImpl service){ this.service = service; } public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("=====================interceptor====================="); return method.invoke(service, args); } }
package com.CGLibTest.fianlTest; /** * 需要被代理的服务类 MovieServiceImpl,不需要实现顶层接口 **/ public final class FinalServiceImpl { /** * 定义服务方法 */ public void startMovie() { System.out.println("Start the movie...."); } public void endMovie() { System.out.println("The movie is over"); } public final void playFinal() { System.out.println("this is final!"); } }
package com.CGLibTest; import com.CGLibTest.fianlTest.FinalInterceptor; import com.CGLibTest.fianlTest.FinalServiceImpl; import net.sf.cglib.proxy.Enhancer; /** * 测试方法 **/ public class Demo { /* * 测试代理Final修饰的类 */ public static void test2(){ // 通过CGLIB动态代理获取代理对象 Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(FinalServiceImpl.class); enhancer.setCallback(new FinalInterceptor(new FinalServiceImpl())); FinalServiceImpl helper = (FinalServiceImpl) enhancer.create(); helper.playFinal(); } public static void main(String[] args) { test2(); } }
测试结果:
CGLIB缺点:由于CGLib是基于继承的方式实现类的动态代理,对于final方法和final类无法进行代理。
1.2.3 其他相关案例
例如:电影公司委托电影院播放电影,电影院想要在播放电影的时候,加一些广告或服务项目赚取收益。(使用代码来模拟)
服务接口: MovieService
服务实现类: MovieServiceImpl
动态代理类: MovieHandler
每个代理的实例都有一个与之关联的 InvocationHandler 接口实现类,这里实现了InvocationHandler接口,要求必须定义其声明的invoke()方法,用来反射调用执行方法。动态代理类构造器传入要代理的服务类,并使用成员变量接收,在invoke()方法中引用被代理的服务类,并调用其方法。
package com.DynamicProxy; /** * 定义一个通用的接口 MovieService,代表电影播放服务 **/ public interface MovieService { void play(); }
package com.DynamicProxy; /** * 动态代理服务实现类 MovieServiceImpl **/ public class MovieServiceImpl implements MovieService { /** * 定义MovieService接口的类 */ // @Override public void play() { System.out.println("Showing a movie:Tom and Jerry...."); } }
package com.DynamicProxy; /** * 动态代理服务实现类 MovieServiceImpl **/ public class MovieServiceImpl2 implements MovieService { /** * 定义MovieService接口的类 */ // @Override public void play() { System.out.println("Showing a movie:doraemon...."); } }
package com.DynamicProxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * 动态代理类 MovieHandler **/ public class MovieHandler implements InvocationHandler { private Object movieService; public MovieHandler(Object movieService) { this.movieService = movieService; //成员变量接收目标对象的引用 } /** * 宣传方法 * @param isStart */ public void advertise(boolean isStart) { if(isStart == true){ System.out.println("The movie starts."); }else{ System.out.println("The movie is over."); } } // @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { advertise(true); method.invoke(movieService,args); advertise(false); return null; } }
package com.DynamicProxy; import sun.misc.ProxyGenerator; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.List; /** * 测试代码 **/ public class Demo { /** * 执行单个服务 */ public static void test1(){ MovieService movieService = new MovieServiceImpl(); InvocationHandler handler = new MovieHandler(movieService); MovieService dynamicProxy = (MovieService) Proxy.newProxyInstance(MovieService.class.getClassLoader(), MovieServiceImpl.class.getInterfaces(),handler); dynamicProxy.play(); } /** * 执行多个服务 */ public static void test2(){ List<MovieService> movieServices = new ArrayList<MovieService>(); MovieService movieService = new MovieServiceImpl(); MovieService movieService1 = new MovieServiceImpl2(); movieServices.add(movieService); movieServices.add(movieService1); for (int i = 0; i < movieServices.size(); i++) { InvocationHandler handler = new MovieHandler(movieServices.get(i)); MovieService dynamicProxy = (MovieService) Proxy.newProxyInstance(MovieService.class.getClassLoader(), MovieServiceImpl.class.getInterfaces(),handler); dynamicProxy.play(); } /* MovieService movieService1 = new MovieServiceImpl(); MovieService movieService2 = new MovieServiceImpl2(); InvocationHandler handler1 = new MovieHandler(movieService1); InvocationHandler handler2 = new MovieHandler(movieService2); MovieService dynamicProxy1 = (MovieService) Proxy.newProxyInstance(MovieService.class.getClassLoader(), MovieServiceImpl.class.getInterfaces(),handler1); dynamicProxy1.play(); MovieService dynamicProxy2 = (MovieService) Proxy.newProxyInstance(MovieService.class.getClassLoader(), MovieServiceImpl2.class.getInterfaces(),handler2); dynamicProxy2.play();*/ } /** * 代理类写入本地 */ public static void test3() throws Exception { // 这里我们将jdk生成的代理类写入本地文件 FileOutputStream out = null; try { byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy", new Class[]{InvocationHandler.class}); out = new FileOutputStream("E:\\java_project\\java_code_4\\code\\DynamicProxy\\target\\classes\\com\\Proxy3.class");//自行修改路径 out.write(classFile); } catch (Exception e) { e.printStackTrace(); } finally { try { if (out != null) { out.flush(); out.close(); } } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) throws Exception { System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); test3(); // test2(); } }