动态代理和静态代理的区别就是在是现阶段不用关心代理类,代理类不是程序员编写的,而是在运行时动态生成的,动态代理的实现方式通常有两种:JDK动态代理和CGLIB代理
JDK动态代理
是Java自带的代理方式
原理:
是在运行时动态生成一个代理类,这个代理对象一定和原有对象实现同一个接口,这样就可以有相同的行为,所以当使用JDK动态代理时,原有对象和代理对象必须满足这个条件
CGLIB代理
Code Generation Library是一个开源项目,是一个强大的、高性能的、高质量的Code生成类库,它可以在运行器拓展Java类和实现Java接口
原理:CGLIB通过动态生成一个子类,该子类继承被代理类,重写被代理类的所有不是final修饰的方法,并在子类中采用方法拦截的技术拦截父类的所有方法的掉用
两种代理方式的使用教程
JDK动态代理的使用
声明一个接口Drink:
public interface Drink {
void drink();
}
创建一个实现该接口的类:
public class Milk implements Drink {
@Override
public void drink() {
System.out.println("drink milk");
}
}
JDK动态代理处理器:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class CommonInvocationHandler implements InvocationHandler {
// 被代理的对象
private Object proxied;
public CommonInvocationHandler(Object proxied) {
this.proxied = proxied;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在调用被代理对象的方法前做一些事情
System.out.println("before doing something");
// 调用被代理对象的方法
Object result = method.invoke(proxied, args);
// 在调用被代理对象的方法后做一些事情
System.out.println("after doing something");
return result;
}
}
使用JDK动态代理:
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
Milk milk = new Milk();
Drink proxy = (Drink) Proxy.newProxyInstance(milk.getClass().getClassLoader(), milk.getClass().getInterfaces(), new CommonInvocationHandler(new Milk()));
proxy.drink();
}
}
输出结果:
before doing something
drink milk
after doing something
CGLIB代理的使用
导入CGLIB依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
创建被代理类:
public class Producer {
public void spanMoney(double monty) {
System.out.println("花费: " + monty + "元");
}
}
使用CGLIB代理:
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class Consumer {
public static void main(String[] args) {
// new一个被代理对象,因为代理类执行被代理类的方法时,需要用到被代理类的对象
final Producer producer = new Producer();
Producer producerProxy = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println(methodProxy);
Double money = (Double) objects[0];
return method.invoke(producer, money * 0.8);
}
});
producerProxy.spanMoney(1000);
}
}
输出结果:
org.springframework.cglib.proxy.MethodProxy@39a054a5
花费: 800.0元
JDK动态代理和CGLIB的区别
- JDK动态代理只能对接口进行代理,不能对普通的类进行代理,这是因为JDK动态代理生成的代理类,其父类是Proxy,且Java不支持类的多继承
- CGLIB能够代理接口和普通类,但是被代理的类不能被final修饰,且接口中的方法不能使用final修饰
- JDK动态代理使用Java反射来实现,在生成类上更高效
- CGLIB使用ASM框架直接对字节码进行修改,使用了FastClass的特性,在某些情况下,类的方法执行会比较高效
动态代理的一些限制:
√表示可以代理,×表示不可代理
| private | static | final | |
|---|---|---|---|
| JDK动态代理 | × | × | √ |
| CGLIB | × | × | × |
为什么CGLIB不能代理private、final、static方法?
因为CGLIB代理类是通过继承被代理类来实现的,所以:
- 如果方法被private修饰,那么子类是无法调用超类的私有方法的;
- 如果被static修饰,从技术上的角度来说是可以实现的,但是被排除了,因为从程序设计角度来说,静态方法是属于类的,而不是属于某个对象的
为什么JDK动态代理不能代理private、static方法,而可以代理final方法?
在JDK动态代理中,代理类和被代理类需要实现共同的接口,来拥有相同的行为,那么private、static方法都是无法修饰接口方法的,自然不行
final修饰的是实现了接口类的子类的方法,不影响代理的调用,所以是可以的
Spring中两者的使用场景
-
如果目标对象实现了接口,默认情况下会采用JDK动态代理来实现AOP,可以强制使用CGLIB来实现AOP
强制使用CGLIB实现AOP的方式:
-
添加CGLIB库
-
在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>,或者在SpringBoot的application中配置:
spring: aop: proxy-target-class: true该值默认为false,表示使用JDK动态代理,手动设置为true,表示使用CGLIB代理,但即使默认为false,当目标没有实现接口时,Spring会自动使用CGLIB代理
-
-
如果目标对象没有实现接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换