代理模式
1 代理模式概述
代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.
简单说代理模式就是在不改变原有代码的基础上对现有的代码进行功能的扩展(增强)。这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需要修改,可以通过代理的方式来扩展该方法
举个例子来说明代理的作用:假设我们想邀请一位明星,那么并不是直接联系明星,而是联系明星的经纪人,来达到同样的目的.明星就是一个目标对象,他只要负责活动中的节目,而其他琐碎的事情就交给他的代理人(经纪人)来解决.这就是代理思想在现实中的一个例子
代理模式的关键点是:代理对象与目标对象。代理对象是对目标对象的扩展,并会调用目标对象。简单说就是通过代理对象来控制对目标对象的访问。
在java中代理模式分为:静态代理和动态代理。
引入案例:日志功能
需求:
基于OOP思想,设计一个计算器类(add minus plus div)
新加需求:希望在计算器每个方法添加代码的日志打印功能
日志打印功能:记录代码执行过程【记录变量值 记录异常 记录执行结果】
3 代理模式实现の静态代理
静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口。
代理类:编写额外增强代码的类
被代理类的一般称为目标类,即要被增强功能的类
实现方案一:静态代理
静态代理总结
优点:
可以做到在不修改目标对象的代码前提下,对目标对象的功能实现扩展.
缺点:
因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护,程序维护难度较高。
如何解决静态代理的缺点呢?答案是可以使用动态代理方式
4 代理模式实现の动态代理
动态代理指利用反射或者其他机制,在jvm执行过程动态创建对象的方式,也就意味着说实际上java文件没有。
动态代理特点:物理上不需要创建代理类,代码运行期间利用反射机制生成代理对象的方式
JDK代理,也称为接口代理
JDK动态代理是使用java.lang.reflect包中的Proxy类与InvocationHandler接口来完成的,要使用JDK动态代理,目标对象必须要实现接口
语法:
static Object newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h)
注意该方法是在Proxy类中是静态方法,且接收的三个参数依次为:
-
ClassLoader loader,:指定当前目标对象使用类加载器,获取加载器的方法是固定的
-
Class<?>[] interfaces,:目标对象实现的接口的类型,使用泛型方式确认类型
-
InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入
动态代理有以下特点:
1.代理对象,需要实现和目标对象一样接口
2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
3.动态代理也叫做:JDK代理,接口代理操作步骤:
1、创建代理工厂类,实现InvocationHandler接口,创建自己增强代码的处理器。
2、给代理工厂类类提供ClassLoader对象和代理接口类型数组,创建动态代理对象。
3、在代理工厂中实现增强操作
案例:使用动态代理实现计算器方法动态添加日志功能
JDK动态代理总结
JDK代理要求被代理的目标对象一定要实现接口,否则不能用动态代理
动态代理のCgLib
CgLib代理
上面的静态代理和JDK代理都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理
//CgLib动态代理生成的代理类,代码形如以下格式:
public class 代理类$Proxy4 extends 目标类{
//实现增强功能
}
Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。
Cglib子类代理实现步骤:
1.需要引入cglib的jar文件,spring内部已经提供cglib代理依赖的jar包,因此spring开发环境中,不需要导入其他jar
2.引入功能包后,就可以在内存中动态构建子类
3.目标的类不能为final,否则报错
4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法
案例:使用Cglib实现动态代理
-
pom.xml导入cglib依赖
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2.2</version> </dependency>
-
cglib代理实现代码如下
public class CglibProxyFactory<T> { //提供目标对象 private T target; public CglibProxyFactory(T target) { this.target = target; } public T getProxyInstance(){ //1. 代理对象创建器 Enhancer hancer=new Enhancer(); //2.为代理对象生成的代码指定父类 把目标对象作为代理对象的父类 hancer.setSuperclass(target.getClass()); //3.指定代理对象调用方法时对应的处理器 hancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println(method.getName()+"方法被调用了,参数是:"+ Arrays.toString(args)); try { //目标对象执行核心功能 Object sum=method.invoke(target,args); System.out.println("方法正常执行,计算结果是"+sum); return sum; } catch (Exception e) { System.out.println("程序执行出错,出现的异常是"+e); throw new RuntimeException(e); } } }); return (T)hancer.create(); } }
-
测试cglib代理对象
@Test public void test03(){ //获取代理对象 //1.找目标 Calculator2 calculator2=new Calculator2(); //2.为目标找代理 CglibProxyFactory<Calculator2> factory=new CglibProxyFactory<>(calculator2); Calculator2 proxy = factory.getProxyInstance(); System.out.println("代理类类型:"+proxy.getClass()); //3.调用代理的方法 proxy.add(10,10); }
JDK代理和CGLIB代理区别
1 JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现.
2 Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)
3 Cglib包的底层是通过使用一个小而快的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉.