设计模式之代理模式
1、什么是代理模式
代理模式的定义:对其他对象提供一种代理以控制这个对象的访问。
代理的作用:代理模式的主要作用是为其它对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
代理模式的思想是为了提供额外的处理或者不同的操作而在实际对象与调用者之间插入一个代理对象。这些额外的操作通常需要与实际对象进行通信
代理模式是一种设计模式,简单说即是在不改变源码的情况下,实现对目标对象的功能扩展
抽象角色(Subject):通过接口或抽象类声明真实角色实现的业务方法。角色具有的行为放入该接口或抽象类中
代理角色(Proxy):实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
真实角色即目标角色(RealSubject):实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
2、代理模式的优点
代理模式可以屏蔽用户真正请求对象,是用户程序和正在的对象解耦,使用代理来担当那些创建耗时的对象替身。一个用户不想或者不能够直接引用一个对象(或者设计者不希望用户直接访问该画图对象),而代理对象可以在客户端和目标对象之间起到中介的作用。而且这个代理对象可以做更多的操作。
3、代理分类
3.1静态代理
静态代理就是写死了在代理对象中执行这个方法前后执行添加功能的形式,每次要在接口中添加一个新方法,则需要在目标对象中实现这个方法,并且在代理对象中实现相应的代理方法
public interface RentHouse { void rentHouse(); } /** *目标对象 */ public class Tenantry implements RentHouse{ @Override public void rentHouse() { System.out.println("租户要租房"); } } /** *代理对象 */ public class MiddleMan implements RentHouse{ //接收目标对象 private Tenantry tenantry; public MiddleMan(Tenantry tenantry) { this.tenantry = tenantry; } @Override //对目标对象的方法进行功能扩展 public void rentOutHouse() { System.out.println("查看房源"); tenantry.rentHouse(); System.out.println("收取中介费"); } } //测试类 public class Test { public static void main(String[] args) { //目标对象 Tenantry tenantry = new Tenantry(); //代理对象 MiddleMan middleMan = new MiddleMan(tenantry); middleMan.rentOutHouse(); }
总结:这里就是创建一个代理类继承了接口并实现了其中的方法,只不过这种实现特意包含了目标对象的方法,正式这种特征使得看起来像试扩展了目标对象的方法。假使代理对象中只是简单地对其方法做了另一种实现而没有包含目标对象的方法,也就不能算作代理模式了。所有这里的包含是关键。
缺点:这种实现方式很直观也很简单,但是缺点是代理对象必须提前写出,如果接口层发生了变化,代理对象的代码也要进行维护。
3.2JDK动态代理
动态代理是在程序运行时通过反射机制动态创建的
* 运行一段时间后,项目经理提出了一个新的需求: 要统计所有业务接口中每一个业务方法的耗时
* 解决方案一: 硬编码,在每一个业务接口中的每一个业务方法中直接添加统计耗时的程序
* 缺点一:违背了OCP开闭原则
* 缺点二:代码没有得到复用【相同的代码写了很多遍】
*
* 解决方案二:编写业务类的子类,让子类继承业务类,对每个业务方法进行重写
* 缺点一: 虽然解决了OCP开闭原则,但是这种方式会导致耦合度很高,因为采用了继承关系,继承关系是一种耦合度非常高的关系,不建议使用
* 缺点二: 代码没有得到复用【相同的代码写了很多遍】
*
*解决方案三:静态代理模式
* 优点一: 解决了OCP问题
* 优点二: 采用了代理模式的has a,降低了耦合度
*
* 缺点:类爆炸,假设系统中有1000个接口,那么每个接口都需要对应代理类,这样类会急剧膨胀,不好维护
* 怎么解决类爆炸问题?
* 可以使用动态代理来解决这个问题
* 动态代理还是代理模式,只不过添加了字节码生成技术,可以在内存中为我们动态生成一个class字节码,这个字节码就是代理类
* 在内存中动态的生成字节码代理的类的技术叫做动态代理
/** * 订单业务接口 */ public interface OrderService {//代理对象和目标对象的公共接口 String getName(); //生成订单 void generate(); //修改订单 void modify(); //查看订单详情 void detail(); } public class OrderServiceImpl implements OrderService{//目标对象 @Override public String getName() { System.out.println("getName()方法执行了"); return "张三"; } @Override public void generate() {//目标方法 //模拟生成订单的耗时 try { Thread.sleep(1234); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("订单已生成"); } @Override public void modify() {//目标方法 //模拟修改订单的耗时 try { Thread.sleep(124); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("订单已修改"); } @Override public void detail() {//目标方法 try { Thread.sleep(123); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("请看订单详情"); } }
代理类
public class TimerInvocationHandler implements InvocationHandler { //目标对象 public Object target; public TimerInvocationHandler(Object target){ this.target=target; } @Override /** * invoke方法是JDK负责调用的,所以JDK调用这个方法的时候会自动给我们传过来这三个参数的 * 我们可以在invoke方法的大括号中直接使用 * @param proxy 代理对象的引用,这个参数使用较少 * @param method 目标对象上的目标方法(要执行的目标方法就是它) * @param args 目标方法上的实参 * @return * @throws Throwable * * invoke方法执行过程中,使用method来调用目标对象的目标方法 * * */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //这个接口的目的是为了写增强代码 //System.out.println("增强一"); long begin=System.currentTimeMillis(); //调用目标对象上的目标方法 //方法四要素: 那个对象,那个方法,传什么参数,返回什么值 Object retValue = method.invoke(target, args); //System.out.println("增强二"); long end=System.currentTimeMillis(); System.out.println("耗时"+(end-begin)+"毫秒"); //注意这个invoke方法返回值,如果代理对象调用代理方法之后,需要返回结果的话,invoke方法必须将目标方法执行结果继续返回 return retValue; } }
1、为什么要强行要求必须实现InvocationHandler? 因为一个类实现接口就必须实现接口中的方法 以下这个方法必须是invoke(),因为JDK在底层调用了invoke()方法的程序已将提前写好了 注意:invoke方法不是我们程序负责调用的,是JDK负责调用的 2、 invoke方法什么时候被调用呢? 当代理对象调用代理方法的时候,注册在InvocationHandler调用处理器当中的invoke()方法被调用 3、invoke方法的三个参数 invoke方法是JDK负责调用的,所以JDK调用这个方法的时候会自动给我们传过来这三个参数的 我们可以在invoke方法的大括号中直接使用
获取代理对象的工具类
//代理工具类 public class ProxyUtil { /** *封装一个工具方法,可以通过这个方法获取代理对象 * * @param target * @return */ public static Object newProxyInstance(Object target){ //底层是调用的还是JDK的动态代理 return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new TimerInvocationHandler(target)); } }
* Proxy.newProxyInstance(类加载器,代理类要实现的接口,调用处理器) * newProxyInstance 翻译为: 新建代理对象 * 也就是说通过调用这个方法可以创建代理对象 * 本质上:这个Poxy.newProxyInstance()方法的执行,做了两件事 * 第一件事:在内存中动态的生成一个代理类的字节码class * 第二件事:new对象。通过内存中生成的代理类这个代码,实例化了代理对象 * 关于newProxyInstance()方法的三个重要的参数,每一个有什么含义,有什么用? * 第一个参数:ClassLoader loader * 类加载器,这个类加载器有什么用? * 在内存当中生成的字节码也是class文件,要执行也得先加载到内存当中,加载类就需要类加载器,所以这里需要指定类加载器 * 并且JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个 * * 第二个参数: Class[] interfaces * 代理类和目标类要实现同一个接口或同一些接口 * 在内存中生成代理类的时候,这个代理类是需要你告诉它实现那些接口的 * * 第三个参数:InvocationHandler h * InvocationHandler 被翻译为:调用处理器,是一个接口 * 在调用处理器接口中编写的的就是增强代码 * 因为具体要增强什么代码,JDK动态代理技术它是猜不到的,没有那么神 * 既然是接口,就要写接口的实现类 * * 需要自己手写调用处理器接口的实现,这不会类爆炸吗? 不会 * 因为这种调用处理器写一次就好了;
客户端
public class Client { //客户端 public static void main(String[] args){ //创建目标对象 OrderService target=new OrderServiceImpl(); //创建代理对象 // OrderService o = (OrderService) //Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfac//es(),new TimerInvocationHandler(target)); //上面的代码通过一个工具类的封装,就简洁了 OrderService o = (OrderService)ProxyUtil.newProxyInstance(target); //调用代理对象的代理方法 //注意:调用代理对象的代理方法的时候,如果你要增强的话,目标对象的目标方法需要执行 o.generate(); o.modify(); o.detail(); String name = o.getName(); System.out.println(name); } }
JDK动态代理的缺点:可以看出静态代理和JDK代理有一个共同的缺点,就是目标对象必须实现一个或多个接口
3.3Cglib代理
使用Cglib的前提条件:
- 需要引入cglib的jar文件,由于Spring的核心包中已经包括了Cglib功能,所以可以直接引入spring-core-3.2.5.jar
- 目标类不能使用final修饰符修饰
- 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。
目标类
public class UserService { //目标方法 public boolean login(String username,String password){ System.out.println("系统正在验证身份....."); if ("admin".equals(username)&&"123".equals(password)) { return true; } return false; } public void logout(){ System.out.println("系统正在退出...."); } }
代理类
public class TimerMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { //前面增强 long begin=System.currentTimeMillis(); //怎么调用目标对象的目标方法呢? Object retValue = methodProxy.invokeSuper(target, objects); //后面增强 long end=System.currentTimeMillis(); System.out.println("耗时"+(end-begin)+"毫秒"); return retValue; } }
客户端
public class Client { public static void main(String[] args){ //创建字节码增强对象 //这个对象是CGLIB库当中的核心对象,就是依靠它来生成代理类 Enhancer e=new Enhancer(); //告诉CGLIB父类是谁,实际上就是告诉CGLIB目标类是谁 e.setSuperclass(UserService.class); //设置回调(等同于JDK动态代理当中的调用处理器,InvocationHandler) //在CGLIB当中不是InvocationHandler接口,是方法拦截器接口:MethodInterceptor e.setCallback(new TimerMethodInterceptor()); //创建代理对象 //这一步会做两件事: //第一件事,在内存中生成UserService类的子类,其实就是代理类的字节码 //第二件事,创建代理对象 //父类是UserService,子类这个代理类一定就是UserService UserService userServiceProxy= (UserService)e.create(); //记住CGLIB动态代理生成的代理对象的名字格式 //根据这个名字可以推测出框架底层是否使用了CGLIB动态代理 System.out.println(userServiceProxy); //调用代理对象的代理方法 boolean success = userServiceProxy.login("admin", "123"); System.out.println(success?"登录成功":"登录失败"); userServiceProxy.logout(); } }
总结:三种代理模式各有优缺点和相应的适用范围,主要看目标对象是否实现了接口。以Spring框架所选择的代理模式举例
在Spring的AOP编程中:如果加入容器的目标对象有实现接口,用JDK代理如果目标对象没有实现接口,用Cglib代理