SpringBoot27 JDK动态代理详解、获取指定的类类型、动态注册Bean、接口调用框架
1 JDK动态代理详解
静态代理、JDK动态代理、Cglib动态代理的简单实现方式和区别请参见我的另外一篇博文。
1.1 JDK代理的基本步骤
》通过实现InvocationHandler接口来自定义自己的InvocationHandler;
1.2 JDK动态代理的应用场景
重要扫盲知识点:利用JDK动态代理获取到的动态代理实例的类型默认是Object类型,如果需要进行类型转化必须转化成目标类的接口类型,因为JDK动态代理是利用目标类的接口实现的
前提准备:创建一个SpringBoot项目并引入相关依赖
<?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> <groupId>cn.xiangxu.com</groupId> <artifactId>webclient_rest_client</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>webclient_rest_client</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
pom.xml
1.2.1 在原有业务执行前后执行一些逻辑操作
》需求:已知一个实例student,该实例有一个studentInfo方法;现在需要在通过student实例调用studentInfo方法的前后插入一些代码
》思路:创建一个实现了 InvocationHandler 接口的代理类处理类 MyInvocationHandler -> 在 MyInvocationHandler 中创建一个 createProxyInstance 方法用来创建动态代理类的实例,需要一个接收目标类实例的成员变量target -> 在重写的invoke方法中 通过成员变量target去执行studentInfo方法并在执行前后增加其他的业务逻辑
技巧01:MyInvocationHandler 中需要有一个成员变量用来接收目标类实例,因为在MyInvocationHandler 中的invoke方法中需要用到目标类的实例来执行原来的逻辑
技巧02:createProxyInstance 返回动态代理类的实例
》按照实录编写的源代码
/** * 学生接口 */ interface IStudent { Object studentInfo(); }
IStudent.java
/** * 学生接口实现类 */ @Slf4j class Student implements IStudent { @Override public Object studentInfo() { log.info("学生接口实现类"); return "学生接口实现类"; } }
Student.java
package cn.xiangxu.com.webclient_rest_client.proxy; import lombok.extern.slf4j.Slf4j; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @author 王杨帅 * @create 2018-08-23 15:30 * @desc 动态代理类的处理类 **/ @Slf4j public class MyInvocationHandler implements InvocationHandler { private Object target; /** * 创建代理类实例 * @param target * @return */ public Object createProxyInstance (Object target) { this.target = target; // 接收目标类实例 // 创建并返回代理类实例 return Proxy.newProxyInstance( this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), this ); } /** * 代理执行方法:利用代理类实例执行目标类的方法都会进入到invoke方法中执行 * @param proxy * @param method * @param args * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { log.info("前置逻辑"); Object result = method.invoke(target, args); // 利用目标类实例执行原有的逻辑 log.info("后置逻辑"); return result; } }
MyInvocationHandler.java
package cn.xiangxu.com.webclient_rest_client.proxy; import lombok.extern.slf4j.Slf4j; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @author 王杨帅 * @create 2018-08-23 14:47 * @desc **/ @Slf4j public class SimpleJdkProxy { public static void main(String[] args) { Student student = new Student(); IStudent proxyInstance = (IStudent) new MyInvocationHandler() .createProxyInstance(student); Object result = proxyInstance.studentInfo(); System.out.println(result); } } /** * 学生接口 */ interface IStudent { Object studentInfo(); } /** * 学生接口实现类 */ @Slf4j class Student implements IStudent { @Override public Object studentInfo() { log.info("学生接口实现类"); return "学生接口实现类"; } }
源代码
1.2.2 替换原有的所有业务
》需求:已知一个实例student,该实例有一个studentInfo方法;现在需要在通过student实例调用studentInfo方法时不执行原有的逻辑,而是执行一些其他的逻辑,而且需要返回studentInfo方法指定的数据类型
》思路:创建一个实现了 InvocationHandler 接口的代理类处理类 MyInvocationHandler -> 在 MyInvocationHandler 中创建一个 createProxyInstance 方法用来创建动态代理类的实例 -> 在重写的invoke方法中不需要执行studentInfo方法,而是直接执行其他的一些逻辑
》按照思路进行编写的源代码
/** * 学生接口 */ interface IStudent { Object studentInfo(); }
IStudent.java
/** * 学生接口实现类 */ @Slf4j class Student implements IStudent { @Override public Object studentInfo() { log.info("学生接口实现类"); return "学生接口实现类"; } }
Student.java
package cn.xiangxu.com.webclient_rest_client.proxy; import lombok.extern.slf4j.Slf4j; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * @author 王杨帅 * @create 2018-08-23 15:30 * @desc 动态代理类的处理类 **/ @Slf4j public class MyInvocationHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { log.info("不执行原来的逻辑,执行一些其他的逻辑"); return "利用动态代理对象执行后的返回结果"; } }
MyInvocationHandler.java
package cn.xiangxu.com.webclient_rest_client.proxy; import lombok.extern.slf4j.Slf4j; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @author 王杨帅 * @create 2018-08-23 14:47 * @desc **/ @Slf4j public class SimpleJdkProxy { public static void main(String[] args) { Student student = new Student(); IStudent proxyInstance = (IStudent) Proxy.newProxyInstance( student.getClass().getClassLoader(), student.getClass().getInterfaces(), new MyInvocationHandler() ); Object result = proxyInstance.studentInfo(); System.out.println(result); } } /** * 学生接口 */ interface IStudent { Object studentInfo(); } /** * 学生接口实现类 */ @Slf4j class Student implements IStudent { @Override public Object studentInfo() { log.info("学生接口实现类"); return "学生接口实现类"; } }
源代码
》简便写法
技巧01:由于不需要执行原来的逻辑,所以在 MyInvocationHandler 类中的 invoke方法就不需要目标类的实例,所以就不需要单独创建一个实现了 InvocationHandler 接口的实现类,直接通过匿名内部类实现就可以啦。
/** * 学生接口 */ interface IStudent { Object studentInfo(); }
IStudent.java
/** * 学生接口实现类 */ @Slf4j class Student implements IStudent { @Override public Object studentInfo() { log.info("学生接口实现类"); return "学生接口实现类"; } }
Student.java
package cn.xiangxu.com.webclient_rest_client.proxy; import lombok.extern.slf4j.Slf4j; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @author 王杨帅 * @create 2018-08-23 14:47 * @desc **/ @Slf4j public class SimpleJdkProxy { public static void main(String[] args) { Student student = new Student(); IStudent proxyInstance = (IStudent) Proxy.newProxyInstance( student.getClass().getClassLoader(), student.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { log.info("根据方法信息调用一些逻辑"); return "代理类调用返回结果"; } } ); Object result = proxyInstance.studentInfo(); System.out.println(result); } } /** * 学生接口 */ interface IStudent { Object studentInfo(); } /** * 学生接口实现类 */ @Slf4j class Student implements IStudent { @Override public Object studentInfo() { log.info("学生接口实现类"); return "学生接口实现类"; } }
源代码
1.2.3 为某些接口创建代理类并将代理类注册到IOC容器中
》说明:需要创建代理类的接口都标注有@RestApi注解;创建的代理类需要执行额外的逻辑,比如:远程Restrul接口调用(不用实现原有逻辑,因为接口中的方法是有声明,默认方法除外)、将创建的Bean注入到IOC容器中【PS: 注入到IOC的Bean必须支持AOP】
》所需知识点:扫描指定目录下的类类型、获取指定类型的类类型、如何实现远程调用Restful接口、动态注入Bean
》注意:本知识点所需知识点比较多,请先阅读下面的所需知识点
2 自定义JDK动态代理工具类
前面的知识点都是利用了Proxy类去直接创建动态代理类的实例,本小节将按照JDK动态代理的5大步骤去自定义一个JDK动态代理工具类
2.1 功能说明
该工具类可以根据 接口数组和代理类处理器 创建出 代理类的类类型和代理类实例
技巧01:利用 java.lang.reflect.Proxy的getProxyClass方法可以创建代理类的类类型,该方法接收一个类加载器和一个接口数组
技巧02:利用java.lang.Class的getConstructor方法可以获取到代理类的构造方法,该方法接收一个Class类型的数组;由于这里是针对动态代理类而言,所以动态代理类的构造器的参数都是一些处理器,所以在这里只需要传入InvocationHandler实现类的实例就可以啦;由于getConstructor方法的定义使用了解构,所以可以不传参数或者之传入一个参数,本案例传入的是InovcationHandler的类类型
技巧03:获取到代理类的Constructor后就可以通过newInstance方法创建代理类的实例了
2.2 设计思路
2.2.1 创建一个实现了 InvocationHandler 接口的代理类处理器 MyInvocationHandler
技巧01:MyInvocationHandler 中的invoke方法不执行原来的业务逻辑,只执行新的业务逻辑
package cn.xiangxu.com.webclient_rest_client.proxy; import lombok.extern.slf4j.Slf4j; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @author 王杨帅 * @create 2018-08-23 15:30 * @desc 动态代理类的处理类 **/ @Slf4j public class MyInvocationHandler implements InvocationHandler { /** * 代理执行方法:利用代理类实例执行目标类的方法都会进入到invoke方法中执行 * @param proxy * @param method * @param args * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { log.info("前置逻辑"); log.info("可以根据method参数和args参数获取一些信息,再根据这些信息去执行新的业务逻辑"); log.info("后置逻辑"); return "动态代理返回的结果"; } }
MyInvocationHandler.java
2.2.2 创建一个代理类创建工具接口ProxyCreator,该接口中定义了两个方法:
createProxyInstanceInfo -> 创建代理类实例
getProxyClassInfo -> 获取代理类的类类型
技巧01:之所以需要创建一个接口的原因是为了以后可以扩展为基于Cglib实现动态代理【PS: 本案例基于JDK动态代理】
package cn.xiangxu.com.webclient_rest_client.proxy; import java.lang.reflect.InvocationTargetException; /** * @author 王杨帅 * @create 2018-08-23 16:35 * @desc 代理工具接口 **/ public interface ProxyCreator { /** * 获取代理类实例 * @return * @throws IllegalAccessException * @throws InvocationTargetException * @throws InstantiationException */ Object createProxyInstanceInfo() throws IllegalAccessException, InvocationTargetException, InstantiationException; /** * 获取代理类的类类型 * @return */ Class<?> getProxyClassInfo(); }
ProxyCreator.java
2.2.3 创建JDK动态代理工具类JdkProxyCreator
package cn.xiangxu.com.webclient_rest_client.proxy; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Proxy; /** * @author 王杨帅 * @create 2018-08-23 16:37 * @desc JDK代理工具类 **/ @Data public class JdkProxyCreator implements ProxyCreator { /** * 需要产生代理类的接口数组 */ private Class<?>[] interfaces; /** * 根据interfaces创建的动态代理类的类类型 */ private Class<?> proxyClass; /** * 根据interfaces创建的动态代理类的构造器 */ private Constructor<?> proxyConstructor; /** * 根据interfaces创建的动态代理类所需的处理器 */ private InvocationHandler invocationHandler; /** * 自定义构造器: * 根据获取到处理器实例创建JDK代理类的类类型 * 并通过JDK代理类的类类型获取代理类的构造器 * @param interfaces 接口数组 * @param invocationHandler 代理类的处理器 * @throws NoSuchMethodException */ public JdkProxyCreator( Class<?>[] interfaces, InvocationHandler invocationHandler ) throws NoSuchMethodException { this.interfaces = interfaces; this.invocationHandler = invocationHandler; // 创建代理类的类类型 this.proxyClass = Proxy.getProxyClass( this.getClass().getClassLoader(), this.interfaces ); // 根据代理类的类类型获取代理类的构造器 this.proxyConstructor = this.proxyClass .getConstructor(InvocationHandler.class); } @Override public Object createProxyInstanceInfo() throws IllegalAccessException, InvocationTargetException, InstantiationException { return this.proxyConstructor.newInstance(this.invocationHandler); } @Override public Class<?> getProxyClassInfo() { return this.proxyClass; } }
JdkProxyCreator.java
技巧01:JdkProxyCreator 中定义了几个成员变量
》JdkProxyCreator中创建一个有参构造器
技巧01:该构造器接收的参数为
interfaces -> 需要产生代理类的接口数组
invocationHandler -> 代理类的处理器
技巧02:通过接收到的参数创建动态代理的类类型并对成员变量proxyClass完成初始化
技巧0201:利用java.lang.reflect.Proxy的getProxyClass方法可以根据类加载器和接口数组创建代理对象的类类型
技巧03:根据已经初始化的proxyClass获取代理类的构造器并对成员变量proxyConstructor完成初始化
》JdkProxyCreator中创建代理类实例
在createProxyInstanceInfo中利用已经初始化的proxyConstructor创建代理类实例
技巧01:Constuctor实例可以利用 newInstance 方法创建实例
》JdkProxyCreator中返回代理类的类类型
在 getProxyClassInfo 中直接返回已经初始化的 proxyClass 成员变量即可
2.3 测试
2.3.1 需求
现有一个接口IStudent,该接口中有一个方法,但是这个接口没有实现类;现在要求给这个接口创建一个实现类的实例,并用这个实例去调用studentInfo
技巧01:由于是给接口创建代理类,所以所有的业务逻辑都是在代理类的处理器的invoke方法中进行定义的
2.3.2 思路
》利用创建好的MyInvocationHandler传进一个动态代理处理类实例invocationHandler
利用封装好的JdkProxyCreator创建一个JDK动态代理创建工具实例jdkProxyCreator
》利用jdkProxyCreator的createProxyInstanceInfo方法创建动态代理类实例proxyInstance
》再利用动态代理类实例proxyInstance去调用studentInfo方法
2.3.3 代码汇总
package cn.xiangxu.com.webclient_rest_client.proxy; import lombok.extern.slf4j.Slf4j; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @author 王杨帅 * @create 2018-08-23 14:47 * @desc **/ @Slf4j public class SimpleJdkProxy { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException { // 01 创建代理类的处理器实例 MyInvocationHandler invocationHandler = new MyInvocationHandler(); // 02 实例化JDK动态代理创建工具 JdkProxyCreator jdkProxyCreator = new JdkProxyCreator(new Class<?>[]{IStudent.class}, invocationHandler); // 03 利用JDK动态代理创建工具创建代理类实例 IStudent proxyInstance = (IStudent) jdkProxyCreator.createProxyInstanceInfo(); // 04 利用代理类实例调用接口方法 Object o = proxyInstance.studentInfo(); // 05 打印代理类的处理器类中invoke方法返回的数据 System.out.println(o); } } /** * 学生接口 */ interface IStudent { Object studentInfo(); }
测试代码汇总
3 获取类类型
3.1 需求
获取一个指定包下所有类的类类型
3.2 思路
利用反射实现,org.reflections依赖可以实现扫描指定包下所有类的类类型
3.3 reflections使用
3.3.1 引入reflections依赖
<!-- https://mvnrepository.com/artifact/org.reflections/reflections --> <dependency> <groupId>org.reflections</groupId> <artifactId>reflections</artifactId> <version>0.9.11</version> </dependency>
3.3.2 reflections可以实现的功能
》获取某个类的所有子类的类类型
》获取某个接口的所有实现类的类类型
》获取标注有某个注解的类的类类型
……
3.3.3 使用案例
》获取IStudent所有实现类的类类型
package cn.xiangxu.com.webclient_rest_client.proxy; import lombok.extern.slf4j.Slf4j; import org.reflections.Reflections; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Set; /** * @author 王杨帅 * @create 2018-08-23 14:47 * @desc **/ @Slf4j public class SimpleJdkProxy { public static void main(String[] args) { // 01 获取指定包下的所有类类型 Reflections reflections = new Reflections( "cn.xiangxu.com.webclient_rest_client.proxy" ); // 02 获取子类的类类型 Set<Class<? extends IStudent>> subTypesOf = reflections .getSubTypesOf(IStudent.class); // 03 遍历输出 for (Class clss : subTypesOf) { System.out.println(clss.getSimpleName()); } } } /** * 学生接口 */ interface IStudent { Object studentInfo(); } /** * IStudent实现类01 */ class Student01 implements IStudent { @Override public Object studentInfo() { return null; } } /** * IStudent实现类02 */ class Student02 implements IStudent { @Override public Object studentInfo() { return null; } }
测试代码汇总
》获取标注了@RestApi注解的类的类类型
package cn.xiangxu.com.webclient_rest_client.proxy; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface RestApi { String value() default ""; }
RestApi.java
package cn.xiangxu.com.webclient_rest_client.proxy; import lombok.extern.slf4j.Slf4j; import org.reflections.Reflections; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Set; /** * @author 王杨帅 * @create 2018-08-23 14:47 * @desc **/ @Slf4j public class SimpleJdkProxy { public static void main(String[] args) { // 01 获取指定包下的所有类类型 Reflections reflections = new Reflections( "cn.xiangxu.com.webclient_rest_client.proxy" ); // 02 获取标注了@RestApi的类的类类型 Set<Class<?>> typesAnnotatedWith = reflections .getTypesAnnotatedWith(RestApi.class); // 03 遍历输出 for (Class clss : typesAnnotatedWith) { System.out.println(clss.getSimpleName()); } } } /** * 学生接口 */ interface IStudent { Object studentInfo(); } /** * IStudent实现类01 */ @RestApi(value = "http://127.0.0.1:8080/dev") class Student22 implements IStudent { @Override public Object studentInfo() { return null; } } /** * IStudent实现类02 */ @RestApi(value = "http://127.0.0.1:8080/stu") class Student implements IStudent { @Override public Object studentInfo() { return null; } }
测试类代码
4 为添加了指定注解的接口创建代理对象
4.1 需求
为那些标注有@RestApi注解的所有接口创建代理类实例
4.2 思路
》扫描指定包获取所有的类类型
》筛选出标注了@RestApi注解的类类型
》创建自定义的JDK动态代理所需的处理器实例
》创建自定义的JDK动态代理工具类实例
》利用JDK动态代理工具类实例创建动态代理类实例
》利用JDK动态代理类实例调用目标接口的方法
4.3 代码实现
说明:自定义的处理器类和JDK动态代理工具类请参见第二小节内容(2.2)
package cn.xiangxu.com.webclient_rest_client.proxy; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface RestApi { String value() default ""; }
RestApi.java
package cn.xiangxu.com.webclient_rest_client.proxy; import lombok.extern.slf4j.Slf4j; import org.reflections.Reflections; import org.springframework.util.CollectionUtils; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * @author 王杨帅 * @create 2018-08-23 14:47 * @desc **/ @Slf4j public class SimpleJdkProxy { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException { // 01 获取指定包下的所有类类型 Reflections reflections = new Reflections("cn.xiangxu.com.webclient_rest_client.proxy"); // 02 获取标注了@RestApi注解的类的类型 Set<Class<?>> typesAnnotatedWith = reflections.getTypesAnnotatedWith(RestApi.class); List<Class> classList = new ArrayList<>(typesAnnotatedWith); // 获取其中一个类类型 Class aClass = classList.get(0); // 03 创建JDK动态代理类所需的处理类实例 MyInvocationHandler myInvocationHandler = new MyInvocationHandler(); // 04 创建JDK动态代理工具类实例 JdkProxyCreator jdkProxyCreator = new JdkProxyCreator(new Class[]{aClass}, myInvocationHandler); // 05 创建动态代理类实例 IStudent proxyInstanceInfo = (IStudent) jdkProxyCreator.createProxyInstanceInfo(); // 06 利用代理类实例调用目标接口方法 proxyInstanceInfo.studentInfo(); } } /** * 学生接口 */ @RestApi(value = "http://127.0.0.1:8080/dev") interface IStudent { Object studentInfo(); }
测试类代码
5 动态注册Bean
待更新……
6 为添加了指定注解的接口创建代理对象并注入到IOC容器中
待更新……
7 基于WebClient的响应式接口调用框架
待更新……