Java反射和注解简介

一、前言

首先我们要知道什么是注解和反射:

  • 注解:代码里的特殊标记,这些标记可以在编译,类加载,或者运行时被读取。是JDK5.0引入的。
  • 反射:反射是指在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性,这种动态获取信息以及动态调用对象方法的功能就称为Java的反射机制。

反射和注解是Java中许多框架底层常用的技术(如SpringBoot),在我们日常使用这些框架进行开发时,对注解肯定不陌生,注解具有强大的功能,在许多框架中使用注解可以快速进行开发,不过反射在日常开发中使用的并不多,反射机制是框架底层代码帮我们做的。因此,要想深度了解框架知识,必须要学习注解和反射。
本博客是在学习B站上尚学堂的课程进行总结的:【尚学堂】Java反射和注解

二、反射

2.1 反射介绍

Java反射机制就是在运行状态下,动态地获取一个类或者对象所有的方法和属性。反射可以将Java类中各个成分映射为一个个的Java对象。
反射的特点

  • 优点:灵活性和扩展性
  • 缺点:性能问题,反射是在运行过程中操作字节码文件实现的,要比直接使用源代码操作慢

对于反射性能问题,可能的原因

  • 需要检查方法是否可见
  • 需要校验参数
  • JIT无法进行优化

总结:反射主要是用在对灵活性和扩展性要求比较高的框架中,普通代码不建议使用。

2.2 Class类介绍

要使用反射,首先要获取该类字节码文件所对应的Class对象,而解剖类中的各成分使用的就是Class类中的方法。
可以查询API文件找到Class类,java.lang.Class
在这里插入图片描述
在这里插入图片描述

可以看到类是没有公共构造方法的,因此不能通过new的方式来创建Class对象,获取Class对象有以下三种方式

  • 通过类直接获取Class
  • 通过对象的getClass()获取
  • 通过类路径获取
public static void main(String[] args) throws ClassNotFoundException {
    // 方法一:直接通过类静态获取
    Class class1 = User.class;

    // 方法二:通过对象来进行获取
    User user = new User();
    Class class2 = user.getClass();

    // 方法三:通过类路径
    String classPath = "com.liu.ref_anno.User";
    Class class3 = Class.forName(classPath);
}

Class对象的常用方法

     public void test01() throws Exception {
        // 获取一个类的结构信息
        String path = "com.liu.ref_anno.User";
        Class clazz = Class.forName(path);
        // 获取类的完整名称,即com.liu.ref_anno.User
        System.out.println(clazz.getName());
        // 获取类名称,即User
        System.out.println(clazz.getSimpleName());
        // 获取类的父类
        System.out.println(clazz.getSuperclass());
        // 获取类的接口
        System.out.println(Arrays.toString(clazz.getInterfaces()));
    }

2.3 获取类中的成分

获取类的成分

  • 构造方法
  • 属性值
  • 普通方法

2.3.1 获取Class的构造方法

一般用new方法创建对象的时候都是调用的类的公开构造方法,使用反射机制也可以用这种方法来创建对象,所以首先要知道怎么获取构造方法
首先定义的User类有三个构造方法

    public User() {
    }

    private User(String name) {
        this.name = name;
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

获取构造方法

    @Test
    public void test02() throws Exception {
        // 获取一个类的结构信息
        String path = "com.liu.ref_anno.User";
        Class clazz = Class.forName(path);

        // 获取全部构造方法
        Constructor[] constructors = clazz.getConstructors();
        System.out.println(Arrays.toString(constructors));
        // 输出结果:[public com.liu.ref_anno.User(), public com.liu.ref_anno.User(java.lang.String,int)]
        // 可以找到public的构造方法

        // 获取无参数构造的构造方法
        Constructor declaredConstructor = clazz.getDeclaredConstructor();
        System.out.println(declaredConstructor);
        // 输出结果:public com.liu.ref_anno.User()

        // 获取有参构造的构造方法
        Constructor declaredConstructor1 = clazz.getDeclaredConstructor(String.class, int.class);
        System.out.println(declaredConstructor1);
        // 输出结果:public com.liu.ref_anno.User(java.lang.String,int)

        // 获取private有参的构造方法
        Constructor declaredConstructor2 = clazz.getDeclaredConstructor(String.class);
        System.out.println(declaredConstructor2);
        // 输出结果:private com.liu.ref_anno.User(java.lang.String)
        
    }

2.3.2 获取Class的属性值

    /**
     * public class User {
     *     public String name;
     *     private int age;
     * }
     */
    @Test
    public void test03() throws Exception {
        // 获取一个类的结构信息
        String path = "com.liu.ref_anno.User";
        Class clazz = Class.forName(path);

        // 获取所有属性(仅public)
        Field[] fields = clazz.getFields();
        System.out.println(Arrays.toString(fields));

        // 获取对应属性,public
        Field name = clazz.getField("name");
        System.out.println(name);

        // 获取对应属性,可以是private
        Field age = clazz.getDeclaredField("age");
        System.out.println(age);

        /**
         * 输出结果
         * [public java.lang.String com.liu.ref_anno.User.name]
         * public java.lang.String com.liu.ref_anno.User.name
         * private int com.liu.ref_anno.User.age
         */
    }

2.3.3 获取Class的普通方法

    @Test
    public void test04() throws Exception {
        // 获取一个类的结构信息
        String path = "com.liu.ref_anno.User";
        Class clazz = Class.forName(path);

        // 获取所有方法,仅public
        Method[] methods = clazz.getMethods();
        System.out.println(Arrays.toString(methods));

        // 获取特定方法(无参),仅public
        Method getName = clazz.getMethod("getName");
        System.out.println(getName);

        // 获取特定方法(无参),可private
        Method hello = clazz.getDeclaredMethod("hello");
        System.out.println(hello);

        // 获取特定方法(有参),可private
        Method setName = clazz.getDeclaredMethod("setName", String.class);
        System.out.println(setName);
    }

2.4 操作类中成分

操作类中成分

  • 实例化对象
  • 操作对象属性
  • 操作对象方法

2.4.1 实例化对象

使用反射来实例化对象有两种方法

  • 通过Class的newInstance()方法
  • 通过Constructor的newInstance()方法

通过Class的newInstance()方法创建对象

  1. 通过Class的newInstance()方法创建对象
    执行newInstance()实际上就是执行无参构造方法来创建该类的实例
    @Test
    public void test01() throws Exception {
        // 获取一个类的结构信息
        String path = "com.liu.ref_anno.Dog";
        Class clazz = Class.forName(path);
			
        Object o = clazz.newInstance();
        System.out.println(o instanceof Dog);
    }
  1. 通过Constructor的newInstance()方法,无参构造
    @Test
    public void test02() throws Exception {
        // 获取一个类的结构信息
        String path = "com.liu.ref_anno.Dog";
        Class clazz = Class.forName(path);
        // 获取无参构造方法
        Constructor constructor = clazz.getConstructor();
        // 通过无参构造方法来实例化对象
        Object o = constructor.newInstance();
        System.out.println(o instanceof Dog);
    }
  1. 通过Constructor的newInstance()方法,有参构造
    既可以操作public的构造方法,也可以操作private的构造方法
    @Test
    public void test03() throws Exception {
        // 获取一个类的结构信息
        String path = "com.liu.ref_anno.Dog";
        Class clazz = Class.forName(path);
        // 获取有参构造方法,public
        Constructor constructor = clazz.getConstructor(String.class, String.class);
        // 通过有参构造方法来实例化对象
        Object o = constructor.newInstance("旺财", "yellow");
        System.out.println(o);

        // 获取有参的构造方法,private
        Constructor declaredConstructor = clazz.getDeclaredConstructor(String.class);
        // private方法要突破封装访问权限的限制
        declaredConstructor.setAccessible(true);
        // 通过有参构造方法来实例化对象
        Object o1 = declaredConstructor.newInstance("大黄");
        System.out.println(o1);
    }

2.4.2 操作对象属性

使用方法:

  • set(Object, Object)
  • get(Object)
    @Test
    public void test04() throws Exception {
        // 获取一个类的结构信息
        String path = "com.liu.ref_anno.Dog";
        Class clazz = Class.forName(path);
        Object dog = clazz.getConstructor().newInstance();
        // 获取属性
        Field name = clazz.getDeclaredField("name");
        // 突破访问限制
        name.setAccessible(true);
        // 设置属性值
        name.set(dog, "旺财");
        System.out.println(dog);
        // 过去属性值
        System.out.println(name.get(dog));
    }

2.4.3 操作对象方法

使用方法

  • invoke(Object obj, Object… args)
    @Test
    public void test05() throws Exception {
        // 获取一个类的结构信息
        String path = "com.liu.ref_anno.Dog";
        Class clazz = Class.forName(path);
        Object o = clazz.getConstructor().newInstance();
        // 获取方法
        Method setName = clazz.getMethod("setName", String.class);
        // 执行方法
        setName.invoke(o, "旺财");
        System.out.println(o);
    }

2.5 使用反射操作泛型

这部分还没看懂,先跳过,今后补充…

三、注解

3.1 注解介绍

注解是从Java5开始引入的一种技术,使用@+名称 就可以对类、方法、参数等内容添加一个描述功能。

在没有出现注解之前,许多技术框架都需要编写大量的XML配置。由于XML文档的结构性问题,想要编写一个文本节点,可能需要2层、3层甚至更多层的嵌套才能实现,但这些问题都可以通过注解来简化。

3.2 常用内置注解

  • @Override:在方法重写时,可以在方法上添加@Override注解来检查方法是否满足重写的语法要求。
  • @Deprecated:如果在方法上添加这个注解,表示该方法已经过时。过时的方法现在还能使用,但已经不再维护了,不推荐使用
  • @SuppressWarning:指示编译器区忽略注解中声明的警告
  • @FunctionallInterface:Java8开始支持,标识一个匿名函数或函数式接口

3.3 元注解

看注解的定义中,可以看到每个注解名称上还有几个其他注解。这些注解称为元注解(Meta-annotation)
Java中共有四个元注解:

  • @Target
  • @Retention
  • @Documented
  • @Inherited

这四个元注解不是必须同时使用的,一般情况下只需要用到@Target和@Retention,另外两个元注解要根据自己的情况添加。

3.3.1 @Target

@Target用于指定注解可以使用的位置
注解里面包含了ElementType[]

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * 注解可以用于哪些范围的数组
     */
    ElementType[] value();
}

