1 | Spring:基础注解使用及场景概述
本来是打算按照书上面的内容,跟着看一看spring的实现。它是从XML中读取bean配置,感觉现在不怎么用XML来配置了,虽然可以跳过bean的读取,直接看bean的生命周期,但是毕竟现在基本上用注解,所以从网上找了一个直接基于注解讲解spring源码的视频。 视频内容讲解spring的AOP感觉很
本来是打算按照书上面的内容,跟着看一看spring的实现。它是从XML中读取bean配置,感觉现在不怎么用XML来配置了,虽然可以跳过bean的读取,直接看bean的生命周期,但是毕竟现在基本上用注解,所以从网上找了一个直接基于注解讲解spring源码的视频。
视频内容讲解spring的AOP感觉很好,至少第一遍的时候,看懂了一个大概。这篇博客在看第一遍的时候,记录了大概的内容,可以算是一个笔记;第二遍是在第一遍笔记的基础上,自己写代码进行实现、测试,并将代码和当时的想法再记录上来,有问题了再看视频。
概览

组件添加类注解

先看看比较基础的注解的用法。这个里面比较重要的注解有@Import,后面讲AOP经常用到,特别是在@Import中传ImportBeanDefinitionRegistrar的用法。因为AOP的入口点就是利用了这个注解。
@Configuration + @Bean
这是一个很常用的组合,在项目中也经常用到过。但是有一个地方需要注意,被@Bean修改的函数的函数名,默认情况下,会是返回bean的名字。要改名可以通过设置@Bean的name属性。
public class BasicMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BasicConfig.class);
// System.out.println(applicationContext.getBean(Person.class));
for (String definitionName : applicationContext.getBeanDefinitionNames()) {
System.out.println(definitionName);
}
}
}
结果是:

这里有一个问题:为什么可以生成3个BeanDefinition,但是获取的时候,会报错失败?。先留给后面吧。

@Scope
spring默认情况下,bean是单例。可设置为多实例:prototype。如下:
@Configuration
public class BasicAnnotationConfig {
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person getPerson() {
return new Person("萨日朗", 11);
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public Actor getActor() {
return new Actor();
}
@Bean
public Programmer getProgrammer() {
return new Programmer();
}
}
然后创建IOC容器,然后对上面的3个bean都获取两次,比较两次获得的内容是否为同一对象。
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BasicAnnotationConfig.class);
Person person1 = applicationContext.getBean(Person.class);
Person person2 = applicationContext.getBean(Person.class);
System.out.println("Person: " + (person1 == person2));
Actor actor1 = applicationContext.getBean(Actor.class);
Actor actor2 = applicationContext.getBean(Actor.class);
System.out.println("Actor: " + (actor1 == actor2));
Programmer programmer1 = applicationContext.getBean(Programmer.class);
Programmer programmer2 = applicationContext.getBean(Programmer.class);
System.out.println("Programmer: " + (programmer1 == programmer2));
}
结果为:

@ComponentScan
自动扫描包下的bean。
public class ComponentScanMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ComponentScanConfig.class);
for (String definitionName : applicationContext.getBeanDefinitionNames()) {
System.out.println(definitionName);
}
}
}
包、类的情况如下,其中dao和services下面的类,都是空类,没有任何其他数据,只有@Service和@Repository注解。
test-spring/src/main/java/
└── run
└── oxffff
└── spring
├── apps
│ └── ComponentScanMain.java
├── configs
│ └── ComponentScanConfig.java
├── dao
│ └── PersonDao.java
└── services
├── SpeakService.java
└── TalkService.java
结果

@ComponentScan还可以指定exclude和include属性,分别表示要排除的内容、扫描的内容。使用一个项目中用到的示例,效果就不演示了:
@ComponentScan(excludeFilters = {@ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
value = {com.***.service.AlgoProducer.AlgoProducerConfig.class,
com.***.service.WsProducer.WsProducerConfig.class})
})
type还有好几种,分别是:
FilterType.ANNOTATION。注解类型FilterType.ASSIGNABLE_TYPE。给定的类型FilterType.ASPECTJ。AspectJ表达式FilterType.REGEX。正则表达式FilterType.CUSTOM。自定义过滤规则
如果指定的是自定义过滤规则,那么后面value表示的类,需要实现TypeFilter的match方法。
@Lazy
懒加载bean
@Conditional
条件加载bean。其中的value是一个数组类型,数组中的元素必须实现了Condition接口。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
Condition接口只有一个match方法,实现即可。
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
matches方法的两个参数,可以用来做什么?简单理解,Spring Boot中有一个@ConditionalOnBean,基于Conditional,它的作用就是当容器中已经某些特定的bean时,加载某些bean。

另外,在Spring Boot中有很多基于@ConditionalXXXXX的注解,都是基于此注解。以@ConditionalOnProperty为例,项目中曾用到过此注解,解决一些关于不同环境的配置问题。
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
// ...
}
所以这个OnPropertyCondition一定是一个实现了Condition的类,可以从下面的继承继承结构中看出来:

@Import
快速导入bean,id默认是全类名。这个注解很重要,理解这个注解,对后面AOP的理解有很大的帮助。
普通类
不是很确定能不能导入,特地测试了一下,是可以import到IOC容器中的。
被@Configuration注解的类
实现了ImportSelector的类。此项返回的是一个类全名数组。
实现了ImportBeanDefinitionRegistrar的类。 手动注册Bean到IOC容器。
主要测试一次实现了ImportBeanDefinitionRegistrar接口的value值,后面用到的比较多。这里先实现一个如下:
public class MyInportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
registry.registerBeanDefinition("programmer", new RootBeanDefinition(Programmer.class));
}
}
配置信息,主要是验证导入普通类、实现了ImportBeanDefinitionRegistrar的类。
@Configuration
@Import({Actor.class, MyInportBeanDefinitionRegistrar.class})
public class ImportConfig {
}
启动类为,主要功能是打印容器中的bean name:
public class ImportMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ImportConfig.class);
for (String definitionName : applicationContext.getBeanDefinitionNames()) {
System.out.println(definitionName);
}
}
}
结果导入了两个bean,如下:

可以看出来,import普通类时,默认的名称是类全名。
FactoryBean的使用
实现FactoryBean的类,然后用@Bean注解一个返回实现了该类的对象。
定义一个Actor的FactoryBean,并实现相应的方法。
public class ActorFactoryBean implements FactoryBean<Actor> {
@Override
public Actor getObject() throws Exception {
return new Actor();
}
@Override
public Class<?> getObjectType() {
return Actor.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
在配置类中,生成相应的bean配置。
@Configuration
public class FactoryBeanConfig {
@Bean
public ActorFactoryBean getActorFactoryBean() {
return new ActorFactoryBean();
}
}
通过ActorFactoryBean的bean名称,获取bean,看得到的bean类型是什么,然后再加一个&符号,对比两次结果:
public class ActorFactoryBeanMain {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(FactoryBeanConfig.class);
for (String definitionName : applicationContext.getBeanDefinitionNames()) {
System.out.println(definitionName);
}
Object obj1 = applicationContext.getBean("getActorFactoryBean");
System.out.println(obj1.getClass());
Object obj2 = applicationContext.getBean("&getActorFactoryBean");
System.out.println(obj2.getClass());
}
}
容器中存在的bean是getObject()方法返回的对象,加&可获取factoryBean本身。

Bean的生命周期
主要参考了这篇博客,总结得很好。
流程图

分类
Bean的完整生命周期经历了各种方法调用,这些方法可以划分为以下几类:
Bean自身的方法:这个包括了Bean本身调用的方法和通过配置文件中bean的init-method和destroy-method指定的方法。
Bean级生命周期接口方法:这个包括了BeanNameAware、BeanFactoryAware、InitializingBean和DiposableBean这些接口的方法。
容器级生命周期接口方法:这个包括了InstantiationAwareBeanPostProcessor 和 BeanPostProcessor 这两个接口实现,一般称它们的实现类为“后处理器”。
工厂后处理器接口方法:这个包括了AspectJWeavingEnabler, ConfigurationClassPostProcessor, CustomAutowireConfigurer等等非常有用的工厂后处理器接口的方法。工厂后处理器也是容器级的。在应用上下文装配配置文件之后立即调用。
用法
通过@Bean的initMethod和destroyMethod分别指定初始化方法、销毁方法。
- 对象创建完成、并赋值好,才调用initMethod;
- 单例bean会执行销毁方法,多实例bean不会执行销毁方法;
- 单示例:在容器启动时创建对象;多实例:在每次获取时创建对象。
- 初始化方法都执行
通过让Bean实现InitializingBean、DisposableBean来实现参与初始化、销毁。
InitializingBean的afterPropertiesSet方法在initMethod方法之前执行;
DisposableBean的destroy方法在destroyMethod之前执行。
通过@PostConstruct、@PreDestroy注解来指定
这两个是J2EE中的注解,需要导入javax.annotation-api依赖,如下:
// https://mvnrepository.com/artifact/javax.annotation/javax.annotation-api
compile group: 'javax.annotation', name: 'javax.annotation-api', version: '1.3.2'
@PostConstruct在bean的所有依赖都注入完成后,会调用。在InitializingBean的afterPropertiesSet方法之前执行(测试结果如此)。
@PreDestroy在DisposableBean的destroy方法之前执行(测试结果如此)。
通过实现BeanPostProcessor方法来
这个接口太厉害了,数据校验、@Autowire注入等等都是通过此接口来实现的。它有两个函数,他们的执行时机如下所示:

示例
对上面4种情况,参考一些资料写了如下demo,验证其中的调用顺序。
测试bean
public class LifeCycleTestBean
implements InitializingBean, DisposableBean,
BeanFactoryAware, BeanNameAware {
public LifeCycleTestBean() {
System.out.println("构造器执行...");
}
public void init1() {
System.out.println("bean init method -> init1...");
}
@PostConstruct
public void init2() {
System.out.println("PostConstruct...init2...");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean...afterPropertiesSet...");
}
@Override
public void destroy() throws Exception {
System.out.println("DisposableBean...destroy...");
}
@PreDestroy
public void destroy2() throws Exception {
System.out.println("PreDestroy...destroy...");
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("BeanFactoryAware...setBeanFactory...");
}
@Override
public void setBeanName(String name) {
System.out.println("BeanNameAware...setBeanName...");
}
}
bean后置处理器
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Before 【" + beanName + "】 initialization");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("After 【" + beanName + "】 initialization");
return bean;
}
}
配置类
@Configuration
public class LifeCycleConfig {
@Bean(initMethod = "init1", destroyMethod = "destroy2")
public LifeCycleTestBean getLifeCycleTestBean() {
return new LifeCycleTestBean();
}
@Bean
public MyBeanPostProcessor getMyBeanPostProcessor() {
return new MyBeanPostProcessor();
}
@Bean
public Actor getActor() {
return new Actor();
}
}
启动类
public class LifeCycleMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
System.out.println("容器创建完成...");
LifeCycleTestBean lifeCycleBean = ac.getBean(LifeCycleTestBean.class);
ac.close();
}
}
结果。结论都写在上面了。

赋值

