一文搞懂Spring中的注解与反射

目录
  • 前言
  • 一、内置(常用)注解
    • 1.1@Overrode
    • 1.2@RequestMapping
    • 1.3@RequestBody
    • 1.4@GetMapping
    • 1.5@PathVariable
    • 1.6@RequestParam
    • 1.7@ComponentScan
    • 1.8@Component
    • 1.9@Service
    • 1.10@Repository
  • 二、元注解
    • @Target
    • @Retention
    • @Documented
    • @Inherited
  • 三、自定义注解
  • 四、反射机制概述
    • 4.1动态语言与静态语言
    • 4.2Java Reflection(Java 反射)
  • 五、理解Class类并获取Class实例
    • 5.1Class类
    • 5.2获取Class类实例
    • 5.3可获得Class对象的类型
  • 六、类的加载与ClassLoader
    • 6.1类的加载过程
    • 6.2类加载器
  • 七、获取运行时类的完整对象
  • 八、反射获取泛型信息
  • 九、反射获取注解信息

前言

注解(Annotation)不是程序,但可以对程序作出解释,也可以被其它程序(如编译器)读取。

注解的格式:以@注释名在代码中存在,还可以添加一些参数值例如@SuppressWarnings(value="unchecked")。

注解可在package、class、method、field等上面使用,作用是为它们添加了额外的辅助信息,从而可以通过反射机制实现对这些元数据的访问。

一、内置(常用)注解

1.1@Overrode

表示某方法旨在覆盖超类中的方法声明,该方法将覆盖或实现在超类中声明的方法。

1.2@RequestMapping

@RequestMapping注解的主要用途是将Web请求与请求处理类中的方法进行映射,注意有以下几个属性:

  • value:映射的请求URL或者其别名
  • value:映射的请求URL或者其别名
  • params:根据HTTP参数的存在、缺省或值对请求进行过滤

1.3@RequestBody

@RequestBody在处理请求方法的参数列表中使用,它可以将请求主体中的参数绑定到一个对象中,请求主体参数是通过HttpMessageConverter传递的,根据请求主体中的参数名与对象的属性名进行匹配并绑定值。此外,还可以通过@Valid注解对请求主体中的参数进行校验。

1.4@GetMapping

@GetMapping注解用于处理HTTP GET请求,并将请求映射到具体的处理方法中。具体来说,@GetMapping是一个组合注解,它相当于是@RequestMapping(method=RequestMethod.GET)的快捷方式。

1.5@PathVariable

@PathVariable注解是将方法中的参数绑定到请求URI中的模板变量上。可以通过@RequestMapping注解来指定URI的模板变量,然后使用@PathVariable注解将方法中的参数绑定到模板变量上。

1.6@RequestParam

@RequestParam注解用于将方法的参数与Web请求的传递的参数进行绑定。使用@RequestParam可以轻松的访问HTTP请求参数的值。

1.7@ComponentScan

@ComponentScan注解用于配置Spring需要扫描的被组件注解注释的类所在的包。可以通过配置其basePackages属性或者value属性来配置需要扫描的包路径。value属性是basePackages的别名。

1.8@Component

@Component注解用于标注一个普通的组件类,它没有明确的业务范围,只是通知Spring被此注解的类需要被纳入到Spring Bean容器中并进行管理。

1.9@Service

@Service注解是@Component的一个延伸(特例),它用于标注业务逻辑类。与@Component注解一样,被此注解标注的类,会自动被Spring所管理。

1.10@Repository

@Repository注解也是@Component注解的延伸,与@Component注解一样,被此注解标注的类会被Spring自动管理起来,@Repository注解用于标注DAO层的数据持久化类。

二、元注解

4个元个元注解分别是:@Target、@Retention、@Documented、@Inherited 。

再次强调下元注解是java API提供,是专门用来定义注解的注解。

@Target

描述注解能够作用的位置,ElementType取值:

  • ElementType.TYPE,可以作用于类上
  • ElementType.METHOD,可以作用于方法上
  • ElementType.FIELD,可以作用在成员变量上

@Retention

表示需要在什么级别保存该注释信息(生命周期):

