Java代理模式小结

问题提出

如果由一个类User,其中有一个方法add(),如下

public class User {
  
  public void add() {
    System.out.println("add...");
  }
}

如果要对add()方法扩展功能,比如在输出"add..."之前,先输出一句"before...",在输出"add..."之后输出一句"after...",那么就需要修改add()方法,如下

public class User {
  
  public void add() {
    System.out.println("before...");
    System.out.println("add...");
    System.out.println("after...");
  }
}

但是直接对add()方法进行修改,不利于系统维护,也容易产生代码的重复。比如在add()以及许多方法中,需要添加记录日志的功能,则需要在这些方法中写记录日志功能的逻辑,或者调用记录日志功能的方法,这会产生很多重复的代码,不利于模块的重用。

问题的解决方案

为了解决如上问题,最早使用的方案是纵向继承机制

纵向继承机制

纵向继承机制即继承于一个类,从而在方法中调用父类的方法,实现功能扩展,如下

public class BaseUser {
  //记录日志的方法
  public void writeLog() {
    ...
  }
}

public class User extends BaseUser {
  public void add() {
    ...

    //调用父类方法,实现功能扩展
    super.writeLog();
  }
}

但是这种做法也有弊端,并不能根本上解决问题,比如对BaseUser类中的writeLog()方法进行修改,将其改名位recordLog()或添加参数,则User类中的add()方法也要进行修改。这样也是不利于维护的。

横向抽取机制(代理模式)

为了从根本上解决问题,出现了代理模式,代理模式即为委托类创建代理类,通过代理类可以调用委托类的部分功能,并添加一些额外的业务处理。代理模式分为静态代理动态代理

  • 静态代理: 自己手动创建代理类,代理类在编译期间就已经确定

    public interface Dao {
      public void add();
    }
    
    //委托类
    public class DaoImpl implements Dao {
      public void add() {
        System.out.println("add...");
      }
    }
    
    //代理类
    public class DaoProxy implements Dao {
      private DaoImpl daoImpl;
      
      public DaoProxy(DaoImpl daoImpl) {
      	this.daoImpl = dapImpl;
      }
      
      public void add() {
        System.out.println("beagin");
        daoImpl.add();
        System.out.println("after");
      }
    }
    
    //测试类
    public class ProxyTest {
      public static void main(String[] args) {
        DaoImpl daoImpl = new DaoImpl();
        DaoProxy daoProxy = new DaoProxy(daoImpl);
        daoProxy.add();
      }
    }
    
  • 动态代理: 代理类不是由自己编写,而是在运行时期生成,下面详细介绍动态代理

动态代理

动态代理分为jdk动态代理和cglib动态代理,Spring的AOP动态代理就是通过这两者实现的

jdk动态代理

jdk动态代理即使用jdk中提供反射类Proxy和回调接口InvocationHandler实现jdk动态代理,并要求委托类必须实现至少一个接口

实现步骤:

  1. 定义业务逻辑

    public interface Dao {
      public void add();
    }
    
    public class DaoImpl implements Dao {
      public void add() {
        System.out.println("add...");
      }
    }
    
  2. 利用反射类Proxy和回调接口InvocationHandler实现jdk动态代理

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    class TestInvocationHandler implements InvocationHandler {
      private Object target;
      
      public TestInvocationHandler(Object target) {
      	this.target = target;
      }
      
      //实现InvocationHandler接口的invoke方法,用于执行目标对象
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before...");
        Object result = method.invoke(target, args);
        System.out.println("after...");
        return result;
      }
      
      //生成代理对象
      public Object getProxy() {
        //定义代理类的加载者
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        //被代理对象实现的接口列表
        Class<?>[] interfaces = target.getClass().getInterfaces();
        //使用Java字节码技术生成代理对象并返回
        return Proxy.newProxyInstance(loader, interfaces, this);
      }
    }
    
  3. 测试类

    public class ProxyTest {
      public static void main(String[] args) {
        Dao dao = new DaoImpl();
        TestInvocationHandler handler = new TestInvocationHandler(dao);
        //daoProxy就是生成的代理对象,它继承于Proxy类,且实现了Dao接口
        Dao daoProxy = (Dao) handler.getProxy();
        //执行add()方法,实际上是执行handler对象的invoke方法,传入的参数分别为当前代理对象,当前执行方法和方法参数
        daoProxy.add();
      }
    }
    

cglib动态代理

jdk动态代理需要实现类通过接口定义业务方法,对于没有接口的类,jdk毫无办法,这就需要cglib了。

  • cglib是一个强大、高性能、高质量的Code生成类库,它封装了asm(Java字节码操控框架)
  • cglib通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑
  • 由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理

实现步骤:

  1. 需要被代理的类

    public class Dao {
      public void add() {
    	System.out.println("add...");
      }
    }
    
  2. 方法拦截器

    import java.lang.reflect.Method;
    
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    //方法拦截器
    public class TestProxy implements MethodInterceptor {
    
      //增强原有方法
      @Override
      public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    	//前置代理
    	System.out.println("before...");
    		
    	Object result = proxy.invokeSuper(obj, args);	//调用父类方法
    		
    	//后置代理
    	System.out.println("end...");
    		
    	return result;
      }
    
    }
    
  3. 产生Dao代理类的工厂类

    import net.sf.cglib.proxy.Enhancer;
    
    public class DaoFactory {
    	
      private static Dao dao;
    	
      static {
    	dao = new Dao();
      }
    
      public static Dao getDao() {
    	return dao;
      }
    	
      //获取代理类
      public static Dao getProxyInstance(TestProxy proxy) {
    	Enhancer en = new Enhancer();
    	en.setSuperclass(Dao.class);
    	en.setCallback(proxy);
    	return (Dao) en.create();
      }
    }
    
  4. 测试

    public class Test {
      public static void main(String[] args) {
    	Dao dao = DaoFactory.getProxyInstance(new TestProxy());
    	dao.add();
      }
    }
    

Spring中AOP的代理方式

  • 如果目标对象实现了接口,默认情况下会采用jdk动态代理实现AOP

  • 如果目标对象实现了接口,可以强制使用cglib动态代理实现AOP

    • 强制使用cglib实现方式:

      1. 添加cglib库

      2. 在Spring配置文件中加入

        <aop:aspectj-autoproxy proxy-target-class="true"/>
        
  • 如果目标对象没有实现接口,AOP必须由cglib动态代理实现

  • Spring会自动在jdk动态代理和cglib动态代理之间切换

posted @ 2020-09-03 14:59  Ryokai  阅读(98)  评论(0)    收藏  举报