• <dd id="weawc"></dd>
  • Java反射自定義注解底層設計原理
    2022-09-06 22:42:29


    文章目錄

    1.什么是反射、反射優缺點
    2.反射的用途/反射應用場景
    3.反射調用方法/給屬性賦值
    4.反射如何越過泛型檢查
    5.什么是注解/注解生效的原理
    6.自定義注解實現API接口限流框架

    一、反射
    1. 反射概念

    使用反射機制可以動態獲取當前class的信息 比如方法的信息、注解信息、方法的參數、屬性等。
    .java 源代碼 編譯.class 類加載器 jvm 字節碼

    2. 反射機制的優缺點

    優點:提供開發者能夠更好封裝框架實現擴展功能。
    缺點:
    (1)反射會消耗一定的系統資源,因此如果不需要動態地創建一個對象,那么就不需要用反射;
    (2)反射調用方法時可以忽略權限檢查,因此可能會破壞封裝性而導致安全問題。

    3. 反射的用途

    反編譯:.class–>.java
    1.通過反射機制訪問java對象的屬性,方法,構造方法等
    2. JDBC加載驅動連接 class.forname
    Class.forName(“com.mysql.jdbc.Driver”); // 動態加載mysql驅動
    3. Spring容器框架IOC實例化對象

    <bean id="mayikt" class="com.mayikt.UserEntity" />

    4.自定義注解生效(反射+Aop)
    5.第三方核心的框架 mybatis orm

    4. 反射技術的使用

    Class類 代表類的實體,在運行的Java應用程序中表示類和接口
    Field類 代表類的成員變量(成員變量也稱為類的屬性)
    Method類 代表類的方法
    Constructor類 代表類的構造方法
    1.getField、getMethod和getCostructor方法可以獲得指定名字的域、方法和構造器。
    2.getFields、getMethods和getCostructors方法可以獲得類提供的public域、方法和構造器數組,其中包括超類的共有成員。
    3.getDeclatedFields、getDeclatedMethods和getDeclaredConstructors方法可以獲得類中聲明的全部域、方法和構造器,其中包括私有和受保護的成員,但不包括超類的成員。

    5. 反射常用的Api

    (1)Object–>getClass
    (2)任何數據類型(包括基本的數據類型)都有一個“靜態”的class屬性
    (3)通過class類的靜態方法:forName(String className)(最常用)
    Class<?> aClass = Class.forName(“com.mayikt.entity.UserEntity”);

    <p>
    * 第1種:獲取class UserEntity.class
    * 第2種:獲取class Class.forName("類的全路徑");
    * 第3種:new UserEntity().getClass()
    */
    @Test
    public void objCreateTest() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
    Class<UserEntity> userClass1 = UserEntity.class;
    //默認執行無參構造函數
    UserEntity userEntity1 = userClass1.newInstance();
    System.out.println(userEntity1);

    //2.類的的完成路徑 報名+類名
    Class<?> userClass2 = Class.forName("com.gblfy.elk.entity.UserEntity");
    System.out.println(userClass1 == userClass2);

    //3.new UserEntity().getClass()
    UserEntity userEntity2 = new UserEntity();
    Class userClass3 = userEntity2.getClass();

    System.out.println(userClass1 == userClass3);//true
    System.out.println(userEntity1 == userEntity2);//false
    }

    運行期間,一個類,只有一個Class對象產生

    6. 反射執行構造函數

    執行無參數構造函數和執行有參數構造函數

    () throws InstantiationException, IllegalAccessException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException {
    // //2.類的的完成路徑 報名+類名
    Class<?> userClass2 = Class.forName("com.gblfy.elk.entity.UserEntity");
    // //默認執行無參構造函數
    UserEntity userEntity = (UserEntity) userClass2.newInstance();
    System.out.println(userEntity);

    //執行有參構造函數
    Constructor<?> declaredConstructor = userClass2.getDeclaredConstructor(String.class, Integer.class);
    UserEntity userEntity2 = (UserEntity) declaredConstructor.newInstance("mayikt", 22);
    System.out.println(userEntity2);
    }
    7. 反射執行給屬性賦值

    反射執行給公有屬性賦值和反射執行給私有屬性賦值

    /**
    * 反射如何給屬性賦值
    */
    @Test
    public void evaluationTest() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
    Class<?> userClass2 = Class.forName("com.gblfy.elk.entity.UserEntity");
    UserEntity userEntity2 = (UserEntity) userClass2.newInstance();

    //給公有屬性賦值
    Field publicName = userClass2.getDeclaredField("publicName");
    publicName.set(userEntity2, "mayikt");
    System.out.println(userEntity2.getPublicName());

    //給私有屬性賦值
    Field userName = userClass2.getDeclaredField("userName");
    //設置訪問私有屬性權限
    userName.setAccessible(true);
    userName.set(userEntity2, "mayikt2");
    System.out.println(userEntity2.getUserName());
    }
    注意:
    xception in thread "main" java.lang.IllegalAccessException: Class com.mayikt.test.Test03 can not access a member of class com.mayikt.entity.UserEntity with modifiers "private"
    at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
    at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
    at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
    at java.lang.reflect.Field.set(Field.java:761)
    at com.mayikt.test.Test03.main(Test03.java:28)
    解決辦法:
    // 設置允許訪問私有屬性
    userName.setAccessible(true);
    8. 反射執行調用方法

    反射調用公有方法

    () throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
    Class<?> userClass2 = Class.forName("com.gblfy.elk.entity.UserEntity");
    //創建類實例
    Object o = userClass2.newInstance();
    //獲取公有和私有方法
    Method method = userClass2.getDeclaredMethod("mayikt");
    //設置訪問私有方法權限
    method.setAccessible(true);
    method.invoke(o);
    }

    反射調用私有方法和反射調用方法傳遞參數

    //使用反射機制調用私有有參方法
    @Test
    public void methodCarryParamTest() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
    Class<?> userClass2 = Class.forName("com.gblfy.elk.entity.UserEntity");
    //創建類實例
    Object o = userClass2.newInstance();
    //獲取公有和私有方法
    Method method = userClass2.getDeclaredMethod("sum", Integer.class, Integer.class);
    //設置訪問私有方法權限
    method.setAccessible(true);
    Integer result = (Integer) method.invoke(o, 1, 5);
    System.out.println(result);
    }
    二、注解
    2.1. 注解概念

    什么是注解
    注解用來給類聲明附加額外信息,可以標注在類、字段、方法等上面,編譯器、JVM以及開發人員等都可以通過反射拿到注解信息,進而做一些相關處理

    SpringBoot 全部都是采用注解化

    2.2. 常用注解
    @Override     只能標注在子類覆蓋父類的方法上面,有提示的作用
    @Deprecated 標注在過時的方法或類上面,有提示的作用
    @SuppressWarnings("unchecked")
    2.3. 元注解
    元注解用來在聲明新注解時指定新注解的一些特性
    @Target 指定新注解標注的位置,比如類、字段、方法等,取值有ElementType.Method等
    @Retention 指定新注解的信息保留到什么時候,取值有RetentionPolicy.RUNTIME等
    @Inherited 指定新注解標注在父類上時可被子類繼承
    2.4. 常用注解
    (ElementType.METHOD) // 指定新注解可以標注在方法上
    @Retention(RetentionPolicy.RUNTIME) // 指定新注解保留到程序運行時期
    @Inherited // 指定新注解標注在父類上時可被子類繼承
    public @interface MayiktName {
    public String name();
    }
    2.5. 注解的Target
    TYPE:類、接口(包括注解類型)和枚舉的聲明
    FIELD:字段聲明(包括枚舉常量)
    METHOD:方法聲明
    PARAMETER:參數聲明
    CONSTRUCTOR:構造函數聲明
    LOCAL_VARIABLE:本地變量聲明
    ANNOTATION_TYPE:注解類型聲明
    PACKAGE:包聲明
    TYPE_PARAMETER:類型參數聲明,JavaSE8引進,可以應用于類的泛型聲明之處
    TYPE_USE:JavaSE8引進,此類型包括類型聲明和類型參數聲明
    2.6. 獲取注解信息
    package com.gblfy.elk.annotate;

    import java.lang.annotation.*;

    /**
    * ElementType.TYPE 注解在類上生效
    * ElementType.METHOD 注解在方法上生效
    * ElementType.FIELD 注解在屬性上生效
    * Retention 加此注解,反射才可以獲取
    * Inherited 子類可以繼承
    */
    @Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD,ElementType.ANNOTATION_TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    public @interface MayiktName {
    }
    package com.gblfy.elk.entity;

    import com.gblfy.elk.annotate.MayiktName;

    @MayiktName
    public class UserEntity {

    private String userName;
    private Integer userAge;

    @MayiktName
    public String publicName;

    public UserEntity() {
    System.out.println("執行無參構造函數");
    }

    public UserEntity(String userName, Integer userAge) {
    System.out.println("執行有參構造函數");
    this.userName = userName;
    this.userAge = userAge;
    }

    public String getUserName() {
    return userName;
    }

    public void setUserName(String userName) {
    this.userName = userName;
    }

    public Integer getUserAge() {
    return userAge;
    }

    public void setUserAge(Integer userAge) {
    this.userAge = userAge;
    }

    public String getPublicName() {
    return publicName;
    }

    public void setPublicName(String publicName) {
    this.publicName = publicName;
    }

    @Override
    public String toString() {
    final StringBuffer sb = new StringBuffer("UserEntity{");
    sb.append("userName='").append(userName).append(''');
    sb.append(", userAge=").append(userAge);
    sb.append('}');
    return sb.toString();
    }

    @MayiktName
    private void mayikt() {
    System.out.println("mayikt");
    }

    @MayiktName
    private Integer sum(Integer a, Integer b) {
    return a + b;
    }
    }
    /**
    * 注解聯練習
    *
    * @author gblfy
    * @date 2022-03-13
    */
    public class AnnotateCase {

    //判斷某類上是否加上@MayiktName注解
    @Test
    public void classAnnotateTest() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException {
    //加載類
    Class<?> userClass = Class.forName("com.gblfy.elk.entity.UserEntity");
    // 1.獲取當前類上的注解
    MayiktName declaredAnnotation = userClass.getDeclaredAnnotation(MayiktName.class);
    System.out.println(declaredAnnotation);
    }

    //判斷指定方法上是否加上@MayiktName注解
    @Test
    public void methodAnnotateTest() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException {
    //加載類
    Class<?> userClass = Class.forName("com.gblfy.elk.entity.UserEntity");
    //創建類實例
    Object o = userClass.newInstance();
    //獲取指定mayikt方法
    Method method = userClass.getDeclaredMethod("mayikt");
    //獲取該方法上的注解,有則返回,無則返回null
    MayiktName mayiktName = method.getDeclaredAnnotation(MayiktName.class);
    System.out.println(mayiktName);
    }

    //判斷某屬性上是否加上@MayiktName注解
    @Test
    public void fieldAnnotateTest() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException {
    //加載類
    Class<?> userClass = Class.forName("com.gblfy.elk.entity.UserEntity");
    // 1.獲取屬性上的注解
    Field publicName = userClass.getDeclaredField("publicName");
    MayiktName mayiktName = publicName.getDeclaredAnnotation(MayiktName.class);
    System.out.println(mayiktName);
    }
    }
    2.7. 注解如何生效

    實際項目中 注解想生效通過反射+aop機制

    2.8. 注解實現案例

    自定義限流注解

    對我們接口實現 限流 比如 每s 只能訪問1次 或者每s 訪問兩次。
    Maven

    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.79</version>
    </dependency>
    <dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>22.0</version>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

    <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    使用谷歌的guava例子

    package com.gblfy.elk.controller;

    import com.gblfy.elk.annotate.GblfyStreamLimit;
    import com.google.common.util.concurrent.RateLimiter;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;

    @RestController
    public class SreamLimitController {

    /**
    * 每秒生成1.0個令牌
    * 滑動窗口、令牌桶、漏桶算法實現
    */
    private RateLimiter rateLimiter = RateLimiter.create(1.0);

    @GetMapping("/get")
    public String get() {
    System.out.println("-----------------執行目標方法-----------------");

    boolean result = rateLimiter.tryAcquire();
    if (!result) {
    return "當前訪問人數過多,請稍后重試!";
    }
    return "my is get";
    }

    @GetMapping("/add")
    public String add() {
    return "my is add";
    }
    }
    2.09. 封裝自定義注解限流框架

    整合自定義注解

    package com.gblfy.elk.annotate;

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;

    /**
    * 自定義請求限流注解
    *
    * @author gblfy
    * @date 2022-03-13
    */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface GblfyStreamLimit {
    /**
    * 限流名稱
    *
    * @return
    */
    String name() default "";

    /**
    * 限流次數,默認限流頻次 1秒/20次
    *
    * @return
    */
    double limitNum() default 20.0;
    }
    2.10. 整合Aop實現接口限流
    package com.gblfy.elk.aop;

    import com.gblfy.elk.annotate.GblfyStreamLimit;
    import com.google.common.util.concurrent.RateLimiter;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.Signature;
    import org.aspectj.lang.annotation.*;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.stereotype.Component;

    import java.util.concurrent.ConcurrentHashMap;

    @Aspect
    @Component
    public class StreamLimitAop {

    //并發map儲存
    private ConcurrentHashMap<String, RateLimiter> rateLimiterStrategy = new ConcurrentHashMap();

    /**
    * 只要在方法上添加該自定義限流注解,就會被AOP環繞通知攔截
    *
    * @param joinPoint
    * @return
    */
    @Around(value = "@annotation(com.gblfy.elk.annotate.GblfyStreamLimit)")
    public Object around(ProceedingJoinPoint joinPoint) {

    try {
    //獲取攔截的方法名
    Signature sig = joinPoint.getSignature();
    //獲取攔截的方法名
    MethodSignature methodSignature = (MethodSignature) sig;
    // 判斷方法上是否有加上該注解,如果有加上注解則限流
    GblfyStreamLimit gblfyStreamLimit =
    methodSignature.getMethod().getDeclaredAnnotation(GblfyStreamLimit.class);
    if (gblfyStreamLimit == null) {
    // 執行目標方法
    return joinPoint.proceed();
    }
    // 1.獲取注解上的限流名稱(name)
    String name = gblfyStreamLimit.name();

    // 2.獲取注解上的limitNum(限流次數),實現對不同的方法限流策略不一樣的效果
    double limitNum = gblfyStreamLimit.limitNum();
    RateLimiter rateLimiter = rateLimiterStrategy.get(name);
    if (rateLimiter == null) {
    //3.動態匹配并創建不同的限流策略
    rateLimiter = RateLimiter.create(limitNum);
    rateLimiterStrategy.put(name, rateLimiter);
    }
    // 開始限流
    boolean result = rateLimiter.tryAcquire();
    if (!result) {
    return "當前訪問人數過多,請稍后重試!";
    }
    return joinPoint.proceed();
    } catch (Throwable throwable) {
    return "系統出現了錯誤!";
    }
    }


    /**
    * 前置通知
    */
    @Before(value = "@annotation(com.gblfy.elk.annotate.GblfyStreamLimit)")
    public void before() {
    System.out.println("----------------------前置通知----------------------");
    }


    /**
    * 后置通知
    */
    @AfterReturning(value = "@annotation(com.gblfy.elk.annotate.GblfyStreamLimit)")
    public void AfterReturning() {
    System.out.println("----------------------后置通知----------------------");
    }

    /**
    * 異常通知
    *
    * @param point
    */
    @AfterThrowing(value = "@annotation(com.gblfy.elk.annotate.GblfyStreamLimit)", throwing = "e")
    public void serviceAspect(JoinPoint point, Exception e) {
    System.out.println("----------------------異常通知----------------------");
    }
    }
    2.11. 案例
    package com.gblfy.elk.controller;

    import com.gblfy.elk.annotate.GblfyStreamLimit;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;

    @RestController
    public class SreamLimitController {


    @GetMapping("/get2")
    @GblfyStreamLimit(name = "get2", limitNum = 1.0)
    public String get2() {
    System.out.println("-----------------執行目標方法-----------------");
    return "my is get";
    }

    @GetMapping("/add")
    public String add() {
    return "my is add";
    }
    }

    ??http://127.0.0.1:8080/get?? http://127.0.0.1:8080/my


    本文摘自 :https://blog.51cto.com/g


    更多科技新聞 ......

    日本成人三级A片
  • <dd id="weawc"></dd>