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>
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(); }
/** * 學生接口實現類 */ @Slf4j class Student implements IStudent { @Override public Object studentInfo() { log.info("學生接口實現類"); return "學生接口實現類"; } }
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; } }

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(); }
/** * 學生接口實現類 */ @Slf4j class Student implements IStudent { @Override public Object studentInfo() { log.info("學生接口實現類"); return "學生接口實現類"; } }
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 "利用動態代理對象執行后的返回結果"; } }

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(); }
/** * 學生接口實現類 */ @Slf4j class Student implements IStudent { @Override public Object studentInfo() { log.info("學生接口實現類"); return "學生接口實現類"; } }

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 "動態代理返回的結果"; } }
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(); }
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; } }
技巧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 ""; }

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 ""; }
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
5.1 動態注冊Bean應用場景
在spring的XML配置文件中通過<bean>標簽注冊的Bean、利用@Bean、@Controller、@Component注冊的Bean都是靜態的方式注冊Bean,在大部分的開發中通過這些靜態配置的方式注冊Bean就可以啦;但是在某些場合下,需要根據一些靜態信息動態向IOC容器中動態注冊Bean,此時之前的方法就會失效了。
5.1.1 需求
例如:下面有一個IStudent接口,該接口中有若干個方法,但是該接口沒有實現類;現在要求根據接口信息動態為該接口創建一個實現類實例並注冊到bean中,在要用的地方依賴注入已經注冊的Bean,然后調用相應的方法就可以實現遠程服務資源的調用。

5.1.2 思路
利用動態代理為標注了@RestApi的接口創建代理類並將其動態注冊到IOC容器中就可以啦
5.2 動態注冊Bean知識點掃盲
5.2.1 Spring中可以進行動態注冊Bean的API
BeanDefinitionRegistry:提供了向容器手工注冊BeanDefinition對象的方法,在Spring的XML配置文件中每一個<bean>標簽都對應一個BeanDefinition對象,最后這些BeanDefinition對象都會通過BeanDefinitionRegistry被注冊到IOC容器中。
SingletonBeanRegistry:提供了向容器中注冊單例Bean的方法。
DefaultListableBeanFactory:同時實現了這兩個接口,在實踐中通常會使用這個接口。
5.2.2 不同地方動態注冊Bean的區別
可以在任何可以獲得BeanDefinitionRegistry、BeanDefinitionRegistry、BeanDefinitionRegistry中的一個實例的地方進行動態注冊Bean,但是,如果Bean不是在BeanFactoryPostProcessor中被注冊的話就不會被BeanPostProcessor處理,即:無法對其應用aop、Bean Validation等功能。
技巧01:在Spring容器的啟動過程中,BeanFactory載入bean的定義后會立刻執行BeanFactoryPostProcessor,此時動態注冊bean,則可以保證動態注冊的bean被BeanPostProcessor處理,並且可以保證其的實例化和初始化總是先於依賴它的bean。
技巧02:在BeanFactoryPostProcessor中注冊Bean指的是在實現了BeanFactoryPostProcessor接口后,在實現類的重寫方法中進行Bean的注冊,例如

