反射
Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。==> 本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。
Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。
通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。
在运行时期,一个类只有一个Class对象产生
反射的优缺点
优点:可以在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的特点,能够让我们很方便的创建灵活的代码,这些代码可以在运行时进行装配,不用在组件之间进行代码之间的链接。
缺点:反射会消耗一定的系统资源,假如不需要动态的创建对象的时候,就不要使用反射;反射调用方法的时候可以忽略权限的检查,因此可能会破坏封装性而导致安全问题
反射的用途
反编译:.class–>.java
-
通过反射机制访问java对象的属性,方法,构造方法等
-
JDBC加载驱动连接 class.forname
Class.forName("com.mysql.jdbc.Driver")
; // 动态加载mysql驱动 -
Spring容器框架IOC实例化对象
<bean id="mayikt" class="com.mayikt.UserEntity" />
-
自定义注解生效(反射+Aop)
-
第三方核心的框架 mybatis orm
反射常用的API
Class类
代表类的实体,在运行的Java应用程序中表示类和接口
Field类
代表类的成员变量(成员变量也称为类的属性)
Method类
代表类的方法
Constructor类
代表类的构造方法
-
getField、getMethod和getCostructor方法可以 获得指定名字的域、方法和构造器。
-
getFields、getMethods和getCostructors方法可以 获得类提供的public域、方法和构造器数组,其中包括超类的共有成员。
-
getDeclatedFields、getDeclatedMethods和getDeclaredConstructors方法可以 获得类中声明的全部域、方法和构造器,其中包括私有和受保护的成员,但不包括超类的成员。
获取Class的方式
// 获取Class的方式
User user = new User();
Class<? extends User> aClass3 = user.getClass(); // 方法1
User user1 = aClass3.newInstance();
Class<User> userClass = User.class; // 方法2
User user5 = userClass.newInstance();
Class<?> aClass = Class.forName("com.study.entity.User"); // 方法2
User user8 = (User) aClass.newInstance();
// 都是获取的同一个类的字节码文件
System.out.println(aClass == userClass);
// 通过构造器进行赋值
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(String.class, Integer.class);
User user2 = (User) declaredConstructor.newInstance("消化", 198);
System.out.println(user2);
/**
* Class.forName(xxx.xx.xx) 返回的是一个类,
* .newInstance() 后才创建一个对象
* Class.forName(xxx.xx.xx);的作用是要求JVM查找并加载指定的类
*/
// Class.forName:返回与给定的字符串名称相关联类或接口的Class对象。 (class com.study.entity.User)
Class<?> aClass2 = Class.forName(User.class.getName());
// 获取setAge这个方法 (public void com.study.entity.User.setAge(java.lang.Integer) )
Method setAge = aClass2.getDeclaredMethod("setAge", Integer.class);
// 开启能直接操作私有属性的权限
setAge.setAccessible(true);
// 创建一个实例对象,默认执行无参构造函数 (User(name=null, age=null))
User user4 = (User) aClass2.newInstance();
// invoke() 重要方法,框架底层的调用都是使用反射来创建对象
setAge.invoke(user4,199);
System.out.println(user4.toString());
注解
注解用来给类声明附加额外信息,可以标注在类、字段、方法等上面,编译器、JVM以及开发人员等都可以通过反射拿到注解信息,进而做一些相关处理
元注解
元注解用来在声明新注解时指定新注解的一些特性
@Target 指定新注解标注的位置,比如类、字段、方法等,取值有ElementType.Method等
@Retention 指定新注解的信息保留到什么时候,取值有 RetentionPolicy.RUNTIME
(指定新注解保留到程序运行时期)等
@Inherited 指定新注解标注在父类上时可被子类继承
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LoginToking {
}
public static void main(String[] args) throws Exception{
Class<?> aClass = Class.forName(User.class.getName());
/*---------------------------获取属性上是否加了某个注解----------------------------------------------*/
// 获取到属性上是否加了某个注解:先使用getDeclaredField获取到这个方法,然后再根据getDeclaredAnnotation获取注解
// 类.class : java里面有一个class类,通过它可以实例一个对象,不用用new来创建对象
LoginToking name = aClass.getDeclaredField("name").getDeclaredAnnotation(LoginToking.class);
System.out.println(name.toString());
System.out.println(name.annotationType());
/*---------------------------获取方法上是否加了某个注解--------------------------*/
Method method = aClass.getDeclaredMethod("getName");
LoginToking declaredAnnotation1 = method.getDeclaredAnnotation(LoginToking.class);
System.out.println(declaredAnnotation1.toString());
/*---------------------------获取类上是否加了某个注解--------------------------*/
LoginToking declaredAnnotation = aClass.getDeclaredAnnotation(LoginToking.class);
System.out.println(declaredAnnotation.toString());
}
使用AOP+反射+自定义注解实现限流框架
1. 定义一个注解,设置好作用域和属性
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LimitRate {
String name() default "";
// 每秒访问的次数
double token() default 20;
}
2. 编写AOP配置类
@Component
@Aspect
public class LimitAOP {
// 创建一个具有指定稳定吞吐量(QPS 2/s)
// private RateLimiter rateLimiter = RateLimiter.create(2.0);
// 定义一个ConcurrentHashMap来存放每次请求不同的限流规则
private ConcurrentHashMap<String, RateLimiter> rateLimiters = new ConcurrentHashMap();
// @Before 在切点方法之前执行
//@After 在切点方法之后执行
//@AfterReturning 切点方法返回后执行
//@AfterThrowing 切点方法抛异常执行
//@Around 属于环绕增强,能控制切点执行前,执行后
@Around(value = "@annotation(com.example.demo.annotation.LimitRate)")
public Object around(ProceedingJoinPoint joinPoint){
// 获取拦截的方法名
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature)signature;
LimitRate declaredAnnotation = methodSignature.getMethod().getDeclaredAnnotation(LimitRate.class);
// 获取该注解的name和token参数
String name = declaredAnnotation.name();
double token = declaredAnnotation.token();
RateLimiter rateLimiter = rateLimiters.get(name);
if (rateLimiter == null) {
rateLimiter = RateLimiter.create(token);
rateLimiters.put(name, rateLimiter);
}
// 开始进行限流
boolean b = rateLimiter.tryAcquire();
if(!b){
return "当前访问人数过多,请稍后再试";
}
Object proceed = null;
try {
proceed = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println(proceed);
return proceed;
}
}
3. 将要进行限流的方法上加上自定义的注解
@RestController
public class MemberController {
@GetMapping("/get")
@LimitRate(name = "get",token = 1)
public String get(){
return "index";
}
@GetMapping("/get1")
@LimitRate(name = "get1",token = 3)
public String get1(){
return "index1";
}
}