文章目录

  • 1、注解是什么?
  • 1.1注解的类型
  • 1.2注解配置和xml配置的关系
  • 1.3注解的作用
  • 2.注解实现
  • 2.1实现注解的步骤
  • 2.2不基于spring容器实现
  • 2.2.1已知class,直接反射
  • 2.2.2类扫描,然后反射
  • 2.3基于Spring容器实现
  • 2.3.1 spring模块结构(部分)
  • 2.3.2 spring容器核心流程(部分)
  • 2.3.3 Spring常见注解实现(部分)
  • 2.3.4 借助Spring接口实现注解方式


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模块结构(部分)

java 元数据驱动的SAAS架构 开源 java中的元数据_开发语言


整个SpringIOC容器,核心模块包括构造Bean定义,实例化BeanFactory,注册Bean定义,实例化Bean并完成依赖注入,提供Bean获取。核心组件包括BeanDefinition实例对象,BeanFactory实例对象,Bean实例对象。

2.3.2 spring容器核心流程(部分)

java 元数据驱动的SAAS架构 开源 java中的元数据_实例化_02


核心流程:

1.BeanFactory实例化

2.注册Bean定义

3.实例化普通Bean(调用是生成)和单例Bean(初始化时生成)

4.依赖注入属性

5.普通Bean初始化

6.所有普通单例Bean实例化完成

加入钩子后流程:

java 元数据驱动的SAAS架构 开源 java中的元数据_spring_03


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常见注解实现(部分)

java 元数据驱动的SAAS架构 开源 java中的元数据_java_04

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

@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;
    }

}

测试

@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();
    }
}