设计模式学习---代理模式
代理模式主要分为三种,静态代理、动态代理、Cglib代理
一、静态代理
静态代理的条件是目标类与代理类必须实现同一个接口然后通过调用相同的函数完成对目标函数的调用 ,业务操作由目标类实现,非业务操作由代理类实现
1.接口
public interface Test { void test(); }
2.目标类
public class TestImpl implements Test{ @Override public void test() { System.out.println("业务代码静态代理测试"); } }
3.代理类
public class TestProxy implements Test{ private Test test; public TestProxy(Test test) { this.test = test; } @Override public void test() { System.out.println("业务代码执行之前执行"); test.test(); System.out.println("业务代码执行之后执行"); } }
下面是测试代理的方法,需要通过代理类指向一个目标类
public static void main(String[] args) { Test test = new TestProxy(new TestImpl()); test.test(); }
执行之后结果:
使用静态代理使不同职能的代码区分降耦合,而且提供了良好的扩展性,但是代理类也必须实现接口,如果某个业务又需要记录日志,又需要管理实务,而且A类代理只能代理A类目标,这样就会多出很多类,而不能通过一个代理类完成对所有目标类的代理
二、动态代理
动态代理解决类静态代理A类代理只能代理A类目标的问题,因为动态代理的代理类不需要实现与目标类相同的接口,而是通过Java JDK提供的API java.lang.reflect.Proxy 实现,虽然代理类不用再与目标类实现同一个接口,但是目标类还是需要依赖接口实现才能完成代理。主要是通过ClassLoader对象来指定需要被代理的类,通过Interface[]来声明需要代理的函数再实现InvocationHandler重写invoke()并使用它进行代理
1.接口
public interface Test { String test(); }
2.目标类
public class TestImpl implements Test { @Override public String test() { System.out.println("业务代码动态代理测试"); return "动态代理返回值"; } }
3.代理类,这里通过代理工厂获取代理类
public class ProxyFactory { private Object obj; public ProxyFactory(Object obj) { this.obj = obj; } public Object getInstance(){ // 实现InvocationHandlet接口创建自己的调用处理器 return Proxy.newProxyInstance( // 通过ClassLoader指定需要被代理的类 obj.getClass().getClassLoader(), // 通过一个Interfaces数组来代理里面所有函数 obj.getClass().getInterfaces(), new InvocationHandler() { // 重写 invoke() 编写代理的逻辑 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("业务代码执行之前执行"); Object returnObj = method.invoke(obj, args); System.out.println("业务代码执行之后执行"); return returnObj; } }); } }
下面是测试代理的方法,需要通过代理类指向一个目标类
public static void main(String[] args) { Test test = new TestImpl(); Test instance = (Test)new ProxyFactory(test).getInstance(); instance.test(); }
执行结果:
这样就可以使用一个代理类代理多种目标类,不用实现相同接口,但是目标类依然要实现接口
三、Cglib代理( Code Generation Library )
Cglib代理也是动态代理的另外一种实现方案,JDK的动态代理有个缺陷,就是目标类必须实现一个接口才能被代理,这也是早期SpringAOP必须实现接口的一个原因,目前Spring支持JDK动态代理(下面称为Java Proxy)也支持Cglib动态代理
Cglib代理需要引入Cglib包,目标类与代理类都不需要再实现接口,首先需要引入maven包,它的主要原理是在内存中动态修改我们的class字节码文件,并且代理类不能是final,目标类的函数也不能是fianl或者static修饰
1.不需要实现接口的目标类
public class Test { public void test(){ System.out.println("业务代码Cglib代理测试"); } }
2.代理类工厂
public class ProxyFactory implements MethodInterceptor { private Object obj; public ProxyFactory(Object obj) { this.obj = obj; } //给目标对象创建一个代理对象 public Object getInstance(){ //1.CGlib工具类 Enhancer en = new Enhancer(); //2.设置父类(目标类) en.setSuperclass(obj.getClass()); //3.设置回调函数 en.setCallback(this); //4.创建子类(代理对象) return en.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("业务代码执行之前执行"); Object returnObj = method.invoke(obj, objects); System.out.println("业务代码执行之后执行"); return returnObj; } }
3.测试函数
public static void main(String[] args) { Test test = (Test)new ProxyFactory(new Test()).getInstance(); test.test(); }
执行结果:
SpringAOP其实使用的就是代理模式,它目前同时支持CGlib和java Proxy的动态代理,Spring在以前一些旧版本中不支持CGlib的情况下,我们的Service都会实现一个接口,从而方便AOP管理,但是现在一些没必要实现接口的类可以使用CGlib动态代理,减少代码量,除非某个函数真的有多种实现方式,这个时候我们才选择java Proxy的代理模式
总结
其实无论java Proxy动态代理或者CGlib动态代理都是通过新增我们的class字节码动态改变代码结构实现的,例如CGlib的类不能使用fianl的原因,是因为CGlib会帮我们的目标类创建一个子类来进行代理,如果类或者函数是final修饰的则无法被继承导致无法使用CGlib代理,下面简单写个例子
1.目标类
public class Test { public void test(){ System.out.println("自编简单继承代理"); } }
2.代理类
public class TestProxy extends Test{ @Override public void test() { System.out.println("业务代码执行之前执行"); super.test(); System.out.println("业务代码执行之后执行"); } }
测试函数
public static void main(String[] args) { Test test = new TestProxy(); test.test(); }
执行结果
如果我们在目标类的test()加上final来修饰
public final void test(){ System.out.println("自编简单继承代理"); } public static void main(String[] args) { Test test = new TestProxy(); test.test(); }
上面的测试结果与CGlib代理的结果是一样的,所以CGlib代理原理就是在我们代码编译的时候动态创建子类,且继承需要代理的函数从而实现动态代理。例如下面的图
但是java Proxy虽然也是在编译的时候动态创建代理类,不过与CGlib不一样的是,java Proxy会动态创建一个实现了与目标类相同接口的代理类,例如下面的图
Java Proxy与CGlib 都是通过新增class字节码来完成动态代理,但是它们实现新增字节码的方式是不一样的,java Proxy是直接操作字节码,而CGlib则是通过ASM操作字节码。当然也存在修改class字节码来完成代理的解决方案(Aspect,javaagent)