《Spring实战》学习笔记-------Bean

1. Bean的生命周期

Bean装配到Spring应用上下文中的生命周期:

  1. 实例化
  2. 填充属性:将值和bean的引用注入到bean对应的属性中
  3. 调用BeanNameAware的setBeanName()方法:如果实现该接口,将bean的ID传递给方法
  4. 调用BeanFactoryAware的setBeanFactory()方法:如果实现该接口,将BeanFactory容器实例传入
  5. 调用ApplicationContextAware的setApplicationContext()方法:如果实现该接口,将bean的应用上下文的引用传进来
  6. 调用BeanPostProcessor的预初始化方法:如果实现BeanPostProcessor接口,调用BeanPostProcessorBeforeInitialization方法
  7. 调用InitializingBean的afterPropertiesSet()方法,如果实现InitializingBean接口,调用afterPropertiesSet方法
  8. 调用自定义的初始化方法
  9. 调用BeanPostProcessor的初始化后方法,如果实现了BeanPostProcessor接口,调用BeanPostProcessorAfterInitialization方法
  10. -----bean可以使用了
  11. -----容器关闭
  12. 调用DisposableBean的destroy()方法
  13. 调用自定义的销毁方法

2. Spring模块

Spring模块

3. 自动化装配Bean(重点记前两个)


3.1 隐式的bean发现机制和自动装配*

@Component(“自定义name”)注解:Spring会为这个类创建bean

@ComponentScan注解:Spring会扫描该类所在包的所有带有@Component的类,并创建一个bean。
可以指定要扫描的包:@ComponentScan(basePackages={“packagename”}或basePackgeClasses={xx.class})

@ContextConfiguration(classes=xxx.class):会告诉Spring在xxx中加载配置。

为了避免@Autowired注解抛出“没有发现匹配bean”异常,可以@Autowired(required=false)


3.2 Java中显式配置*

除了自动装配的方式还可以使用JavaConfig的方式(使用第三方组件时):在Config类上加上@Configuration注解表面该类是配置类。

使用@Bean注解表明得到bean实例:

@Bean 
public T xxx1(){
	return new xxx1();
}

需要有别的bean依赖时:

@Bean public T xxx2(){//使用调用上面bean方法的形式,但实际上Spring会拦截对它的调用
	return new xxx2(xxx1());
}@Bean 
public T xxx2(T xx){
	return new xxx2(xx);
}

所有的bean都是单例的,一个bean实例可以注入到多个其它bean中。


3.3.XML装配Bean(古老方式)

STS中创建 File->New->Spring Bean Configuration File

声明bean:<bean id="bean1" class="Package.Class" />

3.3.1 构造器注入bean引用:
<bean id="bean2" class="Package.Class" >
	<constructor-arg ref="bean1" />
</bean>

也可以使用c-命名空间:引入声明:xmlns:c="http://www.springframework.org/schema/c"

<bean id="bean2" class="Package.Class" 
	c:cd-ref="bean1"/> cd是构造器参数名,-ref表面这是一个引用
	c:_0-ref="bean1"/> 参数索引
	c:_-ref="bean1"/> 因为只有一个构造器参数
3.3.2 构造器注入字面量
<bean id="bean3" class="Package.Class" >
	<constructor-arg value="this is a string1" />
	<constructor-arg value="this is a string2" />
</bean>
3.3.3 构造器装配集合

例如:需要装配的Bean中有一个List属性

可以直接传入string

<bean id="bean4" class="Package.Class" >
	<constructor-arg value="this is a string1" />
	<constructor-arg>
		<list>
			<value>string</value>
			<value>string</value>
			<value>string</value>
		</list>
	</constructor-arg>
</bean>

可以引用别的bean

<bean id="bean5" class="Package.Class" >
	<constructor-arg value="this is a string1" />
	<constructor-arg>
		<list>
			<ref bean="bean1">
			<ref bean="bean2">
		</list>
	</constructor-arg>
</bean>
3.3.4 属性注入(自动调用setter方法)

例如对它进行属性注入:

public class Bean1 {
	private Bean2 bean2;
	
	@Autowried
	public void setBean2(Bean2 bean2){
		this.bean2 = bean2;
	}

	public void play(){
		bean2.method();
	}
}
<bean id="bean2" class="Package.Bean2" />

<bean id="bean1" class="Package.Bean1" >
	<property name="bean2" ref="bean2"/>
</bean>

还有一种p-命名空间的方式,类似c-命名空间,先引入声明,p:bean2-ref="bean2" :p(前缀)bean2(属性名)ref(表面引用)“bean2”(所注入bean的id)

同理,还可以将字面量注入属性中,只需将上面的代码块中property标签中的"ref"属性改成"value"。

3.4 导入和混合配置

(个人理解:类似于java中在类中引用别的Package的类要先import一样)

在一个Config类(@Configuration)中引入另一个或多个Config类(@Configuration):

@Configuration
@Import({BConfig.class})
public class AConfig{
	@Bean
	...
}

在JavaConfig中引入XML配置的bean:

@ImportResource("classpath:xxxx.xml")

在XML配置中引用XML:

在根标签下: <import resoune="xxxx.xml" />

在XML配置中引用JavaConfig:只能将config类作为bean配置进来

在根标签下: <bean class="xxxx.JavaConfig" />

4. profile注解和标签

@profile(“xx”)注解:只有当xx profile处于激活时才创建该bean(可以写在类级别和方法级别)

Spring 在确定哪个profile处于激活状态时,需要依赖两个独立的属性:Spring.profiles.active和Spring.profiles.default。如果设置了Spring.profiles.active的话,那么它的值就会用来确定哪个profile是激活的。但如果没有设置该属性,那Sping会查找Spring.profiles.default的值。如果这两个属性均没有设置,那就没有激活的profile,因此指挥创建那些没有定义在profile中的bean。

书上给了在web.xml中设置默认的profile代码:

.....
<context-param>
	<param-name>spring.profiles.default</param-name>
	<param-value>dev</param-value>
</context-param>

<servlet>
	...
	<init-param>
		<param-name>spring.profiles.default</param-name>
		<param-value>dev</param-value>
	</init-param>
	...
</servlet>

测试时可以使用@ActiveProfiles(“dev”)激活"dev"profiles。

5. 条件化的Bean*

如果需要限定bean的特定创建条件,可以使用@Conditional注解:如果给定的条件计算为true就会创建bean,否则不会创建。

假设有一个MagicBean的类,我们希望设置了magic属性它才被创建:

@Bean
@Conditional(MagicExistsCondition.class)---在本例中它指明的条件为MagicExistsCondition
public MagicBean magicBean(){
	return new MagicBean();
}

@Conditional注解通过Condition接口进行条件对比:

public interface Condition(){
	boolean matches(ConditionContext ctxt, AnnotatedTypeMeteadata metedada);
}

@Conditional中设置的类必须实现了Condition接口。通过matches的返回值判断。

在本例中的MagicExistsCondition:

public class MagicExistsCondition implements Condition{
	
		public boolean matches(ConditionContext ctxt, AnnotatedTypeMeteadata metedata){
			Environment env = context.getEnvironment();//通过给定的上下文获取当前环境
			return env.containsProperty("magic");//检查环境中有没有magic属性
		}
}

ConditionContext是一个接口:

public interface ConditionContext{
	BeanDefinitionRegistry  getRegistry();
	ConfigurableListableBeanFactory  getBeanFactory();
	Environment  getEnvironment();
	ResourceLoader  getResourceLoader();
	ClassLoader  getClassLoader();
}
  • getRegistry()可以用来检查bean定义
  • getBeanFactory()可以用来检查bean是否存在,探查bean的属性
  • getEnvironment()可以用来检查环境变量是否存在以及它的值
  • getResourceLoader()可以用来检查ResourceLoader加载的资源
  • getClassLoader()可以用来检查ClassLoader加载或检查类是否存在

AnnotatedTypeMetedata也是一个接口,可以检查带有@Bean注解的方法上还有什么其它的注解

