《Spring In Action(第4版)》阅读总结(一)Spring上下文、Bean的生命周期、装配Bean

本文基于Spring5.05
官网地址:https://projects.spring.io/sp…

spring 加载应用上下文

AnnotationConfigApplicationContext一个或多个Java配置类加载Spring应用上下文
AnnotationConfigWebApplicationContext一个或多个Java配置类加载Spring Web应用上下文
ClassPathXmlApplicationContext从类路径下的一个或多个XML配置文件加载上下文,把应用上下文的定义文件作为类资源
FileSystemXmlApplicationContext从文件系统下的一个或多个XML配置文件加载上下文
XmlWebApplicationContext从Web应用下的一个或多个XML配置文件加载上下文
GenericApplicationContext以可刷新的方式读取不同bean定义格式加载上下文

Bean的生命周期

实例化Spfing对Bean进行实例化
填充属性Spring将值和bean的引用注入到bean对应的属性中
BeanNameAware 的 SetBeanName()如果Bean实现了BeanNameAware接口,Spring将bean的id传递给SetBeanNamer()方法
BeanFactoryAware 的 setBeanFactory()如果Bean实现了BeanFactoryAware,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
ApplicationContextAware 的 setApplicationContext()如果Bean实现了ApplicationContextAware,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
BeanPostProcess 的 postProcessBeforeInitialization()
InitializingBean 的 afterPropertiesSet() 和自定义初始方法@PostConstruct -> InitializingBean -> init-method
BeanPostProcess 的 postProcessAfterInitialization()
已完成Bean的初始化,使用Bean直至容器关闭
DisposableBean 的 destory()方法 和自定义销毁方法@PreDestory -> DisposableBean -> destory-method

装配Bean

  1. 声明bean
  2. 构造器注入和Setter方法注入
  3. 装配bean
  4. 控制bean的创建和销毁

Spring配置的可选方案

  1. 在XML中进行显式配置
  2. 在Java中进行显式配置(推荐,通常用于第三方Bean定义)
  3. 隐式的bean发现机制和自动装配(推荐)

实现自动化配置

1)组件扫描(compoent sacnning):Spring会自动发现应用上下文所创建的bean
2)自动装配(autowiring):Spring自动满足bean之间的依赖
实现自动化配置通常是在Java类中声明注解,并通过组件扫描使指定范围内的注解生效的一种方式

I 在XML中进行显式配置

元素父元素属性说明
<bean>
声明Bean定义
<beans>id唯一标示
name名称,多个用’,’隔开,不能与已有id重复
class指定实例类型
scope单例/多例
lazy-init懒加载
autowire自动装配策略
autowire-candidate是否参与自动注入
primary优先注入
depends-on依赖指定bean
init-method自定义初始化方法
destroy-method自定义销毁方法
abstract抽象类,是则不创建对象
parent指定父类Bean,继承父类属性值
factory-bean指定工厂Bean
factory-method指定工厂方法
<alias>
声明Bean的别名
<beans>name指定Bean
alias别名
<property>
通过Setter方法初始化Bean
(可使用P-命名空间代替)
<bean>name指定属性名,以setName()形式
ref给引用类型指定bean
value给基本类型赋值
<constructor-arg>
通过构造器方法初始化Bean
(spring3.0后可使用c-命名空间代替)
<bean>index构造方法参数索引,从0开始
type构造方法参数类型,会有歧义
name构造方法参数名称
ref引用类型指定bean
value基本类型赋值
<set>/<List>
声明集合
<constructor-arg>/<property>注入参数为集合时使用(c/p-命名空间无法装配集合)
<ref>
声明集合元素
<set>/<List>bean指定Bean定义
<value>
声明集合元素
<set>/<List><value>val</value>指定集合字面量元素值
util-命名空间<beans><util:constant>应用某个类型的public static域,并将其暴露为bean
<util:list>创建一个java.util.List类型的bean,其中包含值与引用
<util:set>创建一个java.util.Set类型的bean,其中包含值与引用
<util:map>创建一个java.util.Map类型的bean,其中包含值与引用
<util:properties>创建一个java.util.properties类型的bean
<util:property-path>应用某个类型的public static域,并将其暴露为bean
<import>引入新的xml配置

Ps:关于p/c-命名空间,添加xml配置后,在bean中作为属性使用
语法:p:属性名[-ref]=”字面量或BeanId”
属性名可设置为 name(参数名)、_0(参数下标,从0开始)、_(只有一个参数时可使用)

II 在Java中进行显式配置

注解说明属性属性说明
@Bean声明Bean定义,定义在方法上,Bean的Id默认为方法名value/name指定Bean的Id
autowire指定自动注入策略,默认为NO
initMethod指定自定义初始化方法
destroyMethod指定自定义销毁方法
@Scope指定作用域value/scopeName设置单例、多例等作用域
proxyMode设置动态代理模式,JDK、cglib等
@Import导入新的Java配置类value指定要导入的Java配置类,可设置单个(a.class)或数组({a.class,b.class})
@ImportResource导入新的XML配置value指定导入XML配置路径,可为单个(“a.xml”)或数组({“a.xml”,”b.xml”})
@PropertySource导入新的properties文件value指定导入properties文件路径,可为单个(“a.properties”)或数组({“a.properties”,”b.properties”})
III 实现自动化装配