RetentionPolicy.RUNTIME:内存中的字节码,VM将在运行时也保留注解,因此可以通过反射机制读取注解的信息

@Documented

描述注解是否被抽取到api文档中。

@Inherited

描述注解是否被子类继承。

三、自定义注解

学习自定义注解对于理解Spring框架十分有好处,即使在实际项目中可能不需要使用自定义注解,但可以帮助我们掌握Spring的一些底层原理,从而提高对整体项目的把握。

/**
 * 自定义注解
 * @author Created by zhuzqc on 2022/5/31 23:03
 */
public class CustomAnnotation {

    /**
     * 注解中可以为参数赋值,如果没有默认值,那么必须为注解的参数赋值
     * */
    @MyAnnotation(value = "解释")
    public void test(){
    }
}
/**
 * @author zhuzqc
 */
//自定义注解必须的元注解target,指明注解的作用域(此处指明的是在类和方法上起作用)
@Target({ElementType.TYPE,ElementType.METHOD})
//元注解retention声明该注解在何时起作用(此处指明的是在运行时起作用)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation{

    //注解中需声明参数,格式为:参数类型 + 参数名();
    String value() default "";

}

四、反射机制概述

4.1动态语言与静态语言

4.1.1动态语言

是一种在运行时可以改变其结构的语言,例如新的函数、对象甚至代码可以被引进,已有的函数可以被删除或是进行其它结构上的变化。

主要的动态语言有:Object-C、C#、PHP、Python、JavaScript 等。

以 JavaScript 语言举例:

/**
 * 由于未指定var的具体类型,函数在运行时间可以改变var的类型
 * */
function f(){
    var x = "var a = 3; var b = 5; alert(a+b)";
    eval(x)
}

4.2.2静态语言

与动态语言相对的、运行时结构不可变的语言就是静态语言,如 Java、C、C++ 等。

Java 不是动态语言,但 Java 可以称为”准动态语言“。即 Java 有一定的动态性,可以利用反射机制获得类似于动态语言的特性,从而使得 Java 语言在编程时更加灵活。

4.2Java Reflection(Java 反射)

Reflection(反射)是 Java 被视为准动态语言的关键:反射机制允许程序在执行期间借助 Reflection API 获取任何类的内部信息,并能直接操作任意对象的内部属性及方法。

Class c = Class.forName("java.lang.String")

加载完类后,在堆内存的方法区就产生了一个Class类型的对象(一个类只有一个Class对象),这个类就包含了完整的类的结构信息。我没可以通过这个对象,像镜子一样看到类的结构,这个过程形象地被称之为反射。

通过代码更易于理解:

/**
 * 反射的概念
 * @author Created by zhuzqc on 2022/6/1 17:40
 */
public class ReflectionTest extends Object{
    public static void main(String[] args) throws ClassNotFoundException {
        //通过反射获取类的Class对象
        Class c = Class.forName("com.dcone.zhuzqc.demo.User");
        //一个类在内存中只有唯一个Class对象
        System.out.println(c.hashCode());

    }
}

/**
 * 定义一个实体类entity
 * */
@Data
class User{
    private String userName;
    private Long userId;
    private Date loginTime;
}

由于该类继承 Object,在 Object 类中有 getClass() 方法,该方法被所有子类继承:

@HotSpotIntrinsicCandidate
public final native Class<?> getClass();

注:该方法的返回值类型是一个 Class 类,该类是 Java 反射的源头。

反射的优点运行期类型的判断、动态加载类、提高代码灵活度

4.2.1反射机制主要功能

  • 在运行时判断、调用任意一个类的对象信息(成员变量和方法等);
  • 在运行时获取泛型信息;
  • 在运行时处理注解;
  • 生成动态代理。

4.2.2主要API

  • java.lang.Class:代表一个类
  • java.lang.reflect.Field:代表类的成员变量
  • java.lang.reflect.Method:代表类的方法
  • java.lang.reflect.Constructor:代表类的构造器

五、理解Class类并获取Class实例

5.1Class类

前面提到,反射后可以得到某个类的属性、方法和构造器、实现的接口。

  • 对于每个类而言,JRE都为其保留一个不变的 Class 类型的对象;
  • 一个加载的类在 JVM 中只会有一个 Class 实例;
  • Class 类是Reflection的根源,想要通过反射获得任何动态加载的、运行的类,都必须先获取相应的 Class 对象。

