SpringBoot27 JDK動態代理詳解、獲取指定的類類型、動態注冊Bean、接口調用框架


 

1 JDK動態代理詳解

  靜態代理、JDK動態代理、Cglib動態代理的簡單實現方式和區別請參見我的另外一篇博文

  1.1 JDK代理的基本步驟

    》通過實現InvocationHandler接口來自定義自己的InvocationHandler;

    》通過Proxy.getProxyClass獲得動態代理類
    》通過反射機制獲得代理類的構造方法,方法簽名為getConstructor(InvocationHandler.class)
    》通過構造函數獲得代理對象並將自定義的InvocationHandler實例對象傳為參數傳入
    》通過代理對象調用目標方法

  1.2 JDK動態代理的應用場景

    重要掃盲知識點:利用JDK動態代理獲取到的動態代理實例的類型默認是Object類型,如果需要進行類型轉化必須轉化成目標類的接口類型,因為JDK動態代理是利用目標類的接口實現的

    前提准備:創建一個SpringBoot項目並引入相關依賴

      技巧01:我引入的web依賴時響應式的,可以不引入響應式的web依賴,引入springboot的常規web依賴就可了   
<?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可以實現的功能

      詳情參見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

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

      》自定義代理類接口以及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
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;
    }
}
JdkProxyCreator.java

      》自定義@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

      》自定義標注了@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
    );

}
IStudentApi.java

      》自定義動態注冊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();
    }
}
DynamicRegisterBeanUtil.java

      》測試動態注冊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的響應式接口調用框架

  具體知識點待更新【其實就是上面知識點的整合使用】

  服務端和客戶端源代碼

 

      

    

        

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM