当前位置:首页 > IT技术 > 编程语言 > 正文

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