ElementType是一个枚举类型

public enum ElementType {
    /** 类、接口、注解、枚举 */
    TYPE,
    /** 属性值,包括枚举值 */
    FIELD,
    /** 方法定义 */
    METHOD,
    /** 参数声明 */
    PARAMETER,
    /** 构造函数声明 */
    CONSTRUCTOR,
    /** 局部变量声明 */
    LOCAL_VARIABLE,
    /** 注解声明 */
    ANNOTATION_TYPE,
    /** 包声明 */
    PACKAGE,

    /**
     * 类型参数声明
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * 类型使用声明
     * @since 1.8
     */
    TYPE_USE
}

3.3.2 @Retention

@Retention注解用来标识被注解的元素什么时候起作用

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * RetentionPolicy表示注解的保留策略
     */
    RetentionPolicy value();
}

RetentionPolicy是一个枚举值

public enum RetentionPolicy {
    /**
     * 源文件,注解将被编译器丢弃
     */
    SOURCE,

    /**
     * 字节码,注解在class文件可用,但会被JVM丢弃
     */
    CLASS,

    /**
     * 运行时,常用这个,注解在JVM也会保留,可以通过反射机制读取注解信息
     */
    RUNTIME
}

3.3.3 @Documented

生成帮助文档(javadoc)时,是否保留注解信息,如果希望保留就添加这个注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

3.3.4 @Inherited

如果一个注解添加了@Inherited,那么一个类被这个注解修饰后,其子类也自动添加这个注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

3.4 自定义注解

我们也可以理解元注解进行自定义注解,合理的自定义注解可以简化程序开发。

需求:定义一个注解用来检查属性值中的值大小,注解中传入最大值和最小值,待检查的属性值必须在最大值和最小值之间

1.定义注解

@Retention(value = RetentionPolicy.RUNTIME)
public @interface MyCheck {
    int max() default 100;
    int min() default 1;
}

2.使用反射实现注解的功能

