代理模式是一种提供了对目标对象额外的访问方式,即通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
代理模式又分为:静态代理、jdk动态代理、cglib动态代理。静态代理不讲,因为会产生过多的代理对象,不易维护。而动态代理是动态地在内存中构建代理对象,从字节码上进行处理,从而实现对目标对象的代理功能,接口增加方法时代理对象不受影响 。
代码实现
pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.example</groupId>
<artifactId>test</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
JDK动态代理的实现
package test.test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
public class TestJDK {
@org.junit.Test
public void main() {
IPerson target=new Person();
IPerson person=(IPerson) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),new PersonInvocationHandler(target));
person.sing();
}
class PersonInvocationHandler implements InvocationHandler{
private Object target;
public PersonInvocationHandler(Object target){
this.target=target;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
//方法增强
System.out.println("鸡你太美真的是泰裤辣!!!!");
Object invoke = method.invoke(target, objects);
return invoke;
}
}
interface IPerson{
void sing();
}
static class Person implements IPerson{
@Override
public void sing() {
System.out.println("鸡你太美");
}
}
}
结论
1.jdk动态代理需要提供目标类要实现的接口,代理的代理类实现了目标类实现的接口,并且会实现接口所有方法来代码增强,如果目标类未实现接口则无法代理。
2.由运行图结果可以看出,在调用时会先去调用处理类进行增强,再通过反射的方式调用目标类的方法。
3…jdk动态代理会在运行时为目标类生成一个动态代理类$proxy*.class
Cglib代码实现
package test.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.omg.CORBA.PUBLIC_MEMBER;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.test.context.junit4.SpringRunner;
import java.lang.reflect.Method;
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestCglib {
@Test
public void test(){
Person proxy=(Person) Enhancer.create(Person.class, new PersonMethodInterceptor());
proxy.sing();
}
class PersonMethodInterceptor implements MethodInterceptor{
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("你家鲲鲲泰裤辣");
Object result = methodProxy.invokeSuper(o, objects);
return result;
}
}
public static class Person{
public void sing(){
System.out.println("鸡你太美");
}
}
}
结论
1.cglib的底层是通过ASM在运行时动态生成目标类的子类,采用继承的方式,所以目标类不能被final关键字修饰,否则无法使用cglib做动态代理
2.cglib动态代理会重写父类所有的方法来代码增强,调用时先通过代理类进行增强,再直接调用父类对应的方法进行调用目标方法
总结
- 实现机制:jdk动态代理必须要有目标类要实现的接口,且只提供这一种方式,如果目标类没有实现接口,只能用cglib代理,而无法用jdk动态代理
- 底层运行:jdk动态代理利用拦截器(必须实现InvocationHandler)加上反射机制,生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理,而cglib的底层是通过ASM框架,在运行时把代理对象类生成的class文件加载进来,通过修改其字节码生成子类来处理
- 增强机制: jdk动态代理的代理类实现了目标类实现的接口,会通过实现接口所有方法来代码增强。cglib动态代理通过继承机制,重写父类所有的方法来代码增强
- 调用机制:jdk动态代理调用时先去调用处理类进行增强,再通过反射的方式调用目标类的方法。cglib动态代理调用时先通过代理类(子类)进行增强,再直接调用父类对应的方法进行调用目标方法。 jdk调用代理方法,是通过反射机制调用,cglib是通过FastClass机制直接调用方法
- 性能上:在jdk1.6之前,由于反射调用慢,所以jdk动态代理效率不高,cglib是jdk代理速度的10倍左右。后续jdk版本对jdk动态代理进行了优化,在jdk1.8之前,在调用次数较少时效率高于cglib代理,在大量调用的时候cglib效率会高于jdk动态代理。但是在jdk1.8的时候,jdk动态代理效率已经高于cglib
- Spring中档bean实现接口时,会采用jdk动态代理模式;当bean没有实现接口时会采用cglib实现,但是可以通过配置项强制使用cglib