注解说明属性属性说明
@Configuration声明配置类,Spring会从配置类中加载上下文value
@Component声明组件,同@Controller,@Service…value指定Bean的Id,也可通过@Named声明Id(jsr330)
@Autowired可修饰类变量、set方法,以ByType方式自动注入组件,同@Resource(jsr250),@Inject(jsr330)required默认true,未找到注入的Bean会报错,false关闭,关闭时注意NullPointerException
@Qualifier与Autowired配置使用,指定Bean的Id注入value指定Bean的Id
@ComponentScan启用组件扫描,也可在XML中配置<context:component-scan>basePackages指定扫描包,可接受单个包名(“com.*”)和数组{“service”,”controller”},不安全,重构代码修改包结构会出现问题
basePackageClasses指定类所在的包作为组件扫描的基础包。可通过在需要导入的包中创建并指定Marker interface空接口

高级装配

  1. Spring profile
  2. 条件化的bean声明
  3. 自动装配与歧义性
  4. bean的作用域
  5. Spring表达式语言

环境与profile

在Spring3.1中,Spring引入了bean profile的功能,要使用profile,将所有不同的bean定义整理到一个或多个profile之中,在将应用部署到每个环境时,要确保对应的profile处于激活(active)的状态。没有指定profile的bean始终会被创建

在Java中声明profile

@Profile()
在Spring3.1中,只能在类级别上使用@Profile注释,与@Configuration配合使用
在Spring3.2开始,可以再方法级别上使用,与@Bean注解一同使用

在XML中声明profile

在<beans>中声明profile属性
可在<beans>中嵌套声明<beans> 实现多个profile共存一个xml文档

激活profile,依赖于两个独立的属性

spring.profiles.active 设置激活的profile
spring.profiles.default active未设置则默认为default值

设置激活的方式

  1. 作为DispatcherServlet的初始化参数 <init-param>
  2. 作为Web应用的上下文参数 <context-param>
  3. 作为JNDI条目
  4. 作为环境变量 ctx.getEnvironment().setActiveProfiles(“dev”);ctx.refresh();
  5. 作为JVM的系统属性 -Dspring.profiles.active=”dev”
  6. 在集成测试类上,使用@ActiveProfiles注解设置

条件化的bean

Spring4引入@Conditional注解
可以使用到带有@Bean注解的方法上。如果给定的条件为true,就会创建这个Bean,否则忽略。

@Conditional可以指定任意实现Condition接口的类型,
并实现boolean matches(ConditionContext context,AnnotatedTypeMetadata metadata)返回true则创建Bean

ConditionContext的作用:

  1. getRegistry() 返回的BeanDefinitionRegistry检查bean定义
  2. getBeanFactory() 返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性
  3. getEnvironment() 返回的Environment检查环境变量是否存在以及它的值是什么
  4. getResourceLoader() 返回的ResourceLoader 检查加载的资源
  5. getClassLoader() 返回的ClassLoader 加载并检查类是否存在

AnnotatedTypeMetadata的作用:

检查带有@Bean注解的方法上还有什么其他注解
isAnnotatrd() 指定注解是否存在
getAnnotationAttributes 等获取注解集合

spring4开始,Profile注解进行重构,使其基于@conditional和Condition实现。

处理自动装配的歧义性

仅有一个Bean匹配所需的结果时,自动装配才是有效的。
如果不仅有一个bean能够匹配结果的话,这种歧义性会阻碍Spring自动装配属性、构造器参数或方法参数。

消除歧义性

  1. 选Bean中的某一个设为首选primary,@Primary(Java配置)、<bean primary=”true”>(XML配置)
  2. 使用限定符(qualifier)帮助Spring将可选Bean的范围缩小到一个bean。@Qualifier

Bean的作用域

单例(singleton): 在整个应用中,只会创建bean的一个实例(默认)
原型(prototype):每次注入或通过Spring应用上下文获取的时候,都会创建一个新的bean实例。
会话(session) :在Web应用中,为每个会话创建一个bean实例
请求(request) :在Web应用中,为每个请求创建一个bean实例
Java配置:使用 @Scope 注解声明作用域,可以与@Component 或 @Bean一起使用
XML配置:<bean scope=”singleton”>
可通过ConfigurableBeanFactory.SCOPE_PROTOTYPE 或 SCOPE_SINGLETON 指定
也可用WebApplicationContext.SCOPE_SESSION等(需要在Web应用中使用)
@Scope 还有一个proxyMode 可以设置动态代理模式(通过ScopedProxyMode 枚举类来设置JDK、cglib等)
在XML中声明作用域代理:<bean><aop:scoped-proxy ></bean>默认是cglib
可通过设置proxy-target-class=false 更改为JDK代理

Spring表达式语言(Spring Expression Language)

运行时值注入

1)属性占位符(Property placeholder)
2) Spring表达式语言(SpEL)

注入外部的值

