當前位置:首頁 > IT技術 > 編程語言 > 正文

Java反射自定義注解底層設計原理
2022-09-06 22:42:29


文章目錄

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

一、反射
1. 反射概念

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

2. 反射機制的優(yōu)缺點

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

3. 反射的用途

反編譯:.class–>.java
1.通過反射機制訪問java對象的屬性,方法,構造方法等
2. JDBC加載驅動連接 class.forname
Class.forName(“com.mysql.jdbc.Driver”); // 動態(tài)加載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)任何數據類型(包括基本的數據類型)都有一個“靜態(tài)”的class屬性
(3)通過class類的靜態(tài)方法: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;
//默認執(zhí)行無參構造函數
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. 反射執(zhí)行構造函數

執(zhí)行無參數構造函數和執(zhí)行有參數構造函數

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

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

反射執(zhí)行給公有屬性賦值和反射執(zhí)行給私有屬性賦值

/**
* 反射如何給屬性賦值
*/
@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. 反射執(zhí)行調用方法

反射調用公有方法

() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
Class<?> userClass2 = Class.forName("com.gblfy.elk.entity.UserEntity");
//創(chuàng)建類實例
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");
//創(chuàng)建類實例
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以及開發(fā)人員等都可以通過反射拿到注解信息,進而做一些相關處理

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("執(zhí)行無參構造函數");
}

public UserEntity(String userName, Integer userAge) {
System.out.println("執(zhí)行有參構造函數");
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");
//創(chuàng)建類實例
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("-----------------執(zhí)行目標方法-----------------");

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 {

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

/**
* 只要在方法上添加該自定義限流注解,就會被AOP環(huán)繞通知攔截
*
* @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) {
// 執(zhí)行目標方法
return joinPoint.proceed();
}
// 1.獲取注解上的限流名稱(name)
String name = gblfyStreamLimit.name();

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


/**
* 前置通知
*/
@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("-----------------執(zhí)行目標方法-----------------");
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

開通會員,享受整站包年服務立即開通 >