5.2获取Class类实例

有以下5种方式可以获取Class类的实例:

1.若已知具体的类,可以通过类的class属性获取,该fang'shi最为安全可靠,且程序性能最高。

//类的class属性
Class classOne = User.class;

2. 已知某个类的实例,通过调用该实例的getClass方法获取Class对象。

   //已有类对象的getClass方法
   Class collatz = user.getClass();

3.已知一个类的全类名,且该类在类路径下,可以通过静态方法forName()获取。

Class c = Class.forName("com.dcone.zhuzqc.demo.User");

4.内置基本数据类型可以直接使用类名.Type获取。

//内置对象才有的TYPE属性,较大的局限性
Class<Integer> type = Integer.TYPE;

5.利用ClassLoader(类加载器)获取。

5.3可获得Class对象的类型

1.class:外部类、成员(成员内部类,静态内部类),局部内部类,匿名内部类;

//类可以反射
    Class c1 = Person.class;

2.interface:所有接口;

//接口可以反射
     Class c2 = Comparable.class;

3.[]:数组;

//数组可以反射
     Class c3 = String[].class;
     Class c4 = int[][].class;

4.enum:枚举;

//枚举可以反射
     Class c6 = ElementType.class;

5.annotation:注解(@interface);

//注解可以反射
     Class c5 = Data.class;

6.基本数据类型;

//基本数据类型(包装类)可以反射
     Class c7 = int.class;
     Class c8 = Integer.class;

7.void。

//void可以反射
     Class c9 = void.class;

六、类的加载与ClassLoader

6.1类的加载过程

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下3个步骤来对该类进行初始化。

1.类的加载(Load):将类的 class 文件字节码内容读入内存,并将这些静态数据转换成方法区运行时的数据结构,同时创建一个java.lang.Class对象,此过程由类加载器完成;

2.类的链接(Link):将类的二进制数据合并到 JRE 中,确保加载的类信息符合 JVM 规范,同时 JVM 将常量池内的引用替换为地址。

3.类的初始化(Initialize):JVM 负责对类进行初始化,分为类的主动引用和被动引用。

类的主动引用

  • 虚拟器启动时,先初始化main方法所在的类;
  • new 类的对象;
  • 调用类的静态(static)成员和静态(static)方法;
  • 使用java.lang.reflect包的方法对类进行反射调用;
  • 如果该类的父类没有被初始化,则会先初始化它的父类。

类的被动引用

  • 当访问到一个静态域时,只有真正声明这个域的类才会被初始化;
  • 通过数组定义类的引用,不会触发此类的初始化;
  • 引用常量不会触发此类的初始化

6.2类加载器

JVM支持两种类型的类加载器,分别为引导类加载器(BootstrapClassLoader)和自定义类加载器(User-Defined ClassLoader)。

从概念上来讲,自定义类加载器一般指的是程序中由开发人员自定义的一类,类加载器。

但是Java虚拟机规范却没有这么定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器。

无论类加载器的类型如何划分,在程序中我们最常见的类加载器始终只有3个,具体如下图所示:

类加载器

所以具体为引导类加载器(BootstrapClassLoader)和自定义类加载器(包括ExtensionClassLoader、Application ClassLoader(也叫System ClassLoader)、User Defined ClassLoader)。

public class Test03 {
    public static void main(String[] args) {
        //获取系统类的加载器
        ClassLoader sysLoader = ClassLoader.getSystemClassLoader();
        System.out.println(sysLoader);

        //获取系统类的父类加载器
        ClassLoader parent = sysLoader.getParent();
        System.out.println(parent);
    }
}

七、获取运行时类的完整对象

通过反射获取运行时类的完整结构:Field、Method、Constructor、Superless、Interface、Annotation等。

即:实现的全部接口、所继承的父类、全部的构造器、全部的方法、全部的成员变量(局部变量)、注解等。

/**
 * @author Created by zhuzqc on 2022/6/5 0:16
 */
