1.注解是什么?
注解(Annotation)也叫元数据,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。其实说白就是代码里的特殊标志,这些标志可以在编译,类加载,运行时被读取,并根据这些信息执行相应的处理,以便于其他工具补充信息或者进行部署。
元数据(Metadata),又称中介数据、中继数据,为描述数据的数据(data about data),主要是描述数据属性(property)的信息,用来支持如指示存储位置、历史数据、资源查找、文件记录等功能。
如果说注释是写给人看的,那么注解就是写给程序看的。它更像一个标签,贴在一个类、一个方法或者字段上。它的目的是为当前读取该注解的程序提供判断依据。比如程序只要读到加了@Test的方法,就知道该方法是待测试方法,又比如@Before注解,程序看到这个注解,就知道该方法要放在@Test方法之前执行。
1.1 注解的类型
一般常用的注解可以分为三类:
元注解,元注解是用于定义注解的注解,包括@Retention(标明注解被保留的阶段)、@Target(标明注解使用的范围)、@Inherited(标明注解可继承)、@Documented(标明是否生成javadoc文档)。
public enum RetentionPolicy {//此注解类型的信息只会记录在源文件中,编译时将被编译器丢弃,也就是说不会保存在编译好的class文件中//例如:@SuppressWarningsSOURCE,//编译器将注解记录在class文件中,但不会加载到JVM中。如果一个注解声明没指定范围,则系统默认值就是//例如:@OverrideCLASS,//注解信息会保留在源文件、类文件中,在执行的时也加载到Java的JVM中,因此可以通过反射进行读取。//例如:DeprecatedRUNTIME
Java自带的标准注解,包括@Override(标明重写某个方法)、@Deprecated(标明某个类或方法过时)和@SuppressWarnings(标明要忽略的警告),使用这些注解后编译器就会进行检查。
- 
1.2.注解配置和xml配置的关系
我们知道Spring早期是使用xml来进行配置的,现在更推荐注解配置,那么xml配置和注解配置的区别是什么呢?
我们可以认为xml和注解都是元数据。
xml: 是一种集中式的元数据,与源代码无绑定。
注解:是一种分散式的元数据,与源代码紧绑定。1.3 注解的作用
生成文档,通过代码里标识的元数据生成javadoc文档。
编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。
编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。
运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例。2.注解实现
2.1 实现注解的步骤
(1).声明注解
(2).添加注解
(3). 获取添加了注解的目标。通常是Class对象,Method对象,Field对象,还有Constructor对象,Parameter对象,Annotation对象等
a.通过已知对象,获取Class对象
b.通过全类路径,获取Class对象
c.扫描包路径,获取Class对象
(4).实现注解处理器。借助反射,获取注解对象,读取注解属性值, 然后根据注解及属性值做相应处理2.2 不基于spring容器实现
(1). 已知class,直接反射
举个例子:
申明注解@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.FIELD)public @interface Person {String name() default "";String sex() default "";int age() default 18;}
定义一个普通实体类
public class Human {// 姓名private String name;// 性别private String sex;// 年龄private Integer age;@Overridepublic String toString() {return "Human{" +"name='" + name + '\'' +", sex='" + sex + '\'' +", age=" + age +'}';}
注解实现及测试
public class Example01 {@Person(name="张三", sex = "男", age = 23)private Human human1;@Person(name="小红", sex = "女", age = 21)private Human human2;public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {Example01 example01 = new Example01();System.out.println(example01.human1);System.out.println(example01.human2);example01.initField();System.out.println(example01.human1);System.out.println(example01.human2);}// 注解实现public void initField() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {Class clazz = this.getClass();Field[] fields = clazz.getDeclaredFields();for(Field field : fields){Person person = field.getDeclaredAnnotation(Person.class);if(person != null) {Human human = ((Class<Human>) field.getType()).getDeclaredConstructor().newInstance();human.setSex(person.sex());human.setName(person.name());human.setAge(person.age());field.set(this, human);}}}}
(2). 类扫描,然后反射
类扫描的方法:
A. spring(spring工具包)
B. reflections(反射工具包)
C. 自己实现
举例1:借助spring-core
声明注解@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public @interface Cat {String name() default "";}
添加注解
@Cat(name="tom")public class Tomcat {private String name;public Tomcat(){}public Tomcat(String name) {this.name = name;}@Overridepublic String toString() {return "Tomcat{" +"name='" + name + '\'' +'}';}}
实现注解并测试
public class SpringExample {public static void main(String[] args) throws Exception {ClassPathScanningCandidateComponentProvider classPathScanningCandidateComponentProvider =new ClassPathScanningCandidateComponentProvider(false);classPathScanningCandidateComponentProvider.addIncludeFilter(new AnnotationTypeFilter(Cat.class));Set<BeanDefinition> beanDefinitions =classPathScanningCandidateComponentProvider.findCandidateComponents("springboot");for (BeanDefinition beanDefinition : beanDefinitions) {String beanClassName = beanDefinition.getBeanClassName();Class clazz = Class.forName(beanClassName);Cat cat = (Cat) clazz.getDeclaredAnnotation(Cat.class);String name = cat.name();Object obj = clazz.getDeclaredConstructor(String.class).newInstance(name);System.out.println(obj);}}}
举例2:借助reflections反射工具包
声明注解@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public @interface Cat {String name() default "";}
添加注解
@Cat(name="tom")public class Tomcat {private String name;public Tomcat(){}public Tomcat(String name) {this.name = name;}@Overridepublic String toString() {return "Tomcat{" +"name='" + name + '\'' +'}';}}
使用注解及测试
public class ReflectionsExample {public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {Reflections reflections = new Reflections("com.dblones.java.annotation.example03", new TypeAnnotationsScanner(), new SubTypesScanner(), new FieldAnnotationsScanner());Set<Class<?>> cats = reflections.getTypesAnnotatedWith(Cat.class);for(Class<?> clazz : cats){Cat cat = (Cat) clazz.getDeclaredAnnotation(Cat.class);String name = cat.name();Object object = clazz.getDeclaredConstructor(String.class).newInstance(name);System.out.println(object);}}}
基于以上的例子,我们会发现,如果存在多个自定义注解的时候,获取到添加了注解的目标,是整个实现自定义注解过程中,比较频繁发生的事情,如果每个注解的实现都去扫描包,很显然,扫描包这个动作重复了,而且效率低下,那有没有什么办法,可以减少扫描或者我们自己根本不需要扫描呢?一种方式是把扫描抽离出来做为公共初始化方法,另外一种方式就是如果我们的程序里面使用了spring容器,那么我们可以借助spring容器,借助spring的扫描,我们可以不需要自己扫描。
2.3 基于spring容器实现
2.3.1 spring模块结构(部分)
 

整个Spring IOC容器,核心模块包括构造Bean定义,实例化BeanFactory,注册Bean定义,实例化Bean并完成依赖注入,提供Bean获取。核心组件包括BeanDefinition实例对象,BeanFactory实例对象,Bean实例对象。
2.3.2 spring容器核心流程(部分)

核心流程:
1.BeanFactory实例化
2.注册Bean定义
3.实例化普通单例Bean
4.依赖注入属性
5.普通Bean初始化
6.所有普通单例Bean实例化完成
加入钩子后流程:
1.BeanFactory实例化
2.注册Bean定义
3.注册后置处理,实现BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry
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
2.3.3 spring常见注解实现(部分)
2.3.4 借助spring接口实现注解方式
(1). 基于BeanDefinitionRegistryPostProcessor接口
这个接口一般用来注册bean定义,当然也可以修改bean定义信息,触发时机在BeanFactory实例化后,注册了一些系统内置的bean定义之后
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;}
ConfigurationClassPostProcessor就是基于BeanDefinitionRegistryPostProcessor接口实现@Configuration、@Bean、@Import、@ImportResource、@ComponentScan、@PropertySource、@Conditional等注解的。
举例:
声明注解
@Target({ElementType.TYPE}) //声明应用在属性上@Retention(RetentionPolicy.RUNTIME) //运行期生效@Documentedpublic @interface Registry {String value() default "";}
添加注解
@Registrypublic class Dog {}
注解实现
@Componentpublic class AnnotationBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {ClassPathScanningCandidateComponentProvider classPathScanningCandidateComponentProvider = new ClassPathScanningCandidateComponentProvider(false);classPathScanningCandidateComponentProvider.addIncludeFilter(new AnnotationTypeFilter(Registry.class));Set<BeanDefinition> beanDefinitions = classPathScanningCandidateComponentProvider.findCandidateComponents("com.dblones.java.annotation.example05");for(BeanDefinition beanDefinition : beanDefinitions){registry.registerBeanDefinition(beanDefinition.getBeanClassName(), beanDefinition);}}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}}
测试验证
@Configuration@ComponentScan("com.dblones.java.annotation.example05")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). BeanFactoryPostProcessor
这个接口一般修改bean定义信息,触发时机在BeanFactory实例化后,注册了bean定义之后,BeanDefinitionRegistryPostProcessor接口的postProcessBeanDefinitionRegistry方法之后
public interface BeanFactoryPostProcessor {void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;}
@Configuration注解的代理实现就是在这个阶段通过ConfigurationClassPostProcessor类实现的。
(3).InstantiationAwareBeanPostProcessor
是BeanPostProcessor的子接口,通过接口字面意思翻译该接口的作用是感知Bean实例化的处理器。就是全面干预Bean实例化过程,包括Bean实例化前后,设置属性,初始化前后。
public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException;boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException;PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException;}
方法描述postProcessBeforeInitializationBeanPostProcessor接口中的方法,在Bean的自定义初始化方法之前执行postProcessAfterInitializationBeanPostProcessor接口中的方法,在Bean的自定义初始化方法执行完成之后执行postProcessBeforeInstantiation自身方法,是最先执行的方法,它在目标对象实例化之前调用,该方法的返回值类型是Object,我们可以返回任何类型的值。由于这个时候目标对象还未实例化,所以这个返回值可以用来代替原本该生成的目标对象的实例(比如代理对象)。如果该方法的返回值代替原本该生成的目标对象,后续只有postProcessAfterInitialization方法会调用,其它方法不再调用;否则按照正常的流程走postProcessAfterInstantiation在目标对象实例化之后调用,这个时候对象已经被实例化,但是该实例的属性还未被设置,都是null。因为它的返回值是决定要不要调用postProcessPropertyValues方法的其中一个因素(因为还有一个因素是mbd.getDependencyCheck());如果该方法返回false,并且不需要check,那么postProcessPropertyValues就会被忽略不执行;如果返回true,postProcessPropertyValues就会被执行postProcessPropertyValues对属性值进行修改,如果postProcessAfterInstantiation方法返回false,该方法可能不会被调用。可以在该方法内对属性值进行修改
我们常见的注解@Autowired、@Value、@Inject、@Resource、@PostConstruct、@PreDestroy、@WebServiceRef、@EJB、@Required就是借助InstantiationAwareBeanPostProcessor接口实现的,其中@Autowired、@Value、@Inject是AutowiredAnnotationBeanPostProcessor类实现的,@Resource、@PostConstruct、@PreDestroy、@WebServiceRef、@EJB是CommonAnnotationBeanPostProcessor 类实现的,@Required是RequiredAnnotationBeanPostProcessor类实现的。
(4). BeanPostProcessor
对实例Bean进行后置处理, Bean初始化方法调用前被调用或Bean初始化方法调用后被调用
public interface BeanPostProcessor {//bean初始化方法调用前被调用Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;//bean初始化方法调用后被调用Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;}
运行顺序
=Spring IOC容器实例化Bean=
=Bean属性赋值=
=调用BeanPostProcessor的postProcessBeforeInitialization方法=
=调用bean实例的初始化方法=
=调用BeanPostProcessor的postProcessAfterInitialization方法=
我们常见的注解@@PostConstruct、@PreDestroy就是借助BeanPostProcessor 接口实现的,具体是通过InitDestroyAnnotationBeanPostProcessor类实现的。
举例:
声明注解
@Target({ElementType.FIELD}) //声明应用在属性上@Retention(RetentionPolicy.RUNTIME) //运行期生效@Documentedpublic @interface Boy {String name() default "";}
添加注解
@Servicepublic class Hello {@Boy(name = "小明")String name = "world";public void say() {System.out.println("hello, " + name);}}
注解实现
@Component //注意:Bean后置处理器本身也是一个Beanpublic class BoyAnnotationBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {/*** 利用Java反射机制注入属性*/Field[] declaredFields = bean.getClass().getDeclaredFields();for (Field declaredField : declaredFields) {Boy annotation = declaredField.getAnnotation(Boy.class);if (null == annotation) {continue;}declaredField.setAccessible(true);try {declaredField.set(bean, annotation.name());} catch (IllegalAccessException e) {e.printStackTrace();}}return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {return bean;}}
测试验证
@Configuration@ComponentScan("com.dblones.java.annotation.example04")public class Example {public static void main(String[] args) {ApplicationContext context = new AnnotationConfigApplicationContext(Example.class);Hello hello = context.getBean(Hello.class);hello.say();}}
(5). SmartInitializingSingleton
对全体实例bean进行后置处理。
public interface SmartInitializingSingleton {void afterSingletonsInstantiated();}
Spring Cloud中的@LoadBalanced就是借助SmartInitializingSingleton接口实现的。
(6). AOP
对方法进行拦截。
常见权限,加解密,日志,方法调用等处理可以借助AOP完成。