public interface AnnotatedTypeMetedata{
	boolean isAnnotated(String annotationType); //判断这个方法上面有没有annotationType这个注解
	Map<String, Object>  getAnnotationAttributes(String annotationType);
	Map<String, Object>  getAnnotationAttributes(String annotationType, boolean classValuesAsString);
	MutiValueMap<String, Object>  getAllAnnotationAttributes(String annotationType);
	MutiValueMap<String, Object>  getAllAnnotationAttributes(String annotationType, boolean classValuesAsString);
}

6. 处理自动装配的歧义性*(需要装配的bean有多个选择时)

如果有多个bean符合@AutoWired装配,会抛出NoUniqueBeanDefinitionException。

出现这类问题可以将bean中的某一个设为首选(primary)的bean,也可以使用限定符(qualifier)。

6.1 标示首选的bean

自动装配式bean:

@Component
@Primary
public bean1 xxx(){}

Java配置式bean:

@Bean
@Primary
public bean1 xxx(){return new xxx();}

XML配置bean:

<bean id="bean1" class="Package.Class" primary="true" />

6.2 限定符方式

这是利用bean的id的方式:

@Autowired
@Qualifier("beanId")//创建bean时如果没有指定bean的id,会默认将首字母小写的类名作为id
public void setBean(Bean bean){
	this.bean = bean;
}

@Qualifier标签的声明位置不同,相应的作用也不同:声明在@Bean/@Component时代表给这个bean添加限定符;声明在@Autowired时代表使用这个限定符的bean。如下所示:

@Component
@Qualifier("cold") //添加了一个cold的限定符
public class IceCream implements Dessert{....}@Bean
@Qualifier("cold")
public Dessert iceCream(){
	return new IceCream();
}
@Autowired
@Qualifier("cold")//使用cold限定符的bean注入
public void setDessert(Dessert dessert){
	this.dessert = dessert;
}

如果此时又出现了一个带有相同限定符的bean:

@Component
@Qualifier("cold") 
public class Popsicle implements Dessert{....}

java中不允许在同一个条目上重复出现相同的注解,这时候我们可以自定义一个限定符注解:

@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, 
		 ElementType.METHOD, ElementType.TYPE})//可作用的范围,构造器、属性、方法、类
@Retention(RetentionPolicy.RUNTIME)//程序运行时可见,生命周期最长
@Qualifier
public @interface IceCream{};//这里为了方便阅读直接设置为icecream,在实际应用中尽量不要和bean名相同

另一个注解
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, 
		 ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Popsicle{};

上层的cold注解
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, 
		 ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold{};

声明bean时:

@Component
@Cold //添加了一个cold的自定义限定符
@IceCream //添加了一个IceCream的自定义限定符
public class IceCream implements Dessert{....}

@Component
@Cold //添加了一个cold的自定义限定符
@Popsicle //添加了一个Popsicle的自定义限定符
public class Popsicle implements Dessert{....}

使用时:

@Autowired
@Cold
@IceCream //注入icecream bean
public void setDessert(Dessert dessert){
	this.dessert = dessert;

7. bean的作用域

在默认情况下,Spring的所有bean都是单例的。

Spring中bean的作用域有4种:

  • 单例:在整个应用中,只创建bean的一个实例
  • 原型:每次注入或者通过Spring应用Context获取的时候,都会创建一个新的bean实例
  • 会话:在Web应用中,为每个会话创建一个bean实例
  • 请求:在Web应用中,为每个请求创建一个bean实例

原型bean的使用:

@Component //使用JavaConfig的@Bean方式也一样,xml方式可以使用<bean>下的scope="prototype"属性
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)//也可以使用@Scope("prototype"),但是这样更加安全
public class xxx{}

在Web应用中,例如电子商务的购物车bean,如果购物车是单例的,那么所有用户都将向一个同一个购物车添加商品;如果是原型模式,那么用户在当前页添加商品,到了另一个页面可能就不可用了。所以对于购物车来说,会话级的bean是最合适的。

@Component
@Scope(value=WebApplicationContext.SCOPE_SESSION,
		proxyMode=ScopeProxyMode.INTERFACES)
public interface ShoppingCart{}

