一. 反射机制
1. 什么是反射
简而言之,通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。程序中一般的对象的类型都是在编译期就确定下来的,而 Java 反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。
核心,本质
Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。
作用
Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。
使用反射机制可以动态获取当前class的信息 比如方法的信息、注解信息、方法的参数、属性等;
2. 反射机制的优缺点
第三方框架---创建对象 不是直接new 反射机制创建
目的:提供开发者能够更好封装框架实现扩展功能。
在一个类中 定义了一个私有属性/方法 反射机制破解私有属性
优点
在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。
缺点
- 反射会消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射;
- 反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
3. 反射的用途:
反编译:.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
4. 反射技术的使用
Class类 代表类的实体,在运行的Java应用程序中表示类和接口
Field类 代表类的成员变量(成员变量也称为类的属性)
Method类 代表类的方法
Constructor类 代表类的构造方法
- getField、getMethod和getCostructor方法可以获得指定名字的域、方法和构造器。
- getFields、getMethods和getCostructors方法可以获得类提供的public域、方法和构造器数组,其中包括超类的共有成员。
- getDeclatedFields、getDeclatedMethods和getDeclaredConstructors方法可以获得类中声明的全部域、方法和构造器,其中包括私有和受保护的成员,但不包括超类的成员。
4.1 反射常用的Api
- Object-->getClass
- 任何数据类型(包括基本的数据类型)都有一个“静态”的class属性
- 通过class类的静态方法:forName(String className)(最常用)
Class<?> aClass = Class.forName("com.mayikt.entity.UserEntity");
// 1.第一种获取class方式
// UserEntity userEntity = new UserEntity();
// Class userClass = userEntity.getClass();
// // 默认执行无参构造函数
// UserEntity user2 = (UserEntity) userClass.newInstance();
// System.out.println(user2==userEntity);
// 2.第二种方式 直接获取class
// Class userClass = UserEntity.class;
// UserEntity user2 = (UserEntity) userClass.newInstance();
// System.out.println(user2);
// 3.第三种方式 类的完整路径地址
Class<?> aClass = Class.forName("com.mayikt.entity.UserEntity");
UserEntity user3 = (UserEntity) aClass.newInstance();
System.out.println(user3);
4.2 运行期间,一个类,只有一个Class对象产生
验证:
public class Test01 {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
// 1.第一种获取class方式
UserEntity userEntity = new UserEntity();
Class userClass1 = userEntity.getClass();
// 2.第二种方式 直接获取class
Class userClass2 = UserEntity.class;
// 3.第三种方式 类的完整路径地址
Class<?> userClass3 = Class.forName("com.mayikt.entity.UserEntity");
System.out.println(userClass3);//
System.out.println(userClass1==userClass2);// true
System.out.println(userClass1==userClass3);// true
}
}
4.3 反射执行构造函数
1. 执行无参数构造函数
Class<?> userClass3 = Class.forName("com.mayikt.entity.UserEntity");
// 1.默认执行无参构造函数
UserEntity userEntity = (UserEntity) userClass3.newInstance();
2. 执行有参数构造函数
Constructor<?> constructor = userClass3.getConstructor(String.class, Integer.class);
UserEntity userEntity2 = (UserEntity) constructor.newInstance("mayikt", 22);
System.out.println(userEntity2.toString());
4.4 反射执行给属性赋值
1. 反射执行给公有属性赋值
// 1.给共有属性赋值
Class<?> aClass = Class.forName("com.mayikt.entity.UserEntity");
Field userNameField = aClass.getField("pubUserName");
UserEntity userEntity = (UserEntity) aClass.newInstance();
userNameField.set(userEntity, "mayikt");
System.out.println(userEntity.pubUserName);
2. 反射执行给私有属性赋值
Class<?> aClass = Class.forName("com.mayikt.entity.UserEntity");
UserEntity userEntity = (UserEntity) aClass.newInstance();
Field userName = aClass.getDeclaredField("userName");
// 设置允许访问私有属性
userName.setAccessible(true);
userName.set(userEntity, "mayikt");
System.out.println(userEntity.getUserName());
注意:userName.setAccessible(true); 该代码是设置允许访问私有属性
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)
4.5 反射执行调用方法
1. 反射调用公有方法
Class<?> aClass = Class.forName("com.mayikt.entity.UserEntity");
UserEntity userEntity = (UserEntity) aClass.newInstance();
Method mayikt = aClass.getMethods("mayikt");
mayikt.invoke(userEntity);
2. 反射调用私有方法
// 1.使用反射调用共有方法
Class<?> aClass = Class.forName("com.mayikt.entity.UserEntity");
UserEntity userEntity = (UserEntity) aClass.newInstance();
Method mayikt = aClass.getDeclaredMethod("mayikt");
// 2.设置允许调用私有方法
mayikt.setAccessible(true);
mayikt.invoke(userEntity);
Exception in thread "main" java.lang.IllegalAccessException: Class com.mayikt.test.Test04 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.Method.invoke(Method.java:491)
at com.mayikt.test.Test04.main(Test04.java:23)
解决办法:
mayikt.setAccessible(true) // 设置允许调用私有方法
3. 反射调用方法传递参数
Class<?> aClass = Class.forName("com.mayikt.entity.UserEntity");
UserEntity userEntity = (UserEntity) aClass.newInstance();
Method mayikt = aClass.getDeclaredMethod("sum", Integer.class, Integer.class);
// 2.设置允许调用私有方法
mayikt.setAccessible(true);
Integer result = (Integer) mayikt.invoke(userEntity, 1, 2);
System.out.println(result);
4. 通过反射越过泛型检查
泛型用在编译期,编译过后泛型擦除(消失掉),所以是可以通过反射越过泛型检查的
ArrayList<String> strings = new ArrayList<String>();
strings.add("mayikt");
// strings.add(1);
Class<? extends ArrayList> aClass = strings.getClass();
Method add = aClass.getDeclaredMethod("add", Object.class);
Object invoke = add.invoke(strings, 1);
System.out.println(strings.size());
// strings.forEach((t) -> {
// System.out.println(t);
// });
二. 注解
1. 注解概念
什么是注解
注解用来给类声明附加额外信息,可以标注在类、字段、方法等上面,编译器、JVM以及开发人员等都可以通过反射拿到注解信息,进而做一些相关处理
SpringBoot 全部都是采用注解化
常用注解
- @Override 只能标注在子类覆盖父类的方法上面,有提示的作用
- @Deprecated 标注在过时的方法或类上面,有提示的作用
- @SuppressWarnings("unchecked") 标注在编译器认为有问题的类、方法等上面,用来取消编译器的警告提示,警告类型有serial、unchecked、unused、all
元注解
元注解用来在声明新注解时指定新注解的一些特性
- @Target 指定新注解标注的位置,比如类、字段、方法等,取值有ElementType.Method等
- @Retention 指定新注解的信息保留到什么时候,取值有RetentionPolicy.RUNTIME等
- @Inherited 指定新注解标注在父类上时可被子类继承
常用注解
@Target(ElementType.METHOD) // 指定新注解可以标注在方法上
@Retention(RetentionPolicy.RUNTIME) // 指定新注解保留到程序运行时期
@Inherited // 指定新注解标注在父类上时可被子类继承
public @interface MayiktName {
public String name();
}
自定义注解 运行 :反射+aop
注解的Target
- TYPE:类、接口(包括注解类型)和枚举的声明
- FIELD:字段声明(包括枚举常量)
- METHOD:方法声明
- PARAMETER:参数声明
- CONSTRUCTOR:构造函数声明
- LOCAL_VARIABLE:本地变量声明
- ANNOTATION_TYPE:注解类型声明
- PACKAGE:包声明
- TYPE_PARAMETER:类型参数声明,JavaSE8引进,可以应用于类的泛型声明之处
- TYPE_USE:JavaSE8引进,此类型包括类型声明和类型参数声明
2. 获取注解信息
// 1.获取当前类上的注解
Class<?> aClass = Class.forName("com.mayikt.entity.UserEntity");
// MayiktName declaredAnnotation = aClass.getDeclaredAnnotation(MayiktName.class);
// System.out.println(declaredAnnotation);
// 2.获取当前方法上的注解
// Method userNameMethod = aClass.getDeclaredMethod("getUserName");
// MayiktName declaredAnnotation = userNameMethod.getDeclaredAnnotation(MayiktName.class);
// System.out.println(declaredAnnotation);
// 3.获取字段上的注解
Field pubUserName = aClass.getDeclaredField("pubUserName");
final MayiktName declaredAnnotation = pubUserName.getDeclaredAnnotation(MayiktName.class);
System.out.println(declaredAnnotation);
// 4.获得构造方法注解
Constructor<TestAnnotation> constructors = clazz.getConstructor(new Class[] {});// 先获得构造方法对象
MyConstructorAnnotation myConstructorAnnotation = constructors.getAnnotation(MyConstructorAnnotation.class);// 拿到构造方法上面的注解实例
System.out.println(myConstructorAnnotation.desc() + "+" + myConstructorAnnotation.uri());
3. 注解如何生效
实际项目中 注解想生效通过反射+aop机制
4. 注解实现案例
4.1 自定义限流注解
对我们接口实现 限流 比如 每s 只能访问1次 或者每s 访问两次。
Maven
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.9.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
</dependencies>
使用谷歌的guava例子
/**
* 每秒生成2.0个令牌
* //
*/
private RateLimiter rateLimiter = RateLimiter.create(2.0);
@GetMapping("/get")
// @MayiktCurrentLimit(name = "get", token = 1)
public String get() {
boolean result = rateLimiter.tryAcquire();
if (!result) {
return "当前访问人数过多,请稍后重试!";
}
return "my is get";
}
4.2 封装自定义注解限流框架
1. 自定义注解
@Target(ElementType.METHOD) // 指定新注解可以标注在方法上
@Retention(RetentionPolicy.RUNTIME) // 指定新注解保留到程序运行时期
@Inherited // 指定新注解标注在父类上时可被子类继承
public @interface CurrentLimit {
String name() default "";
double token() default 10.0;
}
2. 整合AOP实现接口限流
里面有处理日志的也是用aop注解的方式去处理
@Aspect
@Component
public class LogAspect {
@Autowired
private TDemoSysLogService sysLogService;
private ConcurrentHashMap<String, RateLimiter> rateLimiters = new ConcurrentHashMap();
@Pointcut("@annotation(com.ruangh.aop.product.annotation.SysLog)")
public void logPointCut(){
}
@Pointcut("@annotation(com.ruangh.aop.product.annotation.CurrentLimit)")
public void limitPoinCut(){}
@Around("limitPoinCut()")
public Object around1(ProceedingJoinPoint point) throws Throwable {
// 获取拦截的方法名
MethodSignature methodSignature = (MethodSignature)point.getSignature();
CurrentLimit declaredAnnotation = methodSignature.getMethod().getDeclaredAnnotation(CurrentLimit.class);
String name = declaredAnnotation.name();
double token = declaredAnnotation.token();
RateLimiter rateLimiter = rateLimiters.get(name);
if (null == rateLimiter){
rateLimiter = RateLimiter.create(token);
rateLimiters.put(name,rateLimiter);
}
// 开始限流
boolean b = rateLimiter.tryAcquire();
if (!b){
return "当前访问人数过多,请稍后重试!!!";
}
return point.proceed();
}
//配置环绕通知,使用在方法logPointCut()上注册的切入点
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point)throws Throwable{
long beginTime=System.currentTimeMillis();
//执行方法
Object result=point.proceed();
//可以设置time,监控方法执行的时间,但是我没有去实现
long time=System.currentTimeMillis()-beginTime;
saveSysLog(point,time);
return result;
}
private void saveSysLog(ProceedingJoinPoint point,long time){
MethodSignature signature=(MethodSignature) point.getSignature();
Method method=signature.getMethod();
HttpServletRequest request=((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
SysLog sysLog=method.getAnnotation(SysLog.class);
Date date = new Date();
DateFormat dateFormat = new SimpleDateFormat("yyMMddHHmmss");
String format = dateFormat.format(date);
//获取请求参数
Object[] args=point.getArgs();
String ip = CusAccessObjectUtil.getIpAddress(request);
TDemoSysLog demoSysLog = new TDemoSysLog();
demoSysLog.setId(UUID.randomUUID().toString().replaceAll("-",""));
demoSysLog.setExecutionTime((int) time);
demoSysLog.setIp(ip);
demoSysLog.setUserName(sysLog.value().getAction());
demoSysLog.setVisitMethod(method.getName());
demoSysLog.setVisitTime(format);
sysLogService.insert(demoSysLog);
}
3. 使用效果
@RestController
@RequestMapping("/product")
@CrossOrigin
public class TDemoProductController {
/**
* 服务对象
*/
@Resource
private TDemoProductService tDemoProductService;
@CurrentLimit(name = "get",token = 2)
@GetMapping("get")
public String get(){
return "get方法";
}
@CurrentLimit(name = "add")
@GetMapping("add")
public String add(){
return "add方法";
}
@SysLog(LogActions.FIND_PRODUCT_LIST)
@GetMapping("/findProductList/{page}/{limit}")
public Result findProductList(@PathVariable("page") String page,
@PathVariable("limit") String limit){
return Result.ok(tDemoProductService.findProductList(page,limit));
}
}