public class MyCheckImpl {
    public static boolean check(Object obj) {
        // 1.获取类对象
        Class clazz = obj.getClass();
        // 2.获取所有属性对象
        Field[] fields = clazz.getDeclaredFields();
        // 3.遍历每个属性对象
        try {
            for (Field field : fields) {
                // 设置可访问
                field.setAccessible(Boolean.TRUE);
                // 4.判断属性值是否有MyCheck注解
                if (field.isAnnotationPresent(MyCheck.class)) {
                    // 5.获取注解中的属性值
                    MyCheck annotation = field.getAnnotation(MyCheck.class);
                    int max = annotation.max();
                    int min = annotation.min();
                    if (min > field.getInt(obj)) {
                        throw new RuntimeException("当前值为:" + field.getInt(obj) + ", 小于最小值:" + min);
                    }
                    if (max < field.getInt(obj)) {
                        throw new RuntimeException("当前值为:" + field.getInt(obj) + ", 大于最大值:" + max);
                    }
                }
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
}

3.使用注解进行测试

public class Dog {
    private String name;
    private String color;
    @MyCheck(max = 100, min = 1)
    private int age;
}

public class Demo03 {
    public static void main(String[] args) {
        Dog dog = new Dog("旺财", "yellow", 101);
        // 目前只能使用直接调用的方法进行检查
        MyCheckImpl.check(dog);
    }
}

/**
 * 输出结果
 * Exception in thread "main" java.lang.RuntimeException: 当前值为:101, 大于最大值:100
 * 	at com.liu.ref_anno.MyCheckImpl.check(MyCheckImpl.java:26)
 * 	at com.liu.ref_anno.Demo03.main(Demo03.java:6)
 */

四、SpringBoot中自定义注解

在SpringBoot中自定义注解的一般场景为:自定义注解+拦截器或AOP

4.1 自定义注解+拦截器

定义一个注解来判断请求是否需要验证登录

  1. 定义注解LoginRequired
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {
    boolean isRequired() default true;
}
  1. 设置拦截器
public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        System.out.println(method.getName() + "=====请求前");
        // 检查是否需要验证
        if (method.isAnnotationPresent(LoginRequired.class)) {
            LoginRequired annotation = method.getAnnotation(LoginRequired.class);
            boolean required = annotation.isRequired();
            if (required) {
                return false;
            }
        }
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        System.out.println(method.getName() + "=====请求后");
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        System.out.println(method.getName() + "=====所有请求结束后");
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}
  1. 配置拦截器
@Configuration
public class MyInterceptorConfig extends WebMvcConfigurationSupport {

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        // 注册我的拦截器
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
        super.addInterceptors(registry);
    }
}
  1. 进行测试
@RestController
public class HelloController {

    @GetMapping("/method1")
    public String method1() {
        return "this is source1";
    }

    @LoginRequired(isRequired = true)
    @GetMapping("/method2")
    public String method2() {
        return "this is source2";
    }

}

4.1 自定义注解+AOP

  1. 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AopLog {
    String value() default "自动移注解日志";
}
  1. 自定义AOP
@Aspect
@Component
public class AopAspect {

    // 切入点
    @Pointcut("execution(public * com.example.springdemo02.controller.HelloController.*(..))")
    public void doPointCut() {}

    @Before("doPointCut()")
    public void deBefore(JoinPoint joinPoint) throws NoSuchMethodException, ClassNotFoundException {
        // 获取对应的类名
        String targetName = joinPoint.getTarget().getClass().getName();
        // 获取对应的方法名
        String methodName = joinPoint.getSignature().getName();
        // 获取对应的请求参数
        Object[] arguments = joinPoint.getArgs();
        // 根据类名获取对应类
        Class targetClass = Class.forName(targetName);
        // 获取类中对应方法
        Method[] methods = targetClass.getMethods();
        StringBuilder value = new StringBuilder();
        for (Method method : methods) {
            // 通过方法名来找到对应方法
            if (method.getName().equals(methodName)) {
                Class[] clazzs = method.getParameterTypes();
                // 判断参数是否相同
                if (clazzs.length == arguments.length) {
                    // 获取注解对应的日志值
                    value.append(method.getAnnotation(AopLog.class).value());
                    break;
                }
            }
        }
        System.out.println(value.toString());
    }

    @AfterReturning(value = "doPointCut()", returning = "returnValue")
    public void doAfterReturning(JoinPoint point, Object returnValue){
        String name = point.getSignature().getName();
        Object[] args = point.getArgs();
        System.out.println(name + "后:" + Arrays.toString(args).toString());
    }
}
  1. 定义测试
@RestController
public class HelloController {

    @GetMapping("/hello")
    @AopLog(value = "Get方法请求/hello")
    public String hello(String params) {
        return "hello world";
    }

}

五、总结

学到这里相信大家已经对反射和注解有了基本的了解,这将会是今后学习框架的基础。

references:
1.https://blog.csdn.net/sco5282/article/details/122075217
2.https://blog.csdn.net/liucy007/article/details/123827715
3.https://blog.csdn.net/zt15732625878/article/details/100061528

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值