public class Test04 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class c1 = Class.forName("com.dcone.zhuzqc.demo.User");
        //获取所有属性
        Field field[];
        field = c1.getDeclaredFields();
        for (Field f:field){
            System.out.println(f);
        }
        //获得类的方法
        Method method[];
        method = c1.getDeclaredMethods();
        for (Method m:method){
            System.out.println(m);
        }
    }
}

八、反射获取泛型信息

Java 中采用泛型擦除的机制来引入泛型,Java 中的泛型仅仅是给编译器 javac 使用的,目的是确保数据的安全性以及免去强制类型转换的问题。一旦编译完成,所有和泛型相关的类型全部擦除。

在Java中可以通过反射获取泛型信息的场景有如下三个:

  • (1)成员变量的泛型
  • (2)方法参数的泛型
  • (3)方法返回值的泛型

在Java中不可以通过反射获取泛型信息的场景有如下两个:

  • (1)类或接口声明的泛型
  • (2)局部变量的泛型

要获取泛型信息,必须要注意ParameterizedType类,该类中的getActualTypeArguments()方法可以有效获取泛型信息。

下面以获取成员方法参数的泛型类型信息为例:

public class Demo {
    public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException {

        // 获取成员方法参数的泛型类型信息
        getMethodParametricGeneric();
    }
 /**
     * 获取方法参数的泛型类型信息
     *
     * @throws NoSuchMethodException
     */
    public static void getMethodParametricGeneric() throws NoSuchMethodException {
        // 获取MyTestClass类中名为"setList"的方法
        Method setListMethod = MyClass.class.getMethod("setList", List.class);
        // 获取该方法的参数类型信息(带有泛型)
        Type[] genericParameterTypes = setListMethod.getGenericParameterTypes();
        // 但我们实际上需要获取返回值类型中的泛型信息,所以要进一步判断,即判断获取的返回值类型是否是参数化类型ParameterizedType
        for (Type genericParameterType : genericParameterTypes) {
            ParameterizedType parameterizedType = (ParameterizedType) genericParameterType;
            // 获取成员方法参数的泛型类型信息
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                Class realType = (Class) actualTypeArgument;
                System.out.println("成员方法参数的泛型信息:" + realType);
            }
        }
    }

九、反射获取注解信息

在开发中可能会遇到这样的场景:获取类的属性释义,这些释义定义在类属性的注解中。

/**
 * 定义一个实体类entity
 * */
@Data
class User{
    @ApiModelProperty(value = "姓名")
    private String userName;

    @ApiModelProperty(value = "用户id")
    private Long userId;

    @ApiModelProperty(value = "登录时间")
    private Date loginTime;
}

那么可以如何获取注解中的属性信息呢?

解决方案:

这里我们使用反射,以及java.lang下的两个方法:

//如果指定类型的注释存在于此元素上,  方法返回true
java.lang.Package.isAnnotationPresent(Class<? extends Annotation> annotationClass)
//如果是该类型的注释, 方法返回该元素的该类型的注释
java.lang.Package.getAnnotation(Class< A > annotationClass)
    public static void main(String[] args) throws ClassNotFoundException {
        Class c1 = Class.forName("com.dcone.zhuzqc.demo.User");

        if(User.class.isAnnotationPresent(ApiModel.class)){
            System.out.println(User.class.getAnnotation(ApiModel.class).value());
        }
        // 获取类变量注解
        Field[] fields = User.class.getDeclaredFields();
        for (Field f : fields) {
            if(f.isAnnotationPresent(ApiModelProperty.class)){
                System.out.print(f.getAnnotation(ApiModelProperty.class).name() + ",");
            }
        }
    }

拓展1:获取方法上的注解

    @Bean("sqlSessionFactory")
    public String test(@RequestBody User user) throws ClassNotFoundException {
        Class c2 = Class.forName("com.dcone.zhuzqc.demo.User");
        // 获取方法注解:
        Method[] methods = User.class.getDeclaredMethods();
        for(Method m : methods){
            if (m.isAnnotationPresent((Class<? extends Annotation>) User.class)) {
                System.out.println(m.getAnnotation(ApiModelProperty.class).annotationType());
            }
        }
        return "test";
    }