@Scope同时还有一个proxyMode属性,这个属性解决了将会话或请求级bean注入到单例bean中的问题:单例的bean在Sping应用上下文加载的时候创建,当它创建的时候会试图将会话或请求作用域的bean注入,但是会话bean只有在会话创建了之后才会生成实例,此时并不存在会话bean

proxyMode:如果系统中有多个ShoppingCart实例:每个用户一个。我们希望ShoppingService当前处理的实例恰好是当前会话对应的那个。这时对ShoppingCart使用代理模式,ShoppingService会注入ShoppingCart bean的代理,通过代理对其进行lazy解析并将调用委托给会话作用域内真正的ShoppingCart bean。

配置中的ProxyMode设置成了ScopeProxyMode.INTERFACES,这表明这个代理要实现ShoppingCart接口(最理想的代理模式),并将调用委托给实现bean。如果ShoppingCart是一个具体类的话,那ProxyMode就要设置成:ScopeProxyMode.TARGET_CLASS,使用CGLib生成基于类的代理。


8. 运行时值注入*

在前面的构造器注入字面量时,使用的是硬编码的方式,让这些值在运行时确定有两种方式:

  • 属性占位符
  • Spring表达式语言(SpEL)

8.1 environment获取和属性占位符

两种方式都要提前声明属性源

① 声明属性源并通过Spring的Environment来检索属性:

@Configuration
@PropertySource("classpath:/xxx/xxx.properties")//声明属性源
public class ExpressiveConfig{
	
		@Autowired
		Environment env;
		
		@Bean
		public Bean bean1(){
			return new Bean(env.getProperty("propertyName"));//检索属性值
		}
}

Spring的Environment有以下几个常用方法:

  • String getProperty(String key) : 返回key对应的值
  • String getProperty(String key, String defaultValue) :key对应的值不存在时,返回defaulValue
  • T getProperty(String key, Class<\T> type) :key对应的值不为String时,可以指定返回值的类型
  • T getProperty(String key, Class<\T> type, T defaultValue)
  • String getRequiredProperty(String key) : 返回的属性必须是已存在的,不存在将会抛出非法状态异常
  • Boolean containsProperty(String key) : 检查某个属性是否存在
  • Class<\T> getPropertyAsClass(String key, Class<\T> type) : 将属性解析为类
  • String[] getActiveProfiles() : 返回激活profile名称的数组
  • String[] getDefaultProfiles() : 返回默认profile名称的数组
  • Boolean acceptsProfiles(String profiles) : 如果environment支持给定profile的话返回true

② 属性占位符

XML方式:

<bean id="bean1" class="xxx.Bean" >
	<property name="name" value="${propertyName}" />
</bean>

Java注解方式:

public Bean(@Value("${text}") String text){
	this.text = text;
}

使用占位符之前要配置一个PropertyPlaceholderConfigurer bean:

@Bean
public static PropertyPlaceholderConfigurer PlaceholderConfigurer(){
	return new PropertyPlaceholderConfigurer();
}

8.2 Spring表达式语言

  • 可以引用其它bean或其它bean的属性、方法
#{Bean} //引用id为Bean的bean
#{Bean.field} //引用Bean的field属性
#{Bean.Method}//调用Bean的method方法
  • 加?判断返回值是否为空
#{Bean.getString()?.toUpperCase()}//如果返回值为null就不执行toUpperCase()方法
  • 访问类作用域的方法或常量
#{T(java.lang.Math).PI}
#{T(java.lang.Math).random()}
//T()运算返回一个Class对象,也可以将其装配到一个Class类型的bean中
//更大的价值是它可以访问目标类型的静态方法和常量
  • 支持运算符(±*/%^<>== and or not | 条件运算? 正则表达式matches)
#{2 * T(java.lang.Math).PI * circle.radius}
//PI的值乘2再乘以radius,radius来自id为circle的bean
  • 支持集合操作、查询操作
#{Bean.List[0].property}//访问Bean下的list数组的第一个值的property属性
#{Bean.List.?[property eq 'abc']}//对集合进行过滤,查询返回所有property属性为abc的值的子集
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值