JDK的动态代理
这个词大佬们经常挂在嘴边,可我却是云里雾里。
这几个字分开倒是都认识,组合在一起就不明白它是表达的什么意思了talk is cheap, show me the code.
基础实战
main入口类
- 新建一个含有
main
方法的测试类TestMain.java
package com.itplh.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
* JDK动态代理
* Proxy 代理的本质是方法的拦截
*
* $Proxy0 extends Proxy implements Student,我们看到代理类继承了Proxy类,
* 所以也就决定了java动态代理只能对接口进行代理,Java的继承机制注定了这些动态代理类们无法实现对class的动态代理
*
* 因为JDK的动态代理只能基于接口,所以 newProxyInstance 的第二个参数 Class<?>[] interfaces 的元素必须是接口
*
*
* @author: tanpeng
* @since: 2020-06-02 9:57
*/
public class TestMain {
public static void main(String[] args) {
// 使用匿名内部类的方式声明一个Teacher接口的实现类
Student student = () -> "I'm Student interface of implements class.";
// 一、Proxy.newProxyInstance + 匿名内部类
Student studentProxy1 = (Student) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{Student.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
print("Proxy.newProxyInstance + 匿名内部类",
(String) method.invoke(student, args),
method.getAnnotation(TestAnnotation.class).value()[0].toUpperCase());
return null;
}
});
studentProxy1.hi();
// 二、Proxy.newProxyInstance + lambda表达式
Student studentProxy2 = (Student) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{Student.class},
(proxy, method, args1) -> {
print("Proxy.newProxyInstance + lambda表达式.",
(String) method.invoke(student, args1),
method.getAnnotation(TestAnnotation.class).value()[0].toUpperCase());
return null;
});
studentProxy2.hi();
// 三、Proxy.newProxyInstance + InvocationHandler实现类
Student studentProxy3 = (Student) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{Student.class},
new TestInvocationHandler<Student>(student));
studentProxy3.hi();
// 四、封装ProxyFactory 代理Student接口
Student studentProxy4 = TestProxyFactory.getProxyObject(student);
studentProxy4.hi();
// 五、封装ProxyFactory 代理Teacher接口
Teacher teacher = () -> "I'm teacher.";
Teacher teacherProxy = TestProxyFactory.getProxyObject(teacher);
teacherProxy.hi();
// 这里传实现类 ZhangSan.class 将会报错
// Exception in thread "main" java.lang.IllegalArgumentException: com.itplh.proxy.ZhangSan is not an interface
// Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
// new Class[]{ZhangSan.class},
// new TestInvocationHandler<Student>(student));
}
public static void print(String... text) {
System.out.println("------------------------------------------");
Arrays.asList(text).forEach(t -> System.out.println("------ " + t));
System.out.println("------------------------------------------");
System.out.println();
}
}
interface Student {
@TestAnnotation("select * from student where id = #{id}")
String hi();
}
class ZhangSan implements Student {
@Override
public String hi() {
return "I'm zhangsan.";
}
}
interface Teacher {
@TestAnnotation("select * from teacher where id = #{id}")
String hi();
}
- 控制台输出
------------------------------------------
------ Proxy.newProxyInstance + 匿名内部类
------ I'm Student interface of implements class.
------ SELECT * FROM STUDENT WHERE ID = #{ID}
------------------------------------------
------------------------------------------
------ Proxy.newProxyInstance + lambda表达式.
------ I'm Student interface of implements class.
------ SELECT * FROM STUDENT WHERE ID = #{ID}
------------------------------------------
------------------------------------------
------ InvocationHandler of implements class.
------ I'm Student interface of implements class.
------ SELECT * FROM STUDENT WHERE ID = #{ID}
------------------------------------------
------------------------------------------
------ ProxyFactory com.itplh.proxy.Student.
------ I'm Student interface of implements class.
------ SELECT * FROM STUDENT WHERE ID = #{ID}
------------------------------------------
------------------------------------------
------ ProxyFactory com.itplh.proxy.Teacher.
------ I'm Teacher interface of implements class.
------ SELECT * FROM TEACHER WHERE ID = #{ID}
------------------------------------------
其他类
- 自定义一个注解
TestAnnotation.java
package com.itplh.proxy;
import java.lang.annotation.*;
/**
* @author: tanpeng
* @since: 2020-06-02 10:09
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface TestAnnotation {
String[] value();
}
- 自定义
TestInvocationHandler.java
package com.itplh.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 动态代理-拦截器
* 作用:控制目标对象的目标方法的执行
*
* @author: tanpeng
* @since: 2020-06-02 9:57
*/
public class TestInvocationHandler<T> implements InvocationHandler {
// InvocationHandler持有的被代理对象
private T target;
public TestInvocationHandler(T target) {
this.target = target;
}
/**
* 调用被代理的对象的方法都会进入本方法
*
* @param proxy 代表动态代理对象
* @param method 代表正在执行的方法
* @param args 代表调用目标方法时传入的实参
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
TestMain.print("InvocationHandler of implements class.",
(String) method.invoke(target, args),
method.getAnnotation(TestAnnotation.class).value()[0].toUpperCase());
return null;
}
}
- 封装一个代理工厂
TestProxyFactory.java
package com.itplh.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* @author: tanpeng
* @since: 2020-06-02 11:08
*/
public class TestProxyFactory {
/**
* static <T> 表示将该方法声明为泛型方法
*
* @param target 目标代理对象
* @return
*/
public static <T> T getProxyObject(T target) {
return getProxyObject(target, (proxy, method, args) -> {
TestMain.print("ProxyFactory " + target.getClass().getInterfaces()[0].getName() + ".",
(String) method.invoke(target, args),
method.getAnnotation(TestAnnotation.class).value()[0].toUpperCase());
return null;
});
}
/**
* static<T> 表示将该方法声明为泛型方法
*
* @param target 目标代理对象
* @param invocationHandler
* @return
*/
public static <T> T getProxyObject(T target, InvocationHandler invocationHandler) {
return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
target.getClass().getInterfaces(),
invocationHandler);
}
}
测试含有多个方法的接口
- 新建接口
People 、User
,及实现类DefaultUser
interface People {
@TestAnnotation("String hi();")
String hi();
}
interface User extends People {
@TestAnnotation("select username from user where id = #{id}")
String selectUsernameById(String id);
@TestAnnotation("select email from user where id = #{id}")
String selectEmailById(String id);
}
class DefaultUser implements User {
@Override
public String hi() {
return "This is DefaultUser#hi.";
}
@Override
public String selectUsernameById(String id) {
return "tanpeng";
}
@Override
public String selectEmailById(String id) {
return "tanpeng@itplh.com";
}
}
- main 函数中测试
DefaultUser user = new DefaultUser();
User userProxy = TestProxyFactory.getProxyObject(user);
userProxy.hi();
userProxy.selectUsernameById("1");
userProxy.selectEmailById("1");
People peopleProxy = TestProxyFactory.getProxyObject(user);
peopleProxy.hi();
((User) peopleProxy).selectUsernameById("1");
((User) peopleProxy).selectEmailById("1");
- 控制台输出
------------------------------------------
------ ProxyFactory com.aden.modules.datax.controller.User.
------ This is DefaultUser#hi.
------ STRING HI();
------------------------------------------
------------------------------------------
------ ProxyFactory com.aden.modules.datax.controller.User.
------ tanpeng
------ SELECT USERNAME FROM USER WHERE ID = #{ID}
------------------------------------------
------------------------------------------
------ ProxyFactory com.aden.modules.datax.controller.User.
------ tanpeng@itplh.com
------ SELECT EMAIL FROM USER WHERE ID = #{ID}
------------------------------------------
------------------------------------------
------ ProxyFactory com.aden.modules.datax.controller.User.
------ This is DefaultUser#hi.
------ STRING HI();
------------------------------------------
------------------------------------------
------ ProxyFactory com.aden.modules.datax.controller.User.
------ tanpeng
------ SELECT USERNAME FROM USER WHERE ID = #{ID}
------------------------------------------
------------------------------------------
------ ProxyFactory com.aden.modules.datax.controller.User.
------ tanpeng@itplh.com
------ SELECT EMAIL FROM USER WHERE ID = #{ID}
------------------------------------------
应用场景
-
通过代理对类进行增强
设计模式中有一个设计原则是开闭原则,是说对修改关闭对扩展开放。
我们在工作中有时会接手很多前人的代码,里面代码逻辑让人摸不着头脑(sometimes the code is really like shit)。
这时就很难去下手修改代码,那么这时我们就可以通过代理对类进行增强。 AOP
-
松散耦合
在使用RPC框架的时候,框架本身并不能提前知道各个业务方要调用哪些接口的哪些方法 。
那么这个时候,就可用通过动态代理的方式来建立一个中间人给客户端使用,
也方便框架进行搭建逻辑,某种程度上也是客户端代码和框架松耦合的一种表现。
知识拓展
- 静态代理
在代码编译时就确定了被代理的类是哪一个 - 动态代理
在代码运行期间才加载被代理的类,编译时并未确定被代理的类是哪一个 静态代理
和动态代理
的区别
代理涉及到两个关联词代理类
和委托类
静态代理 一个代理类针对一个委托类,即一对一
动态代理 一个代理类可利用反射机制代理多个委托类,即一对多
- 两种动态代理的区别
JDK的动态代理
基于接口的代理
- 代理对象和目标对象实现了共同的
接口
- 拦截器必须实现
InvocationHanlder
接口
cglib的动态代理
基于类的代理 (Code Generation Library )
- 代理对象是目标对象的
子类
- 拦截器必须实现
MethodInterceptor
接口
- 动态代理的相关问题
- 代理对象是由谁产生的
JVM产生的,不像静态代理,我们自己得new个代理对象出来。 - 代理对象实现了什么接口
实现的接口是目标对象实现的接口。同静态代理中代理对象实现的接口。那个继承关系图还是相同的。代理对象
和目标对象
都实现一个共同的接口。所以Proxy.newProxyInstance()
方法返回的类型就是这个接口类型。 - 代理对象的方法体是什么
代理对象的方法体中的内容就是拦截器中invoke
方法中的内容。
所有代理对象的处理逻辑,控制是否执行目标对象的目标方法。都是在invoke
方法里面处理的。 - 拦截器中的
invoke
方法中的method
参数是在什么时候赋值的
在代理对象
调用目标方法
的时候。
如,studentProxy1.hi();
、userProxy.selectUsernameById("1");
这时实际上进入的是拦截器中的invoke
方法,这个时候拦截器中的invoke
方法中的method
参数会被赋值。
动态代理对象时用JDK的相关代码生成的,叫JDK动态代理
动态代理对象时用cglib
的 jar 包,叫cglib动态代理
参考