文章目录
目录
- 文章目录
- 1.什么是反射、反射优缺点
- 1.1 反射的定义
- 1.2 反射的优缺点
- 2. 反射的用途/反射应用场景
- 2.1 反射相关的几个类与方法
- 2.2 通过反射机制访问java对象的属性,方法,构造方法等
- 三种获取.class的方式:
- 2.3 JDBC加载驱动连接 class.forname
- 2.4 Spring容器框架IOC实例化对象
- 3.反射如何越过泛型检查
- 4.什么是注解/注解生效的原理
- 4.1 注解概念
- 4.2 常用注解
- 4.3 元注解
- 5.自定义注解实现API接口限流框架
- 自定义限流注解
1.什么是反射、反射优缺点
1.1 反射的定义
官网定义:反射使Java代码能够发现有关已加载类的字段、方法和构造函数的信息,并在安全限制内使用反射的字段、方法和构造函数对其底层对应项进行操作
Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁
1.2 反射的优缺点
第三方框架创建对象:不是直接new创建而是通过反射机制创建
优点:提供开发者能够更好封装框架实现扩展功能
缺点:反射会增大系统开销 ;反射破环规则,会访问到私有变量,还会逃避泛型的检查
2. 反射的用途/反射应用场景
2.1 反射相关的几个类与方法
Class类 :代表类的实体,在运行的Java应用程序中表示类和接口
Field类 :代表类的成员变量(成员变量也称为类的属性)
Method类 :代表类的方法
Constructor类 :代表类的构造方法
getField、getMethod和getCostructor方法可以获得指定名字的域、方法和构造器。
getFields、getMethods和getCostructors方法可以获得类提供的public域、方法和构造器数组,其中包括超类的共有成员。
getDeclatedFields、getDeclatedMethods和getDeclaredConstructors方法可以获得类中声明的全部域、方法和构造器,其中包括私有和受保护的成员,但不包括超类的成员
2.2 通过反射机制访问java对象的属性,方法,构造方法等
三种获取.class的方式:
- Class<Student> c1 = Student.class;
- Class<? extends Student> c2 = new Student().getClass();
- Class<?> c = Class.forName("day0424.Student");
C1 == C2 ==C,因为内存种只保留有一份.class信息,所以获取到的类对象都是一样的
------------------------------------学生类---------------------------------
class Student{
// 属性
private int no;
private String name;
// 构造
public Student() {
System.out.println("无参构造");
}
public Student(int no, String name) {
this.no = no;
this.name = name;
System.out.println("带参构造:" + no + ":" + name);
}
// 方法
public void function() {
System.out.println("这是一个无参的方法");
}
public String method(String str,int num) {
return "带参方法:" + str + "," + num;
}
}
----------------------------------------测试类---------------------------------
public class TestStudent {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, SecurityException, InstantiationException, IllegalAccessException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException {
// 对字节码文件操作。
// 1. 获得字节码文件对应的对象 // (1)
// Class<Student> c1 = Student.class;
// (2)通过对象.getClass
// Class<? extends Student> c2 = new Student().getClass();
// (3)通过Class.forName("类的完整路径")
Class<?> c = Class.forName("day0424.Student");
------------------------------属性-------------------------------------------
// 获得所有的属性
Field [] fds = c.getFields();// 获得所有公共的属性
fds = c.getDeclaredFields();// 包括私有的
for(Field fd : fds) {
System.out.println(fd.getName());// 获得属性
System.out.println(fd.getType());// 获得类型
System.out.println(Modifier.toString(fd.getModifiers()));// 获得访问权限
}
// 操作属性:赋值,获得属性值
Field fd = c.getDeclaredField("name");// 获得了name属性
fd.setAccessible(true);// 可以访问私有成员
Object obj = c.newInstance();// 创建一个类的对象
fd.set(obj, "郭靖");// 给属性赋值
System.out.println(fd.get(obj));// 获得属性值
------------------------方法------------------------------------------------
// 获得所有方法
Method [] mds = c.getDeclaredMethods();
for(Method md : mds) {
System.out.println(md.getName());
System.out.println(md.getReturnType());// 获得返回值类型
System.out.println(Arrays.toString(md.getParameterTypes()));// 获得参数列表型
}
// 调用方法
// 无参无返回值
Method md = c.getDeclaredMethod("function");
md.invoke(obj);
// 带参带返回值
md = c.getDeclaredMethod("method", String.class,int.class);
System.out.println(md.invoke(obj, "hello",999));
---------------------构造----------------------------------------------------
//获得构造
Constructor [] crs = c.getDeclaredConstructors();
for(Constructor cr : crs) {
System.out.println(Arrays.toString(cr.getParameterTypes()));
}
// 调用有参构造
Constructor cr = c.getDeclaredConstructor(int.class,String.class);
Student s1 = (Student) cr.newInstance(111,"杨康");
//调用无参构造
cr = c.getConstructor();
Student s2 = (Student)cr.newInstance();
}
}
2.3 JDBC加载驱动连接 class.forname
Class.forName("com.mysql.jdbc.Driver"); // 动态加载mysql驱动
Connection conn = null;
try {
//1.数据库连接的4个基本要素
//①JDBC URL用于标识一个被注册的驱动程序,驱动程序管理器通过这个URL选择正确的驱动程序,从而建立到数据库的连接;
/*jdbc:表示URL中的协议
mysql:子协议,用于标识mysql数据库驱动程序
localhost:本机的IP地址
3306:端口号
test:表示访问test数据库*/
String url = "jdbc:mysql://localhost:3306/test";
//user:登录数据库的用户名
String user = "root";
//password:用户名对应的密码,这些都是自己之前设定的
String password = "123456";
//mySql的驱动:com.mysql.jdbc.Driver
String driverName = "com.mysql.jdbc.Driver";
//2.实例化Driver
Class clazz = Class.forName(driverName);
Driver driver = (Driver) clazz.newInstance();
//3.通过DriverManager来注册驱动
DriverManager.registerDriver(driver);
//4.通过DriverManager的getConnection方法,获取Connection类的对象
conn = DriverManager.getConnection(url, user, password);
}
2.4 Spring容器框架IOC实例化对象
<bean id="student" class="com.hqq.UserEntity"/>
3.反射如何越过泛型检查
场景:有个泛型为<String>的ArrayList,若想添加一个Integer的元素,直接调用add()是不行的!
解决办法:利用反射,获取add()方法,越过泛型检查,add()->加入Object对象
常见报错:加入Integer的list,调用forEach的时候依然会检查每个元素的类型,遇到Integer的时候会报错
public class Test05 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//泛型为String 的集合
ArrayList<String> list = new ArrayList();
list.add("String");
//添加失败
// list.add(123);
//利用泛型:获得字节码文件对应的对象;获取add()方法;调用add()方法
Class<? extends ArrayList> aClass = list.getClass();
Method add = aClass.getDeclaredMethod("add", Object.class);
add.invoke(list,123);
//["String",123]
System.out.println(list);
//Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
//list.forEach(t -> System.out.println(t));
}
}
4.什么是注解/注解生效的原理
4.1 注解概念
注解用来给类声明附加额外信息,可以标注在类、字段、方法等上面,编译器、JVM以及开发人员等都可以通过反射拿到注解信息,进而做一些相关处理
SpringBoot 全部都是采用注解化
4.2 常用注解
- @Override 只能标注在子类覆盖父类的方法上面,有提示的作用
- @Deprecated 标注在过时的方法或类上面,有提示的作用
- @SuppressWarnings("unchecked") 标注在编译器认为有问题的类、方法等上面,用来取消编译器的警告提示,警告类型有serial、unchecked、unused、all
4.3 元注解
元注解:在自定义注解的上方添加的一种注解,专为自定义注解服务,提供一些特性,功能
@Target 指定新注解标注的位置,比如类、字段、方法等,取值有ElementType.Method等
- TYPE:类、接口(包括注解类型)和枚举的声明
- FIELD:字段声明(包括枚举常量)
- METHOD:方法声明
- PARAMETER:参数声明
- CONSTRUCTOR:构造函数声明
- LOCAL_VARIABLE:本地变量声明
- ANNOTATION_TYPE:注解类型声明
- PACKAGE:包声明
- TYPE_PARAMETER:类型参数声明,JavaSE8引进,可以应用于类的泛型声明之处
- TYPE_USE:JavaSE8引进,此类型包括类型声明和类型参数声明
- 举例:
场景:自定义一个注解,在此上方调用使用@Target注解,指定在方法,类,属性上可以使用
-
-----------------------------自定义注解--------------- @Target({ElementType.METHOD,ElementType.TYPE,ElementType.FIELD}) public @interface MyAnnotation { } ------------------------注解的使用------------------ @MyAnnotation public class Entity { @MyAnnotation private int age; @MyAnnotation public void get(){ System.out.println("这是一个方法"); } //@MyAnnotation 报错,没有指定构造器可以使用 public Entity(){} }
@Retention 指定新注解的信息保留到什么时候,取值有RetentionPolicy.RUNTIME等
注:若不添加此注解,则在反射获取注解信息的时候会获取不到
1.不添加@Retention的情况:
-------------------不加@Retention的自定义注解-------------
@Target({ElementType.METHOD,ElementType.TYPE,ElementType.FIELD})
public @interface MyAnnotation { //自定义注解
}
-------------------加上自定义注解的类-------------------
@MyAnnotation
public class Entity {
}
--------------------测试-----------------------
public class Test06 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
//获取字节码文件对象
Class<?> aClass = Class.forName("com.hqq.Entity.Entity");
//获取类的注解信息
MyAnnotation annotation = aClass.getDeclaredAnnotation(MyAnnotation.class);
System.out.println(annotation);
}
}
不添加的结果:
2.添加@Retention的情况:
@Target({ElementType.METHOD,ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation { //自定义注解
}
添加的结果:
@Inherited 指定新注解标注在父类上时可被子类继承
5.自定义注解实现API接口限流框架
自定义限流注解
demo场景:在SpringBoot项目中对我们接口实现 限流 比如 每秒只能访问1次 或者每秒访问两次
Maven配置
<?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>org.example</groupId>
<artifactId>mayikt-rf-springboot</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
</parent>
<dependencies>
<!-- springboot 整合web组件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.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>
</dependencies>
</project>
- 不用限流注解 - Controller
/**
* @Author 苦恼的java小宝
* @Date 2022/6/15 20:55
* @ClassName: MemberService
* @Version 1.0
*/
@RestController
public class MemberService {
//每秒生成两个令牌
private RateLimiter rateLimiter = RateLimiter.create(2.0);
@GetMapping("/get")
public String get(){
boolean res = rateLimiter.tryAcquire();
if(!res){
return "当前人数访问过多";
}
return "success";
}
}
- 使用自定义限流注解(反射+Aop)
自定义注解:定义两个参数,name表示要限流的方法名,token表示访问量
注:此时还没起作用,需要结合反射 + AOP做逻辑处理
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LimitAnnotation {
//限流方法名称
String name() default "";
//默认token数
double token() default 5;
}
Controller上添加上注解
@RestController
public class MemberService {
@GetMapping("/get")
@LimitAnnotation(name = "get", token = 1)
public String get() {
return "my is get";
}
@GetMapping("/add")
@LimitAnnotation(name = "add", token = 10)
public String add() {
return "my is add";
}
}
自定义切片类(Aop+反射) :
@Aspect
@Component
public class CurrentLimitAop {
//因为每个方法每个方法的访问量限制各不同,所以用Map存多个RateLimiter
private ConcurrentHashMap<String, RateLimiter> rateLimiters = new ConcurrentHashMap<>();
//前置通知
@Before(value = "@annotation(com.mayikt.service.annotation.LimitAnnotation)")
public void before() {
System.out.println("----------------前置通知----------------");
}
//后置通知
@AfterReturning(value = "@annotation(com.mayikt.service.annotation.LimitAnnotation)")
public void after() {
System.out.println("----------------后置通知----------------");
}
//最核心的环绕通知 -- 能够拦截业务方法的运行
@Around(value = "@annotation(com.mayikt.service.annotation.LimitAnnotation)")
public Object around(ProceedingJoinPoint joinPoint) {
try {
System.out.println("环绕通知开始执行");
//获取拦截的签名
Signature sig = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) sig;
//获取方法上添加的限流注解
LimitAnnotation annotation = methodSignature.getMethod().getDeclaredAnnotation(LimitAnnotation.class);
//若没有注解直接执行业务方法
if (annotation == null){
//执行 添加注解的业务方法
joinPoint.proceed();
//joinPoint.proceed() 也可以选择注释执行,这样不会执行业务方法,直接饭后下面的"环绕通知结束"
}
--------------------------------下面是反射-----------------------------
//获取注解上的name
String name = annotation.name();
//获取注解上的token
double token = annotation.token();
//从Map中获取业务方法对应的限流rateLimiter
RateLimiter rateLimiter = rateLimiters.get(name);
//如果没有就创建并添加进Map
if (rateLimiter == null){
rateLimiter = RateLimiter.create(token);
rateLimiters.put(name,rateLimiter);
}
//开始限流
boolean res = rateLimiter.tryAcquire();
if(!res){
return "访问人数过多";
}
Object obj = joinPoint.proceed();
//执行结束后,执行后置通知
System.out.println("环绕通知结束执行");
//此时obj为业务方法返回的“my is get”
return obj;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return "success";
}
}
通知的执行顺序: