反射是框架的灵魂。动态代理、很多框架(SoringIOC、AOP等)中都用到了反射。
概述:
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法(包括私有的);对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有的);
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.
1. java.lang.Class
Java 中 的java.lang.Class 类是 Java 反射机制的基础,我们想要在运行期间获取一个类的相关信息,就需要使用 Class 类。
JVM 会为每个类都创建一个 Class 对象,在程序运行时, JVM 会首先检查要加载的类对应的 Class 对象是否已经加载。如果没有加载,那么 JVM 会根据类名查找 .class 文件,并将其Class对象载入。
Student 类
@Data @NoArgsConstructor @AllArgsConstructor public class Student { private Integer id; private String name; private Integer age; private void eat(String name){ System.out.println(name + " 吃东西。"); } public int getBirthsDay(Integer age){ return LocalDateTime.now().getYear() - age; } }
1.1 获取 Class 对象
- 方式 1
调用对象的 getClass()
方法
Student student = new Student(); Class clazz = student.getClass();
- 方式 2
根据类名.class 获取
Class clazz = Student.class();
- 方式 3
根据Class类静态方法(全路径名)
Class clazz = Class.forName("com.snow.Student");
注意:在运行期间,一个类,只有一个Class对象产生。
三种方式常用第三种,第一种对象都有了还要反射干什么。第二种需要导入类的包,依赖太强,不导包就抛编译错误。一般都用第三种,一个字符串可以传入也可写在配置文件中等多种方法。
1.2 通过反射创建对象
使用反射创建对象有 2 种方式:
- 使用 Class 对象的
newInstance()
方法,其原理是通过类中定义的无参构造函数来创建对象的。
Student student = clazz.newInstance();
- 使用 java.lang.redlect.Constructor 类中的
newInstance()
方法,这种方式既可以获取无参数的也可以获取有参数的。
// 调用无参 Student student1 = clazz1.getConstructor().newInstance(); System.out.println(student1); // 调用有参 Student student2 = clazz1.getConstructor(Integer.class, String.class, Integer.class) .newInstance(10, "snow", 100); System.out.println(student2);
1.3 通过反射获取类的属性、方法和注解等
除了 newInstance()
方法,Class 类还有其他方法,可以在运行期间获得一个类的方法、属性和注解等。
1.3.1 反射获取构造方法
方法 | 说明 |
Constructor<?>[] getConstructors() | 返回所有构造器对象的数组(只能拿public的) |
Constructor<?>[] getDeclaredConstructors() | 返回所有构造器对象的数组,存在就能拿到 |
Constructor getConstructor(Class<?>… parameterTypes) | 返回单个构造器对象(只能拿public的) |
Constructor getDeclaredConstructor(Class<?>… parameterTypes) | 返回单个构造器对象,存在就能拿到 |
1.3.2 反射通过构造器创建对象
方法 | 说明 |
T newInstance(Object… initargs) | 根据指定的构造器创建对象(参数为属性赋值) |
public void setAccessible(boolean flag) | 设置为true,表示取消访问检查,进行暴力反射 |
1.3.3 反射获取成员方法
方法 | 说明 |
Method[] getMethods() | 返回所有成员方法对象的数组(只能拿public的) |
Method[] getDeclaredMethods() | 返回所有成员方法对象的数组,存在就能拿到 |
Method getMethod(String name, Class<?>… parameterTypes) | 返回单个成员方法对象(只能拿public的) |
Method getDeclaredMethod(String name, Class<?>… parameterTypes) | 返回单个成员方法对象,存在就能拿到(第一个是方法名称,后面的是参数类型) |
Method 类中用于触发执行的方法
// 参数1:方法名 参数2:形参类型 Method eat = clazz1.getDeclaredMethod("eat", String.class); // 设置此方法可访问(只有私有方法才需要加这个) eat.setAccessible(true); // 运行方法 参数1:执行该方法的对象 参数2:实际参数 eat.invoke(student1, "snow");
Method getBirthsDay = clazz1.getDeclaredMethod("getBirthsDay", Integer.class); Integer year = (Integer) getBirthsDay.invoke(student1, 25); System.out.println(year);
1.3.4 反射获取属性
方法 | 说明 |
Field[] getFields() | 返回所有成员变量对象的数组(只能拿public的) |
Field[] getDeclaredFields() | 返回所有成员变量对象的数组,存在就能拿到 |
Field getField(String name) | 返回单个成员变量对象(只能拿public的) |
Field getDeclaredField(String name) | 返回单个成员变量对象,存在就能拿到 |
Filed类 用于取值,赋值的方法:
方法 | 说明 |
void set(Object obj, Object value) | 赋值 |
Object get(Object obj) | 获取值。 |
Field[] declaredFields = clazz1.getDeclaredFields(); System.out.println(Arrays.toString(declaredFields));
2. 工具类操作
import org.springframework.util.ReflectionUtils; String serviceClass = "orderUserServiceImpl"; String methodName = "getOne"; int id = 1; // 获取方法 Method method = ReflectionUtils.findMethod(SpringContextUtil.getBean(serviceClass).getClass(), methodName, Integer.class); //用spring bean获取操作前的参数,此处需要注意:传入的id类型与bean里面的参数类型需要保持一致 // 获取对象 Object obj = ReflectionUtils.invokeMethod(method, SpringContextUtil.getBean(serviceClass), id);
借助到的 SpringContextUtil 工具类:
import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component public class SpringContextUtil implements ApplicationContextAware { private static ApplicationContext applicationContext; /** * 实现ApplicationContextAware接口的回调方法,设置上下文环境 */ public void setApplicationContext(ApplicationContext applicationContext){ SpringContextUtil.applicationContext = applicationContext; } public static ApplicationContext getApplicationContext(){ return applicationContext; } /** * 获取对象 * @return Object 一个以所给名字注册的bean的实例 (service注解方式,自动生成以首字母小写的类名为bean name) */ public static Object getBean(String name) throws BeansException { return applicationContext.getBean(name); } }
3. 反射是如何破坏单例模式的
反射可以在运行期间获取并调用一个类的任何方法(包括私有方法)。所以使用反射可以破坏单例模式的。
SO : 如何避免单例对象被反射破坏:
只需要改造构造函数,使其在反射调用时识别对象是不是被创建即可:
private Student(){ if(student != null){ throw new RuntimeException("....."); } }
4. 反射结合注解实现操作日志
注解相关内容:https://blog.csdn.net/m0_60915009/article/details/130677945
自定义注解:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface SnowLog { // 操作类型 public String operateType(); // 操作模块 public String operateTarget(); // 操作对象id public String operateTargetIdExpression(); }
切面类
import com.baomidou.mybatisplus.core.toolkit.StringUtils; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.core.LocalVariableTableParameterNameDiscoverer; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import com.google.common.base.CaseFormat; @Aspect @Component @Slf4j public class SnowLogAspect { // 环绕通知 @Around("@annotation(com.baga.web.controller.log.SnowLog)") public Object log(ProceedingJoinPoint point) throws Exception{ Method method = ((MethodSignature) point.getSignature()).getMethod(); SnowLog snowLog = method.getAnnotation(SnowLog.class); Object response = null; try { // 执行目标方法 response = point.proceed(); } catch (Throwable throwable) { throw new Exception(throwable); } if(StringUtils.isBlank(snowLog.operateTargetIdExpression())){ return null; } SpelExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression(snowLog.operateTargetIdExpression()); EvaluationContext context = new StandardEvaluationContext(); // 获取参数值 Object[] args = point.getArgs(); // 获取运行时参数名称 LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer(); String[] parameterNames = discoverer.getParameterNames(method); // 将参数绑定到 context 中 if(parameterNames != null){ for (int i = 0; i < parameterNames.length; i++) { context.setVariable(parameterNames[i], args[i]); } } // 将方法的 resp 当做变量放到 context 中,变量名称为该类名转换为小写字母 // 开头的驼峰形式 if(response != null){ context.setVariable( CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, response.getClass().getSimpleName()), response); } // 解析表达式,获取结果 String operateTargetId = String.valueOf(expression.getValue(context)); // 执行日志记录 handleSnowLog(snowLog.operateType(), snowLog.operateTarget(), operateTargetId); return response; } private void handleSnowLog(String operateType, String operateTarget, String operateTargetId){ log.info("operateType : {}, operateTarget : {}, operateTargetId : {}", operateType, operateTarget, operateTargetId); } }
业务-添加
@PostMapping("/addStudyReview") @SnowLog(operateTarget = "温故知新", operateType = "添加", operateTargetIdExpression = "#baseResult.data.id") public BaseResult addStudyReview(@Validated(Default.class) @RequestBody StudyReviewAddModel addModel){ StudyReview studyReview = studyReviewService.addStudyReview(addModel); return BaseResult.success(studyReview); }
业务-修改
@PutMapping("/updateStudyReview") @SnowLog(operateTarget = "温故知新", operateType = "修改", operateTargetIdExpression = "#updateModel.id") public BaseResult updateStudyReview(@Validated(Default.class) @RequestBody StudyReviewUpdateModel updateModel){ studyReviewService.updateStudyReview(updateModel); return BaseResult.success(); }
业务-删除
@DeleteMapping("/end/{id}") @SnowLog(operateTarget = "温故知新", operateType = "终结", operateTargetIdExpression = "#id") public BaseResult end(@PathVariable("id") Integer id){ StudyReview studyReview = studyReviewService.getById(id); if(studyReview == null || "1".equals(studyReview.getIsClose())){ return BaseResult.success(); } studyReview.setIsClose("1"); studyReviewService.updateById(studyReview); return BaseResult.success(); }
业务
@PostMapping("/test") @SnowLog(operateTarget = "温故知新", operateType = "测试", operateTargetIdExpression = "#id") public BaseResult test(Integer id, Integer age, String name){ return BaseResult.success(); }