使用@PropertySource和Environment

最简单方式:声明属性源(@PropertySource导入资源)并通过Spring的Environment来检索属性
@Autowired
Environment env;

Environment 常用API

String getProperty(String key)
String getProperty(String key, String defaultValue)
T getProperty(String key, Class<T> type)
T getProperty(String key, Class<T> type, String defaultValue)

String getRequiredProperty(String key)
T getRequiredProperty(String key, Class<T> type)

boolean containsProperty(String var1)

Class<T> getPropertyAsClass(String key, Class<T> type)

String[] getActiveProfiles(); 获取激活的profiles
String[] getDefaultProfiles(); 获取默认的profiles
boolean acceptsProfiles(String… var1); 如果environment支持给定profile的话,就返回true

解析属性占位符

在Spring装配中,占位符的形式为使用”${…}”包装的属性名称

在XML中使用属性占位符(前提是通过<context:property-placeholder location=” “/>引入资源)

<context:property-placeholder location="classpath:demo.properties"/>
<bean id="demo" class="" c:_name="${demo.name}">

在Java中使用属性占位符(需配置一个PropertyPlaceholderConfigurer 或 PropertySourcesPlaceholderConfigurer Bean,Spring 3.1 以后推荐后者,因为其能够基于Spring Environment及其属性源来解析占位符)

public class Demo {
    @Value("${demo.name}")
    private String name;
}

使用Spring表达式语言进行装配

Spring3引入Spring表达式语言(Spring Expression Language,SpEL)。通过表达式,在运行时计算得到值,实现装配。
SpEL的形式为”#{…}”

SpEL的特性包括:

1) 使用Bean的Id来引用Bean
2)调用方法和访问对象的属性
3)对值进行算术、关系和逻辑运算
4)正则表达式匹配
5)集合操作
除依赖注入,Spring Security支持SpEL定义安全限制规则;Thymeleaf模板支持SpEL引用模型数据

列子:

//获取当前时间的毫秒值,T()表达式会将java.lang.System视为Java中对应的类,并调用其静态方法currentTimeMills()
#{T(System).currentTimeMills()} 

//获取Id为demo的Bean,并引用其name属性(应该是通过get方法获取的吧!未验证)
#{demo.name}

//通过systemProperties对象引用系统属性
#{systemProperties['disc.title']}

SpEL基础表达式

//1)表示字面值
#{3.14159}
#{9.87E4}
#{'demo'}
#{false}    //true和false的计算结果就是它们对应的Boolean类型的值

//2)引用bean、属性和方法
#{demo}    //获取Id为demo的Bean的引用
#{demo.name}    //获取Id为demo的Bean的name属性值
#{demo.getName()}
#{demo.getName().toUpperCase()}
#{demo.getName()?.toUpperCase()}    //避免getName()返回null,出现NullPointException,使用'?.'类型安全的运算符,如果getName()返回null,则不会调用toUpperCase(),表达式返回null

//3)在表达式中使用类型(依赖T()这个关键的运算符,其真正价值在于访问目标类型的静态方法和常量)
#{T(java.lang.Math)}    //表示Math的class对象引用
#{T(java.lang.Math).PI}
#{T(java.lang.Math).random()}

//4)SpEL运算符
//算数运算:+ - * / % ^
//比较运算:< > == <= >= lt gt eq le ge
//逻辑运算:and or not
//条件运算:?:(ternary) ?:(Elvis)
//正则匹配:matches
#{2 * T(java.lang.Math).PI * cricle.radius}
#{T(java.lang.Math).PI * cricle.radius ^ 2}    //^ 是用于乘方计算的运算符
#{demo.name + 'and' + demo.realname}    //使用String类型的值,+ 为连接符
#{demo.age == 20}
#{demo.age eq 20}    //比较运算符有两种形式:符号形式和文本形式,两者等同,计算结果为Boolean值
#{demo.name != null ? demo.name : "roylion"}    //三元运算符的一个常见场景:检查null值,并用一个默认值替代null
#{demo.name ?: "roylion"}    //此三元运算符通常称为Elvis运算符,用来简化上述场景。
#{demo.emial matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'}    //匹配正则表达式

//5)计算集合
#{demo.books[4].title}
#{demo.books[T(java.lang.Math).random() * demo.books.size()].title}
//[] 运算符可以从集合或数组中按照索引获取元素,甚至String(基于0开始)
#{'Im a big handsome '[3]}
//.?[...] 对集合进行过滤,得到集合的一个子集,
//[]中接受另一个表达式,当SpEL迭代书本列表时,会对每一本书计算这个表达式,如果为true,则会存放到新的集合中
#{demo.books.?

} #{demo.books.^

} //.^[] 查询第一个匹配项 #{demo.books.$

} //.$[] 查询最后一个匹配项 #{demo.books.!

} //.![] 从集合每个成员中选择特定的属性放到另外一个集合中

保持SpEL表达式的简洁,尽量不要写复杂的SpEL表达式。因为SpEl表达式是String类型,测试困难

    原文作者:roylion
    原文地址: https://segmentfault.com/a/1190000014546153
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