技巧03:實現了BeanFactoryPostProcessor接口的實現類上標注@Component注解可以保證容器啟動時就進行動態Bean的創建,因為@Component注解會在Spring容器啟動時把對應的類創建一個Bean注冊到Spring容器,所以在標注了@Component注解並且實現了BeanFactoryPostProcessor接口的類中動態注冊Bean是最好的方式。
5.3 動態創建Bean工具類編寫
5.3.1 思路
創建工具類DynamicRegisterBeanUtil並實現BeanFactoryPostProcessor -> 重寫postProcessBeanFactory 方法 -> 在postProcessBeanFactory方法中實現動態Bean的注冊 -> 利用@Component注解實現DynamicRegisterBeanUtil被IOC容器管理
5.3.2 在postProcessBeanFactory方法中動態創建Bean詳解
說明:postProcessBeanFactory是 BeanFactoryPostProcessor 接口中定義的,所以在DynamicRegisterBeanUtil必須實現postProcessBeanFactory方法
》postProcessBeanFactory 有一個 ConfigurableListableBeanFactory 類型的參數,可以利用這個參數來實現動態注冊Bean
技巧01:由於進行動態注冊Bean的API只有BeanDefinitionRegistry、SingletonBeanRegistry、DefaultListableBeanFactory,所以我們先將ConfigurableListableBeanFactory 的參數轉化成DefaultListableBeanFactory類型的參數並賦值給DynamicRegisterBeanUtil的成員變量defaultListableBeanFactory。
》獲取所有標注了@RestApi注解的類類型
技巧01:需要用到org.reflections相關的東西,所以必須先引入reflections的相關依賴
技巧02:獲取標注了@RestApi注解的類類型分為兩個步驟,首先需要掃描一個指定的路徑得到該路徑下所有的類類型,然后從獲取到的所有類類型中篩選出標注了@RestApi注解的類類型
》將獲取到的標注了@RestApi注解的類類型注冊到IOC容器中
技巧01:創建一個自定義的動態代理所需的處理器實例 handler
技巧02:根據類類型和處理器 handler 去創建類類型對應的動態代理類的Bean定義 proxyBeanDefinition
技巧03:利用defaultListableBeanFactory根據類類型和該類類型的的動態代理類的Bean定義進行Bean的動態注冊
5.3.3 工具類源代碼
說明:需要用到上面提到的處理器類和JDK動態代理工具類
案例:為添加了指定注解的接口創建代理對象並注入到IOC容器中
》自定義處理器類
package cn.xiangxu.com.webclient_rest_client.proxy; import lombok.extern.slf4j.Slf4j; import reactor.core.publisher.Flux; 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("前置邏輯"); System.out.println(method.getName()); log.info("可以根據method參數和args參數獲取一些信息,再根據這些信息去執行新的業務邏輯"); log.info("后置邏輯"); return Flux.just("動態代理返回的結果"); } }
》自定義代理類接口以及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(); }
package cn.xiangxu.com.webclient_rest_client.proxy; import lombok.*; 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代理工具類 **/ @Getter 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; } }
》自定義@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注解的接口
package cn.xiangxu.com.webclient_rest_client.rest_server_api; import cn.xiangxu.com.webclient_rest_client.proxy.RestApi; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; /** * @author 王楊帥 * @create 2018-08-23 22:18 * @desc **/ @RestApi(value = "http://127.0.0.1:8080/stu") public interface IStudentApi { @GetMapping(value = "/") Flux<Object> listStudent(); @GetMapping(value = "/{id}") Mono<Object> getStudent( @PathVariable(value = "id") String studentId ); }
》自定義動態注冊Bean工具類
package cn.xiangxu.com.webclient_rest_client.proxy; import org.reflections.Reflections; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.stereotype.Component; import java.util.Set; /** * @author 王楊帥 * @create 2018-08-23 22:50 * @desc 動態注冊Bean的工具類 **/ @Component public class DynamicRegisterBeanUtil implements BeanFactoryPostProcessor { /** * 動態注冊Bean所需對象 */ private DefaultListableBeanFactory defaultListableBeanFactory; @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { this.defaultListableBeanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory; // 01 獲取指定路徑下的所有類類型 Reflections reflections = new Reflections("cn.xiangxu.com.webclient_rest_client.rest_server_api"); // 02 獲取標注了@Rest注解的類類型 Set<Class<?>> typesAnnotatedWithRestApi = reflections.getTypesAnnotatedWith(RestApi.class); // 03 為標注了@RestApi 注解的所有類類型創建代理類並將該代理類注冊到IOC容器中去 for (Class cls : typesAnnotatedWithRestApi) { System.out.println(cls.getSimpleName()); // 創建代理類的BeanDefinition並將其注冊到IOC容器中去 try { createProxyClass(cls); } catch (NoSuchMethodException e) { e.printStackTrace(); } } } /** * 創建代理類並將代理類注冊到IOC容器中去 * @param cls 需要創建代理類的的類類型 * @throws NoSuchMethodException */ private void createProxyClass(Class cls) throws NoSuchMethodException { // 一、創建代理類對象 MyInvocationHandler handler = getMyInvocationHandler(); // 二、獲取代理類的Bean定義 BeanDefinition proxyBeanDefinition = getProxyBeanDefinition(cls, handler); // 三、將代理類的Bean信息注冊到Spring容器中 registerBeanDefinition(cls, proxyBeanDefinition); } /** * 將代理類的Bean信息注冊到Spring容器中:Bean名稱為接口名 * @param cls * @param proxyBeanDefinition */ private void registerBeanDefinition(Class cls, BeanDefinition proxyBeanDefinition) { this.defaultListableBeanFactory.registerBeanDefinition(cls.getSimpleName(), proxyBeanDefinition); } /** * 獲取JDK代理類的Bean定義:根據目標接口的類型和處理器實例獲取目標類接口的JDK代理類的Bean定義 * @param cls * @param handler * @return * @throws NoSuchMethodException */ private BeanDefinition getProxyBeanDefinition(Class cls, MyInvocationHandler handler) throws NoSuchMethodException { // 獲取JDK代理類的類型 Class<?> jdkDynamicProxyClass = getJDKDynamicProxyClass(cls, handler); // 獲取JDK代理類的Bean定義 BeanDefinition jdkBeanDefinition = getJDKBeanDefinition(jdkDynamicProxyClass, handler); return jdkBeanDefinition; } /** * 獲取JDK代理類的Bean定義:根據JDK代理類的類型和處理類實例創建JDK代理類的Bean定義 * @param jdkDynamicProxyClass * @param handler */ private BeanDefinition getJDKBeanDefinition(Class<?> jdkDynamicProxyClass, MyInvocationHandler handler) { AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder .genericBeanDefinition(jdkDynamicProxyClass) .addConstructorArgValue(handler) .getBeanDefinition(); beanDefinition.setAutowireCandidate(true); return beanDefinition; } /** * 獲取JDK代理類的類類型 * @param cls * @param handler */ private Class<?> getJDKDynamicProxyClass(Class cls, MyInvocationHandler handler) throws NoSuchMethodException { JdkProxyCreator jdkProxyCreator = new JdkProxyCreator(new Class[]{cls}, handler); return jdkProxyCreator.getProxyClassInfo(); } /** * 獲取創建代理時所需的處理類 * @return */ private MyInvocationHandler getMyInvocationHandler() { return new MyInvocationHandler(); } }
》測試動態注冊Bean是否生效
依賴注入IStudentApi,如果可以注入成功就表示注冊成功啦

package cn.xiangxu.com.webclient_rest_client.controller; import cn.xiangxu.com.webclient_rest_client.WebclientRestClientApplicationTests; import cn.xiangxu.com.webclient_rest_client.rest_server_api.IStudentApi; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import reactor.core.publisher.Flux; import static org.junit.Assert.*; @Component public class TestControllerTest extends WebclientRestClientApplicationTests { @Autowired private IStudentApi iStudentApi; @Test public void test01() { Flux<Object> objectFlux = iStudentApi.listStudent(); System.out.println("Hello"); objectFlux.toStream().forEach(System.out::println); } }
7 基於WebClient的響應式接口調用框架
具體知識點待更新【其實就是上面知識點的整合使用】

