Java内功修炼系列一代理模式
代理模式是JAVA设计模式之一,网上设计模式相关的博文铺天盖地,参考它们有助于自己理解,但是所谓“尽信书不如无书”,在参考的同时也要思考其正确性,写博客也是为了记录自己理解知识点的思路历程和心路历程,所以在经过调研、实践之后能把自己所掌握和理解的东西写下来,无论对错,错了只能说明当下的理解不够完整,更重要的是能够在以后遇到相同的问题时回头看看,反思当初这么写到底有没有问题,毕竟是自己写的,错了还可以进行纠正,想必每个写博客记录自己学习过程的人,都不会刻意去写误导别人的东西,有点扯远了,其实想说的就是不为写而写,更不为凑数而写,要真实地去记录思考的过程,以后的博客也要遵循此原则。
说正事了。代理模式是一种模式,所谓的代理在我们日常生活中也是很常见的,比如我们依靠中介公司租房的时候,不会直接跟房东签署协议,而是通过中介间接地跟房东租房,在租房的过程中房东对我们是不可见的,跟我们直接交涉的人是中介,中介相当于代理,房东把房子的出租权限给中介,让中介代理他出租自己的房子,这就是代理模式。在java中代理模式分为静态代理和动态代理两种,动态代理又有JDK和CGLIB两种实现方式,下面依次介绍。
一、静态代理
静态代理的实现要求代理对象和真实对象实现同一个接口,然后在代理类中维护一个指向接口的真实对象,调用真实对象的实现逻辑即可,这样当用户向代理对象发送请求的时候,从用户角度来看处理请求逻辑的是代理对象,屏蔽了真实对象,但是只有代理对象知道,真正做事情的是真实对象。所以可以分三步实现静态代理:
1⃣️创建一个接口;
2⃣️创建代理类和真实类,均实现接口,这是类实现的接口中完成用户请求,代理对象维护一个指向接口的真实对象,并调用其实现;
3⃣️用户直接向代理对象发请求;
下面以租房为例,用代码来实现。
首先,创建租房接口:
1 /* 2 * 定义一个出租房屋接口 3 */ 4 public interface RentalHouseInterface { 5 public void rentalHouse(); 6 }
然后创建代理类和真实类实现接口,真实类实现请求逻辑并在代理类中维护指向接口的真实类:
1 /* 2 * 创建一个房东类,实现出租房屋接口,房屋的地址和接口都是由房东规定,所以房东才是真实对象 3 */ 4 public class Landlord implements RentalHouseInterface{ 5 private String address;//房屋所处的位置 6 private int price;//出租房屋的价格 7 8 public Landlord(String address,int price) { 9 this.address = address; 10 this.price = price; 11 } 12 /** 13 * 房东给出🏠位置和🏠月租 14 */ 15 public void rentalHouse() { 16 System.out.println("出租"+address+"主卧一间,月租"+price+"元"); 17 } 18 19 }
1 /* 2 * 创建一个中介公司类,实现出租房屋接口,并维护一个指向接口的真实对象 3 */ 4 public class AgencyCompany implements RentalHouseInterface{ 5 RentalHouseInterface landlord = new Landlord("海淀区", 2000); 6 //中介按照房东给出的位置和价格来出租房屋 7 public void rentalHouse() { 8 landlord.rentalHouse(); 9 } 10 }
最后,由客户向代理类发送租房的请求:
1 /* 2 * 租房的用户 3 */ 4 public class Customer { 5 public static void main(String[] args) { 6 //用户找中介 7 RentalHouseInterface agency = new AgencyCompany(); 8 //中介出租房屋给用户 9 agency.rentalHouse(); 10 } 11 }
运行结果如下:
出租海淀区主卧一间,月租2000元
上面是比较简单的例子,即一个用户只有一个需求,且这个需求只需要一个代理对象和一个真实对象就能完成,如果需求变成一个用户有多个请求,而且需要多个真实对象完成,比如张三要租5套房子,每个房子的房东都是不同的人,这个时候怎么代理呢?有两种方案:
第一种方案:每个房东都有一个不同的中介代理他出租房屋。这种方案需要为每个真实类创建一个单独的代理类,并且他们各自维护自己的真实对象,而用户需要找不同的代理完成自己的需求,如果租的房子越多,用户要委托的代理就越多,这个方案看上去要创建多个逻辑相同而引用对象不同的代理类,会产生太多重复代码,似乎不是个好的方案;
第二种方案:用户只委托同一个中介,该中介可以为多个不同的房东做代理出租房屋。该方案要求同一个代理类维护多个不同的真实对象,用户要求越多,维护的真实对象也就越多,这样就会让代理类看起来很臃肿,也不是个好的方案。
其实上面两个方案的缺点就是静态代理的缺点,也是动态代理需要解决的问题,所以理解静态代理的缺点有助于理解动态代理。
二、动态代理
动态代理可以理解为动态地生成代理,其实就是在静态代理中介绍的第一种方案,只不过把该方案中的代理对象改成由jdk自动生成,而不是手动创建。因为有多个不同的真实类对应各自的代理类,所以真实对象和代理对象之间的关系可以理解为:通过真实对象用反射技术生成代理对象并调用真实对象中对应的接口。动态代理的实现技术有两种,一种是通JDK自带的API实现,简称JDK动态代理或接口代理,另一种是通过第三方提供的CGLIB库实现,简称CGLIB动态代理,不管哪种动态代理方式,其技术原理都是相同的。下面分别用这两种技术实现一个简单的动态代理:
1、JDK动态代理
动态代理中的代理对象需要根据它所代理的真实对象动态生成,所以代理对象和真实对象之间必须要有某种关系,才能反射出代理对象并让它进行代理。这种关系就是接口,也是jdk动态代理中必须的,这个接口由真实类实现,然后动态生成一个指向接口引用的代理类,这样代理对象就能调用真实对象的方法进行代理操作了。除此之外还有一点就是,动态生成代理类时需要创建一个中介类实现InvocationHandler接口,这个接口中的invoke方法中进行代理逻辑处理,所以jdk动态代理的实现可以分为以下三四步:
1⃣️定义接口
1 /** 2 * @author hyc 3 * 定义接口:租房,需知道租房位置和价格 4 */ 5 public interface DynamicRentalHouseInterface { 6 public void rentalHouse(String address,int price); 7 }
2⃣️定义真实类并实现接口中的方法
1 /* 2 * 定义真实类并实现接口:房东,需实现接口中的方法,决定出租位置和价格 3 */ 4 public class DynamicLandlord implements DynamicRentalHouseInterface { 5 private String name; 6 7 public DynamicLandlord(String name) { 8 this.name = name; 9 } 10 11 //由房东规定位置和价格,出租给客户 12 public void rentalHouse(String address, int price) { 13 System.out.println(name + "出租" + address + "房屋一间,月租" + price + "元"); 14 } 15 }
3⃣️创建中介类动态生成代理对象,即关联代理对象和真实对象
1 /* 2 * 中介:通过反射方法动态调用真实对象的方法,即中介根据房东给出的信息向用户提供出租信息 3 */ 4 public class DynamicAgency implements InvocationHandler{ 5 private DynamicRentalHouseInterface target = null; 6 // 中介需和房东合作 7 public DynamicAgency(DynamicRentalHouseInterface lanlord) { 8 this.target = lanlord; 9 } 10 11 //建立中介和房东直接的关系:动态生成指向真实类实现接口的代理对象 12 public DynamicRentalHouseInterface bind() { 13 //获取类加载器 14 ClassLoader cl = target.getClass().getClassLoader(); 15 //动态代理对象 16 DynamicRentalHouseInterface proxy = (DynamicRentalHouseInterface) Proxy.newProxyInstance(cl, target.getClass().getInterfaces(),this); 17 return proxy; 18 } 19 //实现接口中的方法,将要代理的方法下挂给真实对象 20 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 21 System.out.println("出租之前中介向房东确认信息"); 22 Object obj = method.invoke(target,args); 23 System.out.println("出租之后中介向房东说明信息"); 24 return obj; 25 } 26 }
4⃣️由代理对象代理真实对象处理业务
1 /* 2 * 由代理对象代理真实对象处理业务:客户只将需求说与中介,无需和房东直接沟通 3 */ 4 public class DynamicCustomer { 5 public static void main(String[] args) { 6 //创建一个真实对象 7 DynamicRentalHouseInterface landlord = new DynamicLandlord("郑女士"); 8 //获取动态生成的代理对象 9 DynamicRentalHouseInterface proxy = new DynamicAgency(landlord).bind(); 10 //由代理对象代理处理用户请求 11 proxy.rentalHouse("海淀区", 1500); 12 } 13 }
执行结果如下:
1 出租之前中介向房东确认信息 2 郑女士出租海淀区房屋一间,月租1500元 3 出租之后中介向房东说明信息
从结果可见,用户的需求是通过真实对象动态产生的代理实现的,就相当于房东根据用户的请求动态为之分配中介以达到出租房屋的目的。至于动态代理的生成过程和代理方法的实现细节,可以去看源代码进行分析。
2、CGLIB动态代理
在jdk动态代理模式中,必须要创建一个接口让真实类去实现,但是有时候不允许创建接口,怎么办呢?答案是采用cglib动态代理,这种方式不需要实现接口,只要一个非抽象的真实类就可以了。然后还是需要一个中介类用来动态生成代理对象,不同于jdk的中介类,该类需要实现的是第三方库提供的MethodInterceptor接口,因为是第三方库的实现方式,所以使用该方法之前要先导入相关的库。
我导入的库有(spring方式)
1 <dependency> 2 <groupId>cglib</groupId> 3 <artifactId>cglib</artifactId> 4 <version>2.2.2</version> 5 </dependency> 6 7 <dependency> 8 <groupId>cglib</groupId> 9 <artifactId>cglib-nodep</artifactId> 10 <version>3.2.7</version> 11 </dependency> 12 13 <dependency> 14 <groupId>aopalliance</groupId> 15 <artifactId>aopalliance</artifactId> 16 <version>1.0</version> 17 </dependency> 18 <dependency> 19 <groupId>org.springframework</groupId> 20 <artifactId>spring-core</artifactId> 21 <version>5.0.8.RELEASE</version> 22 </dependency> 23 24 <dependency> 25 <groupId>org.springframework</groupId> 26 <artifactId>spring</artifactId> 27 <version>2.5.6.SEC03</version> 28 </dependency> 29 30 <dependency> 31 <groupId>org.springframework</groupId> 32 <artifactId>spring-aop</artifactId> 33 <version>5.0.8.RELEASE</version> 34 </dependency>
实现步骤如下:
1⃣️创建真实类,完成业务逻辑处理
1 /* 2 * cglib动态代理:真实类,完成业务逻辑处理 3 */ 4 public class CglibLandlord { 5 private String name; 6 7 public CglibLandlord(String name) { 8 this.name = name; 9 } 10 11 public void rentalHouse(String address, int price) { 12 System.out.println(name + "出租" + address + "房屋一间,月租" + price + "元"); 13 } 14 }
2⃣️创建中介类,实现MethodInterceptor接口,并生成动态代理对象
1 import org.springframework.cglib.proxy.Enhancer; 2 import org.springframework.cglib.proxy.MethodInterceptor; 3 import org.springframework.cglib.proxy.MethodProxy; 4 5 /* 6 * 中介类:动态生成代理对象并代理方法 7 */ 8 public class CglibAgency implements MethodInterceptor { 9 10 /** 11 * 代理逻辑方法 12 * 13 * @param proxy 代理对象 14 * @param method 方法 15 * @param args 方法参数 16 * @param methodProxy 方法代理 17 */ 18 public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { 19 System.err.println("调用真实对象之前"); 20 // CGLIB 反射调用真实对象方法 21 Object result = methodProxy.invokeSuper(proxy, args); 22 System.err.println("调用真实对象之后"); 23 return result; 24 } 25 26 /** 27 * 生成代理逻辑对象 28 * 29 * @param cls 30 * @return CglibAgency.java 31 */ 32 public Object getProxy(Class cls) { 33 // CGLIB Enhancer 增强类对象 34 Enhancer enhancer = new Enhancer(); 35 // 设置增强类型的超类,即真实类 36 enhancer.setSuperclass(cls); 37 // 定义代理逻辑对象为当前对象,要求当前对象实现MethodInterceptor方法 38 enhancer.setCallback(this); 39 Class<?>[] paramClass = {String.class}; 40 Object[] paramValue = {"张女士"}; 41 //如果真实对象中存在含参构造方法,需要传入参数类型数组和对应值的数组 42 return enhancer.create(paramClass, paramValue); 43 } 44 45 }
3⃣️用户向代理对象发送请求
1 public class CglibCustomer { 2 public static void main(String[] args) { 3 CglibAgency cglib = new CglibAgency(); 4 //动态生成代理对象 5 CglibLandlord landlordAgency = (CglibLandlord) cglib.getProxy(CglibLandlord.class); 6 //由代理对象代理完成用户请求 7 landlordAgency.rentalHouse("朝阳区", 3000); 8 } 9 }
上面代码的第5行中可以看出,生成的代理对象是一个指向真实对象的引用,而在生成动态代理对象的中介类中,我们看到有个设置增强类的过程,所以我觉得cglib实现动态代理的过程相当于继承,根据父类动态产生一个继承它的子类,这个子类就是代理类。
测试结果:
1 调用真实对象之前 2 张女士出租朝阳区房屋一间,月租3000元 3 调用真实对象之后
以上就是java代理模式的所有内容,看到这篇文章的你,如果发现有错误或描述不当的地方,希望指出来。
本文来自博客园,作者:bug改了我,转载请注明原文链接:https://www.cnblogs.com/hellowhy/p/9607433.html