拓展2:获取方法参数上的注解

    @Bean("sqlSessionFactory")
    public String test(@RequestBody User user) throws ClassNotFoundException {
        Class c2 = Class.forName("com.dcone.zhuzqc.demo.User");
        // 获取方法参数注解
        Method[] methods2 = User.class.getDeclaredMethods();
        for (Method m : methods2) {
            // 获取方法的所有参数
            Parameter[] parameters = m.getParameters();
            for (Parameter p : parameters) {
                // 判断是否存在注解
                if (p.isAnnotationPresent(ApiModelProperty.class)) {
                    System.out.println(p.getAnnotation(ApiModelProperty.class).name());
                }
            }
        }
        return "test";
    }

以上就是一文搞懂Spring中的注解与反射的详细内容,更多关于Spring注解 反射的资料请关注我们其它相关文章!

(0)

相关推荐

  • Spring常用注解及http数据转换教程

    注意:本节内容需要结合2.2.2小节的内容联合起来一起看,然后理解 一.HTTP协议的四种传参方式 HTTP协议组成 协议内容示例 对应Spring注解 path info传参 /articles/12 (查询id为12的文章,12是参数) @PathVariable URL Query String传参 /articles?id=12 @RequestParam Body 传参 Content-Type: multipart/form-data @RequestParam Body 传参 Co

  • Spring框架学习常用注解汇总

    目录 类注解 方法或属性上注解 参数注解 类注解 @component 标注类,泛指各种组件,类不属于各种分类的时候,用它做标注. @Service 标注类,声明该类为业务层组件,用于处理业务逻辑 @Repositor 标注类,声明该类为持久层的接口.使用后,在启动主程序类上需要添加@MapperScan("xxx.xxx.xxx.mapper")注解 @Mapper 标注类,用在持久层的接口上,注解使用后相当于@Reponsitory加@MapperScan注解,会自动进行配置加载

  • CSS的三大样式你了解多少

    目录 CSS样式 行内式 内部式 外部式 多重样式优先级 总结 CSS样式 行内式 又叫:内联样式.行内样式.嵌入式样式 <!-- style作为属性直接写在标签后面, style属性可以包含任何 CSS属性 --> <div style="font-size: 40px; color: #FF0000;">我是div</div> <div style="font-size: 40px; color: blue;">我

  • Spring详解使用注解开发流程

    目录 在Spring4之后 要使用注解开发 必须保证aop包导入了 使用注解需要导入context约束 增加 注解的支持 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance

  • Spring注解@Autowired背后实现的原理

    目录 前言 使用spring开发时,进行配置主要有两种方式,一是xml的方式,二是java config的方式. spring技术自身也在不断的发展和改变,从当前springboot的火热程度来看,java config的应用是越来越广泛了,在使用java config的过程当中,我们不可避免的会有各种各样的注解打交道,其中,我们使用最多的注解应该就是@Autowired注解了.这个注解的功能就是为我们注入一个定义好的bean. 那么,这个注解除了我们常用的属性注入方式之外还有哪些使用方式呢?它

  • 一文搞懂Spring中的注解与反射

    目录 前言 一.内置(常用)注解 1.1@Overrode 1.2@RequestMapping 1.3@RequestBody 1.4@GetMapping 1.5@PathVariable 1.6@RequestParam 1.7@ComponentScan 1.8@Component 1.9@Service 1.10@Repository 二.元注解 @Target @Retention @Documented @Inherited 三.自定义注解 四.反射机制概述 4.1动态语言与静态语

  • 一文搞懂Java中的注解和反射

    目录 1.注解(Annotation) 1.1 什么是注解(Annotation) 1.2 内置注解 1.3 元注解(meta-annotation) 1.4 自定义注解 2.反射(Reflection) 2.1 反射和反射机制 2.2 Class类的获取方式和常用方法 2.3 反射的使用 1.注解(Annotation) 1.1 什么是注解(Annotation) 注解不是程序本身,可以在程序编译.类加载和运行时被读取,并执行相应的处理.注解的格式为"@注释名(参数值)",可以附加在

  • 一文搞懂SpringMVC中@InitBinder注解的使用

    目录 简介 应用示例 原理解读 环境:Springboot2.4.12 简介 ​@Controller或@ControllerAdvice类可以有@InitBinder方法来初始化WebDataBinder的实例,这些方法可以: 将请求参数(即表单或查询数据)绑定到模型对象. 将基于字符串的请求值(如请求参数.路径变量.头.cookie等)转换为控制器方法参数的目标类型. 渲染HTML表单时,将模型对象的值格式化为字符串值. @InitBinder方法可以注册控制器特定的java.bean.Pr

  • 一文搞懂Spring中@Autowired和@Resource的区别

    目录 1.来源不同 2.依赖查找顺序不同 2.1 @Autowired 查找顺序 2.2 @Resource 查找顺序 2.3 查找顺序小结 3.支持的参数不同 4.依赖注入的支持不同 5.编译器提示不同 总结 @Autowired 和 @Resource 都是 Spring/Spring Boot 项目中,用来进行依赖注入的注解.它们都提供了将依赖对象注入到当前对象的功能,但二者却有众多不同,并且这也是常见的面试题之一,所以我们今天就来盘它. @Autowired 和 @Resource 的区

  • 一文搞懂Spring中的Bean作用域

    目录 概述 Singleton prototype request session application 概述 scope用来声明容器中的对象所应该处的限定场景或者说该对象的存活时间,即容器在对象进入其 相应的scope之前,生成并装配这些对象,在该对象不再处于这些scope的限定之后,容器通常会销毁这些对象. Spring容器bean的作用域类型: singleton:Spring IoC 容器的单个对象实例作用域都默认为singleton prototype:针对声明为拥有prototyp

  • 一文搞懂Spring中Bean的生命周期

    目录 一.使用配置生命周期的方法 二.生命周期控制——接口控制(了解) 小结 生命周期:从创建到消亡的完整过程 bean声明周期:bean从创建到销毁的整体过程 bean声明周期控制:在bean创建后到销毁前做一些事情 一.使用配置生命周期的方法 在BookDaoImpl中实现类中创建相应的方法: //表示bean初始化对应的操作 public void init(){ System.out.println("init..."); } //表示bean销毁前对应的操作 public v

  • 一文搞懂Spring中的JavaConfig

    目录 配置类 注册组件 扫描包配置 事务注解驱动 单元测试加载配置类 properties配置文件加载(了解) aspectj注解开关 传统spring一般都是基于xml配置的,不过后来新增了许多JavaConfig的注解.特别是springboot,基本都是清一色的java config,不了解一下,还真是不适应.这里给大家普及下Spring中的JavaConfig知识. 什么是JavaConfig.通过注解和配置类完成Spring的相关配置 Spring配置都做了什么? 注册组件.其他配置(

  • 教你一文搞懂Kotlin中的Jvm注解

    JvmOverloads 创建一个kotlin的类 class Student(val name: String, val sex: Int = 1, val age: Int = 18) 可以看出来 这个构造函数的参数是有默认值的,kotlin的特性对吧,我们在使用的时候可以方便的使用,比如: val student = Student("wuyue") val student2 = Student("wuyue", age = 18) 但是这个特性如果你用jav

  • 一文搞懂Spring Bean中的作用域和生命周期

    目录 一.Spring Bean 作用域 singleton(单例) prototype(原型) 小结 二.Spring Bean生命周期 如何关闭容器 生命周期回调 通过接口设置生命周期 通过xml设置生命周期 一.Spring Bean 作用域 常规的 Spring IoC 容器中Bean的作用域有两种:singleton(单例)和prototype(非单例) 注:基于Web的容器还有其他种作用域,在这就不赘述了. singleton(单例) singleton是Spring默认的作用域.当

  • 一文搞懂Spring AOP的五大通知类型

    目录 一.通知类型 二.环境准备 添加AOP依赖 创建目标接口和实现类 创建通知类 创建Spring核心配置类 编写运行程序 三.添加通知 普通通知 环绕通知(重点) 一.通知类型 Advice 直译为通知,也有人翻译为 “增强处理”,共有 5 种类型,如下表所示. 通知类型 注解 说明 before(前置通知) @Before 通知方法在目标方法调用之前执行 after(后置通知) @After 通知方法在目标方法返回或异常后调用 after-returning(返回通知) @AfterRet

随机推荐