动态代理大揭秘,带你彻底弄清楚动态代理!
共 20699字,需浏览 42分钟
·
2021-09-19 15:47
作者:铂赛东
来源:SegmentFault 思否社区
前言
代理模式是一种设计模式,能够使得在不修改源目标的前提下,额外扩展源目标的功能。即通过访问源目标的代理类,再由代理类去访问源目标。这样一来,要扩展功能,就无需修改源目标的代码了。只需要在代理类上增加就可以了。
其实代理模式的核心思想就是这么简单,在java中,代理又分静态代理和动态代理2种,其中动态代理根据不同实现又区分基于接口的的动态代理和基于子类的动态代理。
其中静态代理由于比较简单,面试中也没啥问的,在代理模式一块,问的最多就是动态代理,而且动态代理也是spring aop的核心思想,spring其他很多功能也是通过动态代理来实现的,比如拦截器,事务控制等。
熟练掌握动态代理技术,能让你业务代码更加精简而优雅。如果你需要写一些中间件的话,那动态代理技术更是必不可少的技能包。
那此篇文章就带大家一窥动态代理的所有细节吧。
静态代理
在说动态代理前,还是先说说静态代理。
所谓静态代理,就是通过声明一个明确的代理类来访问源对象。
我们有2个接口,Person和Animal。每个接口各有2个实现类,UML如下图:
每个实现类中代码都差不多一致,用Student来举例(其他类和这个几乎一模一样)
public class Student implements Person{
private String name;
public Student() {
}
public Student(String name) {
this.name = name;
}
@Override
public void wakeup() {
System.out.println(StrUtil.format("学生[{}]早晨醒来啦",name));
}
@Override
public void sleep() {
System.out.println(StrUtil.format("学生[{}]晚上睡觉啦",name));
}
}
假设我们现在要做一件事,就是在所有的实现类调用wakeup()
前增加一行输出早安~
,调用sleep()
前增加一行输出晚安~
。那我们只需要编写2个代理类PersonProxy
和AnimalProxy
:
PersonProxy:
public class PersonProxy implements Person {
private Person person;
public PersonProxy(Person person) {
this.person = person;
}
@Override
public void wakeup() {
System.out.println("早安~");
person.wakeup();
}
@Override
public void sleep() {
System.out.println("晚安~");
person.sleep();
}
}
AnimalProxy:
public class AnimalProxy implements Animal {
private Animal animal;
public AnimalProxy(Animal animal) {
this.animal = animal;
}
@Override
public void wakeup() {
System.out.println("早安~");
animal.wakeup();
}
@Override
public void sleep() {
System.out.println("晚安~");
animal.sleep();
}
}
最终执行代码为:
public static void main(String[] args) {
Person student = new Student("张三");
PersonProxy studentProxy = new PersonProxy(student);
studentProxy.wakeup();
studentProxy.sleep();
Person doctor = new Doctor("王教授");
PersonProxy doctorProxy = new PersonProxy(doctor);
doctorProxy.wakeup();
doctorProxy.sleep();
Animal dog = new Dog("旺旺");
AnimalProxy dogProxy = new AnimalProxy(dog);
dogProxy.wakeup();
dogProxy.sleep();
Animal cat = new Cat("咪咪");
AnimalProxy catProxy = new AnimalProxy(cat);
catProxy.wakeup();
catProxy.sleep();
}
输出:
早安~
学生[张三]早晨醒来啦
晚安~
学生[张三]晚上睡觉啦
早安~
医生[王教授]早晨醒来啦
晚安~
医生[王教授]晚上睡觉啦
早安~~
小狗[旺旺]早晨醒来啦
晚安~~
小狗[旺旺]晚上睡觉啦
早安~~
小猫[咪咪]早晨醒来啦
晚安~~
小猫[咪咪]晚上睡觉啦
结论:
静态代理的代码相信已经不用多说了,代码非常简单易懂。这里用了2个代理类,分别代理了Person
和Animal
接口。
这种模式虽然好理解,但是缺点也很明显:
会存在大量的冗余的代理类,这里演示了2个接口,如果有10个接口,就必须定义10个代理类。
不易维护,一旦接口更改,代理类和目标类都需要更改。
JDK动态代理
java.lang.reflect.Proxy
和java.lang.reflect.InvocationHandler
。public class JdkProxy implements InvocationHandler {
private Object bean;
public JdkProxy(Object bean) {
this.bean = bean;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (methodName.equals("wakeup")){
System.out.println("早安~~~");
}else if(methodName.equals("sleep")){
System.out.println("晚安~~~");
}
return method.invoke(bean, args);
}
}
public static void main(String[] args) {
JdkProxy proxy = new JdkProxy(new Student("张三"));
Person student = (Person) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Person.class}, proxy);
student.wakeup();
student.sleep();
proxy = new JdkProxy(new Doctor("王教授"));
Person doctor = (Person) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Person.class}, proxy);
doctor.wakeup();
doctor.sleep();
proxy = new JdkProxy(new Dog("旺旺"));
Animal dog = (Animal) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Animal.class}, proxy);
dog.wakeup();
dog.sleep();
proxy = new JdkProxy(new Cat("咪咪"));
Animal cat = (Animal) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Animal.class}, proxy);
cat.wakeup();
cat.sleep();
}
JDK动态代理是需要声明接口的,创建一个动态代理类必须得给这个”虚拟“的类一个接口。可以看到,这时候经动态代理类创造之后的每个bean已经不是原来那个对象了。
为什么这里JdkProxy还需要构造传入原有的bean呢?因为处理完附加的功能外,需要执行原有bean的方法,以完成代理的职责。
这里JdkProxy最核心的方法就是
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
method.invoke(proxy, args)
的话,那么就会陷入一个死循环。InvocationHandler
的实现类里。上述例子我是需要继续执行原有bean的逻辑,才将原有的bean构造进来。只要你需要,你可以构造进任何对象到这个代理实现类。也就是说,你可以传入多个对象,或者说你什么类都不代理。只是为某一个接口”凭空“的生成多个代理实例,这多个代理实例最终都会进入InvocationHandler
的实现类来执行某一个段共同的代码。InvocationHandler
的实现类就可以了,同时把yaml解析过的对象传入。最终这些动态代理类都会进入invoke
方法来执行某个共同的逻辑。Cglib动态代理
jdk动态代理只能基于接口,代理生成的对象只能赋值给接口变量,而Cglib就不存在这个问题,Cglib是通过生成子类来实现的,代理对象既可以赋值给实现类,又可以赋值给接口。
Cglib速度比jdk动态代理更快,性能更好。
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
private Object bean;
public CglibProxy(Object bean) {
this.bean = bean;
}
public Object getProxy(){
//设置需要创建子类的类
enhancer.setSuperclass(bean.getClass());
enhancer.setCallback(this);
//通过字节码技术动态创建子类实例
return enhancer.create();
}
//实现MethodInterceptor接口方法
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
String methodName = method.getName();
if (methodName.equals("wakeup")){
System.out.println("早安~~~");
}else if(methodName.equals("sleep")){
System.out.println("晚安~~~");
}
//调用原bean的方法
return method.invoke(bean,args);
}
}
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy(new Student("张三"));
Student student = (Student) proxy.getProxy();
student.wakeup();
student.sleep();
proxy = new CglibProxy(new Doctor("王教授"));
Doctor doctor = (Doctor) proxy.getProxy();
doctor.wakeup();
doctor.sleep();
proxy = new CglibProxy(new Dog("旺旺"));
Dog dog = (Dog) proxy.getProxy();
dog.wakeup();
dog.sleep();
proxy = new CglibProxy(new Cat("咪咪"));
Cat cat = (Cat) proxy.getProxy();
cat.wakeup();
cat.sleep();
}
//设置需要创建子类的类
enhancer.setSuperclass(bean.getClass());
enhancer.setCallback(this);
//通过字节码技术动态创建子类实例
return enhancer.create();
intercept
方法。而在intercept
方法里,进行了附加功能的执行,最后还是调用了原始bean的相应方法。javassist动态代理
public class JavassitProxy {
private Object bean;
public JavassitProxy(Object bean) {
this.bean = bean;
}
public Object getProxy() throws IllegalAccessException, InstantiationException {
ProxyFactory f = new ProxyFactory();
f.setSuperclass(bean.getClass());
f.setFilter(m -> ListUtil.toList("wakeup","sleep").contains(m.getName()));
Class c = f.createClass();
MethodHandler mi = (self, method, proceed, args) -> {
String methodName = method.getName();
if (methodName.equals("wakeup")){
System.out.println("早安~~~");
}else if(methodName.equals("sleep")){
System.out.println("晚安~~~");
}
return method.invoke(bean, args);
};
Object proxy = c.newInstance();
((Proxy)proxy).setHandler(mi);
return proxy;
}
}
public static void main(String[] args) throws Exception{
JavassitProxy proxy = new JavassitProxy(new Student("张三"));
Student student = (Student) proxy.getProxy();
student.wakeup();
student.sleep();
proxy = new JavassitProxy(new Doctor("王教授"));
Doctor doctor = (Doctor) proxy.getProxy();
doctor.wakeup();
doctor.sleep();
proxy = new JavassitProxy(new Dog("旺旺"));
Dog dog = (Dog) proxy.getProxy();
dog.wakeup();
dog.sleep();
proxy = new JavassitProxy(new Cat("咪咪"));
Cat cat = (Cat) proxy.getProxy();
cat.wakeup();
cat.sleep();
}
ByteBuddy动态代理
public class ByteBuddyProxy {
private Object bean;
public ByteBuddyProxy(Object bean) {
this.bean = bean;
}
public Object getProxy() throws Exception{
Object object = new ByteBuddy().subclass(bean.getClass())
.method(ElementMatchers.namedOneOf("wakeup","sleep"))
.intercept(InvocationHandlerAdapter.of(new AopInvocationHandler(bean)))
.make()
.load(ByteBuddyProxy.class.getClassLoader())
.getLoaded()
.newInstance();
return object;
}
public class AopInvocationHandler implements InvocationHandler {
private Object bean;
public AopInvocationHandler(Object bean) {
this.bean = bean;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (methodName.equals("wakeup")){
System.out.println("早安~~~");
}else if(methodName.equals("sleep")){
System.out.println("晚安~~~");
}
return method.invoke(bean, args);
}
}
}
public static void main(String[] args) throws Exception{
ByteBuddyProxy proxy = new ByteBuddyProxy(new Student("张三"));
Student student = (Student) proxy.getProxy();
student.wakeup();
student.sleep();
proxy = new ByteBuddyProxy(new Doctor("王教授"));
Doctor doctor = (Doctor) proxy.getProxy();
doctor.wakeup();
doctor.sleep();
proxy = new ByteBuddyProxy(new Dog("旺旺"));
Dog dog = (Dog) proxy.getProxy();
dog.wakeup();
dog.sleep();
proxy = new ByteBuddyProxy(new Cat("咪咪"));
Cat cat = (Cat) proxy.getProxy();
cat.wakeup();
cat.sleep();
}
各种动态代理的性能如何
JDK动态代理采用接口代理的模式,代理对象只能赋值给接口,允许多个接口
Cglib,Javassist,ByteBuddy这些都是采用了子类代理的模式,代理对象既可以赋值给接口,又可以复制给具体实现类
JDK PROXY循环10000遍所耗时:0.714970125秒
Cglib循环10000遍所耗时:0.434937833秒
Javassist循环10000遍所耗时:1.294194708秒
ByteBuddy循环10000遍所耗时:9.731999042秒