1.静态代理
静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。
所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
一句话,自己手写代理类就是静态代理。
被代理对象RealSubject负责执行相关业务逻辑,不同的被代理对象处理不同的业务,而代理对象只需要访问这些业务方法就行,不需要处理具体业务逻辑,但是针对某一类共同问题的的处理,就可以移到代理对象里来实现对方法的增强,由于代理模式功能增强这一块在实际开发中尤为突出,所以后面的例子也会稍微针对一下。
1、静态代理的实例
- Subject:定义一个普通的接口方法
public interface Moveable { void move(); }
- RealSubject:实现该接口方法
public class Car implements Moveable { @Override public void move() { System.out.println("汽车行驶中...."); } }
- ProxySubject: 调用RealSubject的方法,并在调用前后进行增强处理。
public class CarProxy implements Moveable{
private Moveable move;
@Override
public void move() {
if(move==null){
move = new Car();
}
System.out.println("开始记录日志:");
move.move();
System.out.println("记录日志结束!");
}
}
- 测试方法:实际上是调用ProxySubject的move()方法。
public static void main(String[] args) throws Exception {
Moveable m =new CarProxy();
m.move();
}
2、静态代理的优缺点
优点:
业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理模式的共有优点。
缺点:
1)代理对象的一个接口只服务于一种类型的对象,如果要代理的类型很多,势必要为每一种类型的方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
- 比如Car类的move()方法需要记录日志,如果还有汽车,火车,自行车类的move()方法也需要记录日志,我们都要一个个的去为它们生成代理类,太麻烦了。
2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。显而易见,增加了代码维护的复杂度。
2.动态代理
动态代理有jdk动态代理模式和cglib代理模式。JDK的动态代理只能代理实现了接口的类, 没有实现接口的类不能实现动态代理;而cglib的动态代理是通过继承实现的,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
动态代理实例
JDK动态代理实现步骤:
- 创建被代理的类以及实现的接口;
- 创建一个实现接口InvocationHandler的类,它必须实现invoke方法;
- 调用Proxy的newProxyInstance静态方法,创建一个代理类。
- 通过代理对象调用目标方法。
- 1 创建一个Subject接口,里面有三个方法
public interface Subject {
String speak();
}
- 2 创建一个目标对象实现接口
public class TargetSubject implements Subject {
@Override
public String speak() {
System.out.println("speak方法执行");
return "结束";
}
}
- 3 创建handler,实现InvocationHandler,Proxy创建代理类建时需要这个handler参数。在handler的invoke方法里,执行被代理对象中参数为args的method方法,method是在代理类中通过反射获得,传入handler的,这个后面会说明。
public class DynamicInvocationHandler implements InvocationHandler {
private Subject subject;
public DynamicInvocationHandler(Subject subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
//增强处理
System.out.println("增强处理--前");
//通过反射获取到接口的method,执行subject目标对象中带参数args的method方法
String response = (String) method.invoke(subject, args);
//增强处理
System.out.println("增强处理--后");
return response;
}
}
- 4 在Main方法中创建代理类,测试代理类创建流程
Subject targetSubject = new TargetSubject();
InvocationHandler handler = new DynamicInvocationHandler(targetSubject);
//创建代理类的实例
Subject proxySubject = (Subject) Proxy.newProxyInstance(targetSubject.getClass()
.getClassLoader()
, targetSubject.getClass().getInterfaces()
, handler);
//调用被代理类的方法
proxySubject.speak();
- 查看结果:
增强处理--前
speak方法执行
增强处理--后
>5)InvocationHandler分析
接下来看看代理类的字节码文件里的信息,看看代理类是如何通过InvocationHandler来控制目标对象的。
- 获取字节码文件的方法:上面的分析,通过ProxyGenerator.generateProxyClass里可以代理类的字节码,通过输出流可以写到本地文件如$Proxy0.class,然后直接通过反编译查看。
public final class $Proxy0
extends Proxy
implements Subject {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
// 反射时获取这个构参的构造函数来创建代理类的实例对象
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
//代理类实现方法,会执行InvocationHandler的invoke方法
public final String getResponse(String string) {
try {
return (String)this.h.invoke((Object)this, m3, new Object[]{string});
}
catch (Error | RuntimeException v0) {
throw v0;
}
catch (Throwable var2_2) {
throw new UndeclaredThrowableException(var2_2);
}
}
//静态代码块,反射得到我通过代理类调用的方法
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName
("java.lang.Object"));
m3 = Class.forName("com.example.pver.proxydesign.dynamicproxy.Subject")
.getMethod("getResponse", Class.forName("java.lang.String"));
m2 = Class.forName("java.lang.Object")
.getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object")
.getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException var1) {
throw new NoSuchMethodError(var1.getMessage());
}
catch (ClassNotFoundException var1_1) {
throw new NoClassDefFoundError(var1_1.getMessage());
}
}
字节码文件分析:
1,通过Proxy.newProxyInstance生成的代理类,执行接口里的方法,实际上是调用了InvocationHandler 的
invoke方法(参考上面直接码里的getResponse方法),参数分别为当前代理类、用户执行的方法和我们调用该方法时传入的参数。
2,InvocationHandler需要用户自定义,在invoke方法里执行method.invoke(subject, args),用来执行目标对象里参数为args的方法method,故InvocationHandler才是真正的被代理类的直接访问者。
3,代理类继承了Proxy,由于对象的单继承性质,所以代理类不能再继承其他类,所以JDK的动态代理只支持接口的方式代理,并不能支持具体实现类的代理。
通过上面的分析,给出动态代理的详细类图:
>6)总结下动态代理的流程:
- 1,jdk获取RealSubject上的所有接口列表,确定要生成的代理类类名。
- 2,jdk根据接口信息和类名动态创建代理类的字节码。
- 3,通过类加载器将字节码转换成代理类class对象。
- 4,创建InvocationHandler的实例handler,用来处理对被代理类的所有方法的访问。
- 5,通过反射获取代理类中以handler类为参数的构造。
- 6,使用构造创建代理类的实例。
- 7,用户通过代理类的实例,调用接口里的方法,将方法参数和该方法传入handler的invoke方法。
- 8,handler的invoke方法里,调用method.invoke(subject, args),用来执行目标对象subject里参数为args的方法method。
- 9,目标对象RealSubject执行具体业务逻辑,如果有返回值,就将返回传值回给handler。
(二)cglib的动态代理
实例1:
引用cglib的依赖包
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>2.2</version>
</dependency>
为了方便思考它的原理,我把执行步骤按顺序写下
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
//设置父类,被代理类(这里是Car.class)
enhancer.setSuperclass(Car.class);
//设置回调函数
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
//增强处理...
Object o= proxy.invokeSuper(obj, args);//代理类调用父类的方法
//增强处理...
return o;
}
});
//创建代理类并使用回调(用父类Car去引用)
Car car = (Car) enhancer.create();
//执行目标方法
System.out.println(car.move());
}
方法拦截器
实现MethodInterceptor接口的intercept方法后,所有生成的代理方法都调用这个方法。
intercept方法的具体参数有
- obj 目标类的实例
- method 目标方法实例(通过反射获取的目标方法实例)
- args 目标方法的参数
- proxy 代理类的实例
该方法的返回值就是目标方法的返回值。
特点
- cglib的动态代理是针对类来实现代理。
- 对指定目标类产生一个子类,通过方法拦截技术拦截所有父类方法的调用。
- 因为是通过继承实现,final类无法使用。
实例2:
静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理
Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.
- JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现.
- Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)
- Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉.
Cglib子类代理实现方法:
1.需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入
pring-core-3.2.5.jar
即可.
2.引入功能包后,就可以在内存中动态构建子类
3.代理的类不能为final,否则报错
4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.
代码示例:
目标对象类:UserDao.java
/**
* 目标对象,没有实现任何接口
*/
public class UserDao {
public void save() {
System.out.println("----已经保存数据!----");
}
}
Cglib代理工厂:ProxyFactory.java
/**
* Cglib子类代理工厂
* 对UserDao在内存中动态构建一个子类对象
*/
public class ProxyFactory implements MethodInterceptor{
//维护目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//给目标对象创建一个代理对象
public Object getProxyInstance(){
//1.工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回调函数
en.setCallback(this);
//4.创建子类(代理对象)
return en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开始事务...");
//执行目标对象的方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务...");
return returnValue;
}
}
测试类:
/**
* 测试类
*/
public class App {
@Test
public void test(){
//目标对象
UserDao target = new UserDao();
//代理对象
UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();
//执行代理对象的方法
proxy.save();
}
}
在Spring的AOP编程中:
如果加入容器的目标对象有实现接口,用JDK代理
如果目标对象没有实现接口,用Cglib代理