【Java 进阶】详细探究 Spring 框架中的注解与反射
【进阶】Spring中的注解与反射
前言
注解(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类的实例:
若已知具体的类,可以通过类的class属性获取,该fang'shi最为安全可靠,且程序性能最高。
//类的class属性
Class classOne = User.class;
已知某个类的实例,通过调用该实例的getClass方法获取Class对象。
//已有类对象的getClass方法
Class collatz = user.getClass();
已知一个类的全类名,且该类在类路径下,可以通过静态方法forName()获取。
Class c = Class.forName("com.dcone.zhuzqc.demo.User");
内置基本数据类型可以直接使用类名.Type获取。
//内置对象才有的TYPE属性,较大的局限性
Class<Integer> type = Integer.TYPE;
利用ClassLoader(类加载器)获取。
5.3可获得Class对象的类型
class:外部类、成员(成员内部类,静态内部类),局部内部类,匿名内部类;
//类可以反射
Class c1 = Person.class;
interface:所有接口;
//接口可以反射
Class c2 = Comparable.class;
[]:数组;
//数组可以反射
Class c3 = String[].class;
Class c4 = int[][].class;
enum:枚举;
//枚举可以反射
Class c6 = ElementType.class;
annotation:注解(@interface);
//注解可以反射
Class c5 = Data.class;
基本数据类型;
//基本数据类型(包装类)可以反射
Class c7 = int.class;
Class c8 = Integer.class;
void。
//void可以反射
Class c9 = void.class;
六、类的加载与ClassLoader
6.1类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下3个步骤来对该类进行初始化。
类的加载(Load):将类的 class 文件字节码内容读入内存,并将这些静态数据转换成方法区运行时的数据结构,同时创建一个java.lang.Class对象,此过程由类加载器完成;
类的链接(Link):将类的二进制数据合并到 JRE 中,确保加载的类信息符合 JVM 规范,同时 JVM 将常量池内的引用替换为地址。
类的初始化(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";
}
以上就是我学习 Java 注解与反射时候的笔记了,如有不足和错误,期待大家的指正和交流。
【Java 进阶】详细探究 Spring 框架中的注解与反射的更多相关文章
- 详解Java的Spring框架中的注解的用法
转载:http://www.jb51.net/article/75460.htm 1. 使用Spring注解来注入属性 1.1. 使用注解以前我们是怎样注入属性的 类的实现: class UserMa ...
- spring框架中的注解
- Spring框架中IoC(控制反转)的原理(转)
原文链接:Spring框架中IoC(控制反转)的原理 一.IoC的基础知识以及原理: 1.IoC理论的背景:在采用面向对象方法设计的软件系统中,底层实现都是由N个对象组成的,所有的对象通过彼此的合作, ...
- 【进阶】Spring中的注解与反射
[进阶]Spring中的注解与反射 目录 [进阶]Spring中的注解与反射 前言 一.内置(常用)注解 1.1@Overrode 1.2@RequestMapping 1.3@RequestBody ...
- 再析在spring框架中解决多数据源的问题
在前面我写了<如何在spring框架中解决多数据源的问题>,通过设计模式中的Decorator模式在spring框架中解决多数据源的问题,得到了许多网友的关注.在与网友探讨该问题的过程中, ...
- Spring框架中 配置c3p0连接池 完成对数据库的访问
开发准备: 1.导入jar包: ioc基本jar jdbcTemplate基本jar c3p0基本jar 别忘了mysql数据库驱动jar 原始程序代码:不使用配置文件方式(IOC)生成访问数据库对象 ...
- Spring框架中ModelAndView、Model、ModelMap区别
原文地址:http://www.cnblogs.com/google4y/p/3421017.html SPRING框架中ModelAndView.Model.ModelMap区别 注意:如果方法 ...
- Spring框架中的定时器 使用和配置
Spring框架中的定时器 如何使用和配置 转载自:<Spring框架中的定时器 如何使用和配置>https://www.cnblogs.com/longqingyang/p/554543 ...
- 细说shiro之五:在spring框架中集成shiro
官网:https://shiro.apache.org/ 1. 下载在Maven项目中的依赖配置如下: <!-- shiro配置 --> <dependency> <gr ...
- Spring5源码解析-Spring框架中的单例和原型bean
Spring5源码解析-Spring框架中的单例和原型bean 最近一直有问我单例和原型bean的一些原理性问题,这里就开一篇来说说的 通过Spring中的依赖注入极大方便了我们的开发.在xml通过& ...
随机推荐
- 01-什么是 Java:Java 初学者指南
什么是Java? Java 是一种用于互联网分布式环境的面向对象编程语言.它是一种高级语言,也易于阅读和理解.有了它,开发人员可以"编写一次,随处运行"(WORA),这意味着编译后 ...
- PS 独立集中标识下修改项目采购类型增强
1.当物料独立集中标识为2,采购类型通过BAPI:BAPI_NETWORK_COMP_ADD,type_of_pur_resv传入'7',则报错"组件分配并不是未销售订单或项目库存而设的&q ...
- Educational DP Contest R - Walk(倍增floyd,矩阵快速幂)
题目来源:AtCoder EDU DP题集 题目链接:Here 单独拎出来是因为这道题是一个很好的板子,值得记录 题意 给定一个 n 个节点的有向图的邻接矩阵,求该有向图中长度为 k 的路径长. 解法 ...
- SQL常用日期格式化转换与百分数转换
目录 SQL将小数转为保留两位的百分数 常用的日期格式化 补充: 秒/毫秒转为持续时间 常用拼接方式: 本篇开启数据库在工作中常用到的格式转换与工具,欢迎大家评论留言 SQL将小数转为保留两位的百分数 ...
- xapian 搜索引擎介绍与使用入门
Xapian 是一个开源搜索引擎库,使用 C++ 编写,并提供绑定(bindings )以允许从多种编程语言使用.它是一个高度适应性的工具包,允许开发人员轻松地将高级索引和搜索功能添加到自己的应用程序 ...
- TapTap 算法平台的 Serverless 探索之路
分享人:陈欣昊,TapTap/IEM/AI平台负责人 摘要:本文主要介绍心动网络算法平台在Serverless上的实践. <TapTap算法平台的 Serverless 探索之路> Ser ...
- mouseenter和mouseover区别
mouseenter事件 当鼠标移动到元素上时,就会触发mouseenter事件. 类似mouseover,它们两者之间的差别是:mouseover鼠标经过自身盒子会触发,经过子盒子还会触发.mous ...
- Java标签在循环中的使用
定义 标签,类似--label1: 放在循环外部,用于内部多重循环语句的跳出 例子 public static void main(String[] args) { Scanner sc = new ...
- SV 接口
概述 接口 main bus有很多信号线 verilog会先将模块的输出信号拉出来,然后再将其连接到其他模块,进行不同模块之间的连接比较麻烦且容易出错 interface - 将端口封装到接口中 接口 ...
- Laravel路由匹配
Route常规用法如下,特别是最后一个传参之后可以进行正则匹配,非常好用. //@后面内容为所要访问的方法 Route::get('foo', 'Photos\AdminController@meth ...