1. 什么是动态代理?
动态代理是一种实现代理模式的技术,允许在运行时创建代理对象,并将方法调用重定向到特定的处理器,说白了就是能在不破坏原有代码的基础上进行拓展,能在方法执行之前、执行之后或者其他时机做额外的操作。
2. 动态代理有哪些?
动态代理分为两种:
- Cglib(基于继承的方式实现)
- JdkProxy(基于接口的方式实现)
3. 如何实现动态代理?
公共依赖
XML
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
基础代码
UserService
类
JAVA
public class UserService {
public void addUser(String user) {
System.out.println("添加user信息:" + user);
this.printUser(user);
}
private void printUser(String user) {
System.out.println("printUser...:" + user);
}
}
PersonService
接口
JAVA
public interface PersonService {
void addPerson(String person);
}
PersonService
实现类
JAVA
public class PersonServiceImpl implements PersonService {
@Override
public void addPerson(String person) {
System.out.println("添加人的信息:" + person);
this.printPerson(person);
}
private void printPerson(String person) {
System.out.println("printPerson...:" + person);
}
}
3.1 Cglib
- 首先定义一个Cglib的工具类
JAVA
/**
* 基于Cglib的代理,无需实现接口,基于继承实现
*/
public class CglibProxy implements MethodInterceptor {
Class<?> target;
public CglibProxy(Class<?> target) {
this.target = target;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("begin intercept");
methodProxy.invokeSuper(o, objects);
System.out.println("end intercept");
return o;
}
}
- 编写测试代码
JAVA
public class CglibTest {
@Test
public void test() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(new CglibProxy(UserService.class));
UserService userService = (UserService) enhancer.create();
userService.addUser("张三");
}
@Test
public void test2() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(PersonServiceImpl.class);
enhancer.setCallback(new CglibProxy(PersonServiceImpl.class));
PersonServiceImpl personService = (PersonServiceImpl) enhancer.create();
personService.addPerson("张三");
}
}
执行test()
JAVA
begin intercept
添加user信息:张三
printUser...:张三
end intercept
执行test2()
JAVA
begin intercept
添加人的信息:张三
printPerson...:张三
end intercept
- 结果分析
UserService
类和PersonService
实现类都可以成功被Cglib代理,并且方法调用的私有方法不会被再次代理
3.2 JdkProxy
- 编写工具类
JAVA
/**
* JDK动态代理,基于接口实现
*/
public class JdkProxy implements InvocationHandler {
private final Object targetObject;
public JdkProxy(Object targetObject) {
this.targetObject = targetObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before invoke...");
Object invoke = method.invoke(targetObject, args);
System.out.println("after invoke....");
return invoke;
}
}
- 编写测试代码
JAVA
public class JdkProxyTest {
@Test
public void test() {
PersonService personService = (PersonService) Proxy.newProxyInstance(PersonServiceImpl.class.getClassLoader(),
PersonServiceImpl.class.getInterfaces(),
new JdkProxy(new PersonServiceImpl()));
personService.addPerson("张三");
}
/**
* 执行失败
*/
@Test
public void test2() {
UserService userService = (UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(),
UserService.class.getInterfaces(),
new JdkProxy(new UserService()));
userService.addUser("张三");
}
}
执行test()
JAVA
before invoke...
添加人的信息:张三
printPerson...:张三
after invoke....
执行test2()
JAVA
java.lang.ClassCastException: com.sun.proxy.$Proxy4 cannot be cast to org.example.service.UserService
- 结果分析
基于jdk动态代理的方式,只有实现了接口的类才可以被成功代理,没有实现接口的类无法被代理,且被代理的方法调用内部方法时不会被再次代理。
- 分析原因
为什么内部方法不会被代理?
- JDK 动态代理是基于接口的,它生成的代理对象是实现了目标类所实现的接口的。而私有方法是无法在接口中定义的,因此无法被代理。只有通过继承和实现接口的公共方法才能被动态代理。
- Cglib 动态代理是通过继承目标类来创建代理对象的。当目标类的方法被代理时,Cglib 会创建一个子类并重写该方法,从而实现代理的功能。但是,私有方法并不会被继承到子类中,因此无法被重写和代理。
- 总结来说,无论是 JDK 动态代理还是 Cglib 动态代理,私有方法都无法被代理。
把UserService
类里面的printUser()
和PersonServiceImpl
类里面的printPerson()
方法改为public试试
JAVA
public void addUser(String user) {
System.out.println("添加user信息:" + user);
this.printUser(user);
}
public void printUser(String user) {
System.out.println("printUser...:" + user);
}
JAVA
@Override
public void addPerson(String person) {
System.out.println("添加人的信息:" + person);
this.printPerson(person);
}
public void printPerson(String person) {
System.out.println("printPerson...:" + person);
}
再次调用代理,发现只有基于Cglib的代理可以生效,能过再次代理方法内调用的方法,基于JdkProxy的代理无法生效。因为JDK 动态代理的代理对象只会代理通过它进行的方法调用,而不会对目标对象内部的方法调用进行代理。
4. 总结
- Cglib的可以代理没实现接口的类,也可以代理实现接口的类
- JDKProxy只能代理实现了接口的类
- Cglib和JDKProxy都无法代理方法中调用的
private
方法 - Cglib在代理了方法的基础上,可以再次代理方法内调用的
public
方法
写完了个demo,对这两种代理的方式和场景起码是有了初步的认识,虽说在网上刷到过很多次,但确实都不如自己实践一次理解得深刻,更多的理解可能还是需要在工作中碰到才能进一步深挖,纸上得来终觉浅,绝知此事要躬行。
上述代码的链接:gitee.com/szq2021/pro...