@[TOC]
1、注解是什么?
注解
也叫元数据:用于对代码进行说明,可以对包、类、接口、字段方法参数、局部变量等进行注解。其实说白了就是代码里的特殊标志,这些标志可以在编译,类加载,运行时被读取,并根据这些信息执行相应的处理,以便于其他工具补充信息或者进行部署。
元数据(metadata),又叫中介数据、中继数据,为描述数据的数据(data about data
),主要是描述数据属性(property
)的信息,用来支持如指示存储位置、历史数据、资源查找、文件记录等功能。
如果说注释是写给人看的,那么注解就是写给程序看的。它更像一个标签,贴在一个类、一个方法或者字段上。它的目的是为当前读取该注解的程序提供判断依据。比如程序只要添加了@Test
的方法,就知道该方法时待测试方法,又比如@Before
注解,程序看到这个注解,就知道该方法要放在@Test方法之前执行。
1.1注解的类型
一般常用的注解可以分为三类:
1.元注解
,元注解是用于定义注解的注解,包括@Retention
(标明注解被保留的阶段)、@Target(标明注解使用的范围)、@Inherited(标明注解可继承)、@Documented(标明是否生成javadoc文档)
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
* 此注解类型的信息只会记录在源文件中,编译时将被编译器丢弃,也就是说不会保存在编译好class文件中,只会在.java文件中
* 例如:@SuppressWarnings
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
* 编译器将注解记录在class文件中,但不会加载到JVM中,如果一个注解声明没指定范围,则系统默认值就是CLASS
* 例如:@Override
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
* 注解信息会保留在源文件、类文件中,在执行时也加载到java的JVM中,因此可以通过反射惊醒读取
* 例如:@Deprecated
*/
RUNTIME
}
2.java
自带的标准注解,包括@Override
(标明重写某个方法)、@Deprecated
(标明某个类或方法过时)和@SuppressWarnings
(标明要忽略的警告),使用这些注解后编译器就会进行检查。
3.自定义注解,可以根据自己的需求定义注解。
1.2注解配置和xml配置的关系
Spring
早期是使用xml
来进行配置的,现在更推荐注解配置,那么xml
配置和注解配置的区别是什么呢?xml
: 是一种集中式的元数据,与源代码无绑定。
注解:是一种分散式的元数据,与源代码紧绑定。
1.3注解的作用
1.生成文档,通过代码里标识的元数据生成javadoc文档。
2.编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。(压制警告、重写)
3.编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。(运行之前生成代码)
4.运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例。
2.注解实现
2.1实现注解的步骤
(1)声明注解
(2)添加注解
(3)获取添加了注解的目标。通常是Class
对象,Method
对象,Field
对象,还有Constructor
对象,Parameter
对象,Annotation
对象等
a.通过已知对象,获取Class
对象
b.通过全路径,获取Class
对象
c.扫描包路径,获取Class
对象
(4)实现注解处理器
。借助反射
,获取注解对象
,读取注解属性值
,然后根据注解及属性值做相应处理
2.2不基于spring容器实现
2.2.1已知class,直接反射
举个例子:
新建性别Enum
public enum SexEnum {
MALE,FEMALE,NUKONW;
}
申明注解
@Retention(RetentionPolicy.RUNTIME) //保留到运行时,不加的
@Target(ElementType.FIELD) //作用在某个字段上
@Documented
public @interface Person {
String name() default "";
SexEnum sex() default SexEnum.NUKONW;
int age() default 18;
}
新建Human实体类
public class Human {
private String name;
private SexEnum sex;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public SexEnum getSex() {
return sex;
}
public void setSex(SexEnum sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Human{" +
"name='" + name + '\'' +
", sex=" + sex +
", age=" + age +
'}';
}
}
写测试方法:
public class Example01 {
@Person(name = "张三",sex = SexEnum.FEMALE,age = 20)
private Human human1;
@Person(name = "李四",sex = SexEnum.MALE)
private Human human2;
@Test
public void test() throws NoSuchFieldException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
Example01 example01 = new Example01();
System.out.println(example01.human1);
System.out.println(example01.human2);
//注解处理器
initField(example01);
System.out.println(example01.human1);
System.out.println(example01.human2);
}
/**
* 注解处理器
* @param example01
* @throws NoSuchFieldException
*/
public void initField(Example01 example01) throws NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//1 获取类(Example01)的反射对象
Class clazz = example01.getClass();
//获取根据反射对象获取类的两个属性对象
Field field1 = clazz.getDeclaredField("human1");
Field field2 = clazz.getDeclaredField("human2");
//根据类的属性对象获取属性上的Person注解
Person person1 = field1.getDeclaredAnnotation(Person.class);
Person person2 = field2.getDeclaredAnnotation(Person.class);
//2 获取类型对应的Class对象(类型对应的声明类型Human)
Class clz = field1.getType();
// 获取构造器
Constructor constructor = clz.getConstructor();
// 根据构造器获取两个Human实例
Human human1 = (Human) constructor.newInstance();
Human human2 = (Human) constructor.newInstance();
//3 从Person注解中获取值设置到两个human对象中
human1.setName(person1.name());
human1.setSex(person1.sex());
human1.setAge(person1.age());
human2.setName(person2.name());
human2.setSex(person2.sex());
human2.setAge(person2.age());
//4 将两个human对象设置到Example01对象的Field中
field1.set(example01,human1);
field2.set(example01,human2);
}
}
结果
null
null
Human{
name='张三', sex=FEMALE, age=20}
Human{
name='李四', sex=MALE, age=18}
2.2.2类扫描,然后反射
类扫描的方法:
A.spring(spring工具包,不是Spring容器,而是spring-core包)
B.reflections(反射工具包)
C.自己实现
举例1:借助spring-core
声明注解
@Retention(RetentionPolicy.RUNTIME) //保留打运行时
@Target(ElementType.TYPE) //范围
@Documented //生成javadoc文档
@Inherited //自动继承
public @interface Cat {
String name() default "";
}
创建两个实体类Tomcat和Jerry
@Cat(name = "tom")
public class Tomcat {
private String name;
public Tomcat() {
}
public Tomcat(String name) {
this.name = name;
}
@Override
public String toString() {
return "Tomcat{" +
"name='" + name + '\'' +
'}';
}
}
@Cat(name = "jetty")
public class Jerry {
private String name;
public Jerry() {
}
public Jerry(String name) {
this.name = name;
}
@Override
public String toString() {
return "Jerry{" +
"name='" + name + '\'' +
'}';
}
}
测试
public class ExampleSpring {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//根据路径扫描候选组件提供者 -- 不使用默认过滤器
ClassPathScanningCandidateComponentProvider classPathScanningCandidateComponentProvider = new ClassPathScanningCandidateComponentProvider(false);
//添加过滤器,过滤Cat注解
classPathScanningCandidateComponentProvider.addIncludeFilter(new AnnotationTypeFilter(Cat.class));
//根据路径扫描符合条件的候选者组件
Set<BeanDefinition> candidateComponents = classPathScanningCandidateComponentProvider.findCandidateComponents("com.atguigu.spring5.annotation2");
//循环所有的筛选结果组件
for (BeanDefinition candidateComponent : candidateComponents) {
//获取类名
String beanClassName = candidateComponent.getBeanClassName();
//获取类的反射对象
Class clazz = Class.forName(beanClassName);
//获取类上的注解
Cat cat = (Cat) clazz.getDeclaredAnnotation(Cat.class);
//获取注解的name() 值
String name = cat.name();
//获取当前反射对象的构造器(有name属性的构造器)
Constructor declaredConstructor = clazz.getDeclaredConstructor(String.class);
//用该构造器生成类的对象
Object object = declaredConstructor.newInstance(name);
//输出该对象
System.out.println(object);
}
}
}
结果
Jerry{
name='jetty'}
Tomcat{
name='tom'}
举例2:借助reflections反射工具包
有兴趣的可以了解一下
需要引入依赖
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.10</version>
</dependency>
2.3基于Spring容器实现
基于以上的例子,注解处理器中扫描扫描包这个操作是比较频繁发生的,扫描包这个动作重复了,而且效率低下,因此,可以将扫描包这不操作抽取出来,或者使用Spring容器,Spring容器本身就会执行一次扫描,我们借助Spring容器来实现。
2.3.1 spring模块结构(部分)
整个SpringIOC容器,核心模块包括构造Bean定义,实例化BeanFactory,注册Bean定义,实例化Bean并完成依赖注入,提供Bean获取。核心组件包括BeanDefinition实例对象,BeanFactory实例对象,Bean实例对象。
2.3.2 spring容器核心流程(部分)
核心流程:
1.BeanFactory实例化
2.注册Bean定义
3.实例化普通Bean(调用是生成)和单例Bean(初始化时生成)
4.依赖注入属性
5.普通Bean初始化
6.所有普通单例Bean实例化完成
加入钩子后流程:
1、BeanFactory实例化
2、注册Bean定义
3、注册后置处理,实现BeanDefinitionRegistryPostProcessor.postProcessBeanDfinitionRegistry
4、BeanFactory后置处理,实现BeanFactoryPostProcessor.postProcessBeanFactory
5、实例化Bean前置处理,实现InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation
6、实例化普通单例Bean
7、依赖注入属性
8、实例化Bean后置处理,实现InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation
9、依赖注入后置处理,实现InstantiationAwareBeanPostProcessor.postProcessPropertyValues
10、Bean初始化前置处理,实现BeanPostProcessor.postProcessBeforeInitialization
11、普通Bean初始化(@PostConstruct注解方法初始化,InitializingBean接口afterPropertiesSet方法初始化,@Bean注解init-method属性方法初始化)
12、Bean初始化后置处理,实现BeanPostProcessor.postProcessBeforeInitialization
13、所有普通单例Bean实例化完成
14、所有普通单例Bean实例化后置处理,实现SmartInitializingSingleton.afterSingletonsInstantiated
- BeanFactoryPostProcessor:是针对于beanFactory的扩展点,即spring会在beanFactory初始化之后,beanDefinition都已经loaded,但是bean还未创建前进行调用,可以修改,增加beanDefinition。
- BeanPostProcessor:是针对bean的扩展点,即spring会在bean初始化前后 调用方法对bean进行处理
- 总结:以上两种都为Spring提供的后处理bean的接口,只是两者执行的时机不一样。BeanPostProcessor为实例化之后,BeanFactoryPostProcessor是实例化之前。
- BeanDefinition里面的方法名字类似我们bean标签的属性,setBeanClassName对应bean标签中的class属性,所以当我们拿到BeanDefinition对象时,我们可以手动修改bean标签中所定义的属性值。
- 具体这个BeanDefinition是个什么对象,当我们在xml中定义了bean标签时,Spring会把这些bean标签解析成一个javabean,这个BeanDefinition就是bean标签对应的javabean。
- 所以当我们调用BeanFactoryPostProcess方法时,这时候bean还没有实例化,此时bean刚被解析成BeanDefinition对象。
- Spring容器初始化bean大致过程 定义bean标签>将bean标签解析成BeanDefinition>调用构造方法实例化(IOC)>属性值得依赖注入(DI)。
- BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor都是接口,BeanDefinitionRegistryPostProcessor是BeanFactoryPostProcessor的子接口。实现类用来修改Spring容器的beanDefinition行为的,注意这里的两个不是bean的后置处理器,可以称为bean工厂的后置处理器和bean注册中心的后置处理器,针对的时beanDefinition的map,并不是单个的beanDefinition,关键的一点是,此时的这些bean还没有被实例化。
BeanDefinitionRegistry只可以获取,修改beanDefinition,ConfigurableListableBeanFactory不但可以修改beanDefinition,还可以getBean(),也就是实例化某个bean,将bean的实例化过程提前,可以满足用户的某些特殊需求。除此之外,可以配置多个实现BeanDefinitionRegistryPostProcessor接口的bean,会按照被加载的顺序执行,并且可以通过配置指定执行的先后顺序。
2.3.3 Spring常见注解实现(部分)
2.3.4 借助Spring接口实现注解方式
(1)基于
BeanDefinitionRegistryPostProcessor
接口
这个接口一般用来注册bean定义,当然也可以修改bean定义信息,触发时机在BeanFactory实例化后,注册了一些系统内置的bean定义之后public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor { void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry var1) throws BeansException; }
ConfigurationClassPostProcessor
就是基于BeanDefinitionRegistryPostProcessor
接口实现@Configuration
、@Bean
、@Import
、@ImportResource
、@ComponentScan
、@PropertySource
、@Conditional
等注解的。
声明注解@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Inherited public @interface Registry { String value() default ""; }
实体类Dog
@Registry public class Dog { }
实现
BeanDefinitionRegistryPostProcessor
@Component public class AnnotationBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException { //根据路径扫描候选组件 ClassPathScanningCandidateComponentProvider classPathScanningCandidateComponentProvider = new ClassPathScanningCandidateComponentProvider(false); //添加过滤器 classPathScanningCandidateComponentProvider.addIncludeFilter(new AnnotationTypeFilter(Registry.class)); //根据扫描范围查找所有符合条件的组件(候选者) Set<BeanDefinition> candidateComponents = classPathScanningCandidateComponentProvider.findCandidateComponents("com.atguigu.spring5.annotation4"); //将所有符合条件的组件,加入Spring容器 for (BeanDefinition candidateComponent : candidateComponents) { beanDefinitionRegistry.registerBeanDefinition(candidateComponent.getBeanClassName(),candidateComponent); } } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { } }
测试
@Configuration @ComponentScan("com.atguigu.spring5.annotation4") public class Example01 { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(Example01.class); Dog dog = context.getBean(Dog.class); System.out.println(dog); } }
(2)基于
BeanPostProcessor
接口
声明注解@Retention(RetentionPolicy.RUNTIME) //保留到运行期 @Target(ElementType.FIELD) //作用在属性字段 @Documented //生成javadoc文档 @Inherited //自动继承 public @interface Boy { String name() default ""; }
新建实体列
Hello
@Service public class Hello { @Boy(name = "小明") String name = "world"; public void say(){ System.out.println("hello, " + name); } }
BoyAnnotationBeanPostProcessor
实现BeanPostProcessor
```java
@Component //注意:Bean后置处理器本身也是一个Bean
public class BoyAnnotationBeanPostProcessor implements BeanPostProcessor {@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {/** * 利用java反射机制注入属性 */ //获取对象的反射对象 Class clazz = bean.getClass(); //获取类的 所有属性对象 Field[] declaredFields = clazz.getDeclaredFields(); //循环 for (Field declaredField : declaredFields) { //获取每个字段的注解 Boy annotation = declaredField.getAnnotation(Boy.class); //如果注解不存在,下一个 if (null == annotation) { continue; } // true值表示在使用反射对象时应该抑制Java语言访问检查。 false值表示反射对象应该执行Java语言访问检查。 declaredField.setAccessible(true); try { //将注解的值设置到bean对象的该属性中 declaredField.set(bean,annotation.name()); } catch (IllegalAccessException e) { e.printStackTrace(); } } return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {return bean;
}
}
**测试**
```java
@Configuration
@ComponentScan("com.atguigu.spring5.annotation3")
public class Example {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Example.class);
Hello hello = context.getBean(Hello.class);
hello.say();
}
}