@Value
- 基本数值;
@Value("洋芋") - SpEL;
@Value("#{20 - 2}") - 配置文件取值。
@Value("${server.port}")。可以用@PropertySource("classpath:/**.properties")指定外部配置文件
自动装配
Spring利用依赖注入(DI),完成对IOC容器中各个组件的依赖关系赋值。
@Autowired
Spring注解。 默认优先按类型去容器中找TestBean组件。
@Autowired
private TestBean testBean1;
如果有多个TestBean,默认情况下,会再按照属性名testBean1去匹配。此时也可以用@Qualifier指定想要的beanName。如下会加载beanName为testBean的bean。
@Qualifier("testBean")
@Autowired
private TestBean testBean1;
默认情况下,如果IOC容器中没有相应的bean,会报初始化错误。可以通过@Autowired注解中的required属性来设置,默认为true。即:
@Autowired(required = false)
private TestBean testBean1;
还可以通过@Primary来设置首选bean。
@Autowired标注在不同地方的区别:
- set方法上。从IOC容器中取bean作为参数。
- 构造器上。从IOC容器中去bean作为参数;一个参数时,可省略@Autowired。
- 参数上。从IOC容器中去bean作为参数。
- 被@Bean注解的函数的参数,可用@Autowired注入,可省略。
@Resource
Java规范注解。 默认按照组件名称进行装配,可以通过name属性修改。不支持@Primary、没有required属性。
@Inject
Java规范注解。 需要导入依赖javax.inject。与@Autowired类似,但是没有required属性。
环境相关
@Profile
它的作用是指定此Bean所对应的环境,不是该环境时,不加载此bean。环境可以通过spring.profiles.active属性来指定,也可以通过JVM参数:-Dspring.profiles.active=test来指定。
获取Spring容器底层组件

想要名为xxx的组件,实现xxxAware接口,最后交由xxxAwareProcessor。
例如:获取ApplicationContext需要实现ApplicationContextAware,而对它的处理是通过ApplicationContextAwareProcessor来处理,ApplicationContextAwareProcessor即实现了BeanPostProcessor,也就是前面所遇到的那个后置处理器。
Spring AOP
基础概念
- 通知(Advice)具体做的工作。比如打印日志。
- 连接点(JoinPoint)可以进行扩展的一个时机。比如说调用方法时、修改一个字段时等,但spring只支持调用方法时。
- 切点(PointCut)通知所要织入的具体位置。以调用方法这个连接点为例,方法有很多,不可能为所有的方法都加入通知,那些被选中的方法就是切点。
- 切面(Aspect)切点与切面的集合。
- 织入(Weaving)将切面应用到目标对象并创建代理对象的过程。通俗理解,把目标对象装饰加强成加入通知的代理对象。有3种织入时期:编译期、类加载期、运行期(Spring AOP)。

使用步骤
导入aop模块,即spring-aspects依赖
compile project(":spring-aspects")
定义业务逻辑类。
public class Divider {
public int div(int x, int y) {
return x / y;
}
}
定义切面类。
通知包括:①前置通知(@Before)、②后置通知(@After)、③返回通知(@AfterReturning)、④异常通知(@AfterThrowing)、⑤环绕通知(@Around),分别对应目标方法执行前、执行后、返回后、异常时。
@Aspect
public class DividerAspect {
@Pointcut("execution(* run.oxffff.spring.beans.Divider.div(..))")
public void pointcut() {
}
@Before("pointcut()")
public void before() {
System.out.println("Before div()");
}
@After("pointcut()")
public void after() {
System.out.println("After div()");
}
@AfterReturning("pointcut()")
public void afterReturning() {
System.out.println("After div() returning");
}
@AfterThrowing("pointcut()")
public void afterThrowing() {
System.out.println("After div() throwing");
}
}
@Aspect一定要加,这个注解相当于一个开关,加上了才会进行织入操作。
在切面类的目标方法中标注何时在何处运行。
可使用@PointCut简化代码。切点的写法可参考:

将切面类、业务类都放入IOC容器,并且通过IOC容器获取业务类。
@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
@Bean
public Divider getDivider() {
return new Divider();
}
@Bean
public DividerAspect getDividerAspect() {
return new DividerAspect();
}
}
必须使用@Aspect指明哪个bean将作为切面类。
在@Configuration注解标注的配置类中,开启注解@EnableAspectJAutoProxy。
启动类
public class AopMain {
public static void main(String[] args) {
ApplicationContext ac = new AnnotationConfigApplicationContext(AopConfig.class);
Divider divider = ac.getBean(Divider.class);
System.out.println(divider.div(1, 2));
System.out.println(divider.div(1, 0));
}
}
执行结果

注意:JoinPoint在切面方法中如果要注入,必须要放在第一个参数位上。为啥?深刻怀疑是用了args[0],哈哈。