SpringBoot注解IoC

一、Ioc简介

IoC是一种通过描述来生成或者获取对象的技术。在Spring 中把每个需要管理的对象称为Spring Bean,简称Bean,Spring 管理这些Bean的容器,被称为Spring IoC容器(简称IoC容器)。

2个基本作用:

(1)通过描述管理Bean,包括发布和获取Bean;

(2)通过描述完成Bean之间的依赖关系。

Spring IoC 容器其实就是管理Bean的容器。Spring定义中,要求所有的 IoC 容器都需要实现 BeanFactory 接口,它是一个顶级容器接口。

BeanFactory 接口主要代码:

package org . springframework.beans.factory;

public interface BeanFactory {
    // 前缀 用来区分是获取FactoryBean还是FactoryBean的createBean创建的实例.如果&开始则获取FactoryBean;否则获取createBean创建的实例.
    String FACTORY BEAN PREFIX = " &" ;
    //多个getBean方法
    Object getBean(String name) throws BeansException;

    <T> T getBean(String name, Class<T> requiredType) throws BeansException;

    Object getBean(String name, Object... args) throws BeansException; 

    <T> T getBean(Class<T> requiredType) throws BeansException;

    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

    <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);

    <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);

    //是否包含某个Bean
	boolean containsBean(String name);
	//Bean 是否为单例
	boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

	//Bean 是否为原型
	boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
	//是否类型匹配
	boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
 
	boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
 	
    //获取Bean的类型
	@Nullable
	Class<?> getType(String name) throws NoSuchBeanDefinitionException;
	 //获取Bean的别名
	String[] getAliases(String name);
}

源码中有多个 getBean() 的方法,有按类型获取Bean,有按名称获取Bean,Spring IoC容器允许按类型或者名称获取Bean。

isSingleton() 方法判断Bean 在Spring IoC中是否为单例。在Spring IoC中,默认情况下,Bean 都是以单例存在的,即使用 getBean() 方法返回的都是同一个对象。

isPrototype() 方法与isSingleton() 方法刚好相反,如果它返回true, 那么使用 getBean() 方法获取Bean的时候,Spring IoC容器就会创建一个新的Bean返回。

Spring IoC 容器布局:

spring IoC 接口

ApplicationContext 接口通过继承上级接口进而继承 BeanFactory 接口,还在BeanFactory 的基础上扩展了消息国际化接口(MessageSource)、环境可配置接口(EnvironmentCapable)、应用事件发布接口(ApplicationEventPublisher)和资源模式解析接口(ResourcePatternResolver),功能更强大。

XML相关的IoC 容器装配的方式主要是使用<bean> 标签,通过解析bean标签的描述来管理bean及相关依赖。

在SpringBoot 中主要是通过注解来装配BeanSpring IoC容器中,基于注解的IoC 容器为AnnotationConfigApplicationContext

示例:

一个普通的POJO

package com.smallrose.web.app.model;

public class User {

	private String id;
	private String userName;
	private String note;
 	// getter setter  tostring
}

SpringBoot 的配置:

package com.smallrose.web.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.smallrose.web.app.model.User;

@Configuration
public class AppConfig {

	@Bean(name="user")
	private User initUser() {
		User u = new User();
		u.setId("123");
		u.setUserName("zhangxiaocai.cn");
		u.setNote("小菜");
		return u;
	}
}

@Configuration :表示一个Java配置文件,类似Spring的xml配置文件。Spring的容器会根据它来生成IoC容器去装配Bean;

@Bean:表示将 initeUser() 方法返回的POJO装配到IoC容器中,它的属性name 定义这个Bean的名称,如果没有配置name属性,则将方法名称“initUser”作为Bean的名称保存到Srping IoC 容器中。

然后就可以使用AnnotationConfigApplicationContext来构建自己的IoC容器:

package com.smallrose.web.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.smallrose.web.app.model.User;

public class IoCTest {
	
	private static Logger log = LoggerFactory.getLogger(IoCTest.class);

	public static void main(String[] args) {
		ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
		User user = ctx.getBean(User.class);
		System.out.println(user);
	}
}

输出结果:

15:06:34.640 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'appConfig'
15:06:34.656 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'user'
15:06:34.792 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Found key 'spring.liveBeansView.mbeanDomain' in PropertySource 'systemProperties' with value of type String
User [id=123, userName=zhangxiaocai.cn, note=小菜]

二、Bean 的装配

Spring 允许通过XML或Java配置文件装配Bean,但 SpringBoot 是基于注解的方式。

1、扫描装配Bean

前面的例子是使用注解@Bean 注入到SpringIoC 容器,实际项目中,肯定会有很多个Bean,每个都这么写,岂不是要累死人。

Spring 还允许进行扫描装配Bean 到 IoC 容器,对于扫描装配而言使用的注解是@Component@ComponentScan

@Component :用来标明哪个类被扫描进入Spring IoC 容器。

@ComponentScan :用来标明采用何种策略去扫描装配Bean。

实例:

在刚才的 config目录下建一个POJO 如,可以将刚才的User移过来,为了区分我新建一个类:Employee

package com.smallrose.web.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("emp")
public class Employee {

	@Value("123")
	private Long id;
	@Value("zhangxiaocai.cn")
	private String empName;
	@Value("note_xiaocai")
	private String empNote;
	
	//getter setter tostring
}

使用@Component 注解表示该类将会被Spring IoC容器扫描装备,其中“emp”表示作为Bean的名称,也可以不配置这个名称字符串,IoC 容器会把类名第一个字母作为小写其他不变作为Bean的名称放入到IoC容器中;

@Value 注解在学习YML语法的时候遇到过,是用来绑定属性注入参数,也可以直接赋值。

修改AppConfig类:

package com.smallrose.web.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
public class AppConfig {
 
}

这里添加了@ComponentScan 注解,它会进行扫描,默认情况下它只会扫描所在注解类AppConfig所在的当前包和其子包。为什么要把Employee建在config 目录下就是这个原因。

运行测试类:

package com.smallrose.web.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.smallrose.web.app.model.User;

public class IoCTest {
	
	private static Logger log = LoggerFactory.getLogger(IoCTest.class);

	public static void main(String[] args) {
		ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
		Employee emp = ctx.getBean(Employee.class);
		System.out.println(emp);
	}
}

运行结果:

16:07:46.227 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'appConfig'
16:07:46.238 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'emp'
Employee [id=123, empName=zhangxiaocai.cn, empNote=note_xiaocai]

找到注解类ComponentScan源码:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)//在一个类中可重复定义
public @interface ComponentScan {
 	//** 定义扫描的包
	@AliasFor("basePackages")
	String[] value() default {};
 	//** 定义扫描的包
	@AliasFor("value")
	String[] basePackages() default {};
 	//** 定义扫描的类
	Class<?>[] basePackageClasses() default {};
 	// Bean name 生成器
	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

 	// 作用域解析器
	Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
 	// 作用域代理模式
	ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

	 // 资源匹配模式
	String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
 	//是否启用默认的过滤器
	boolean useDefaultFilters() default true;
 	//** 当满足过滤条件时扫描
	Filter[] includeFilters() default {};
	//** 当不满足过滤条件时扫描
	Filter[] excludeFilters() default {};
    // ** 是否启用延迟加载
	boolean lazyInit() default false;
 
    //定义过滤器
	@Retention(RetentionPolicy.RUNTIME)
	@Target({})
	@interface Filter {
 		// 过滤器类型,可以按注解类型或者正则表达式等过滤
		FilterType type() default FilterType.ANNOTATION;
 		// 定义过滤的类
		@AliasFor("classes")
		Class<?>[] value() default {};
 		// 定义过滤的类
		@AliasFor("value")
		Class<?>[] classes() default {};
   		// 匹配方式
		String[] pattern() default {};

	}
}

“**”的是最常用的配置项。

basePackages: 定义扫描的包名,没有定义时只会扫描当前包和子包下的路径;

basePackagesClasses:定义扫描的类;

includeFilters:定义满足过滤器(Filter)条件的 Bean才去扫描

excludeFilters:排除过滤器(Filter)条件的 Bean才去扫描,二者都需要通过一个@Filter去定义,它有一个type类型,这里可以定义为注解或者正则等类型。classes定义注解类,pattern定义正则式类。

按照前面的实例,User类不在config目录,可以将AppConfig中的注解修改为:

@ComponentScan("com.smallrose.web.app.*")

或者

@ComponentScan(basePackages={"com.smallrose.web.app.model"})

或者

@ComponentScan(basePackageClass={User.class})

三种方式都可以使IoC 容器去扫描User 类,而包名可以采用正则式去匹配。

但有些时候需求是扫描一些包,将一些Bean 装配到SpringIoC 容器中,而不想加载这个包里的某些Bean。比如在包com.smallrose.web.app 下可能有service包,在service包下有个UserService类,一般使用@Service 注解标注(该标注注入了@Component,在默认情况下会被Spring 扫描装配到 IoC容器),类如下:

package com.smallrose.web.app.service;

import org.springframework.stereotype.Service;

import com.smallrose.web.app.model.User;

@Service
public class UserService {

	public void printUser(User user){
		System.out.println(user);
	}
}

如果正常启动,那么这个类是会被扫描到Spring IoC 容器中。假设不想装配这个类呢?

则需要把AppConfig 扫描策略修改为:

@ComponentScan(basePackages = "com.smallrose.web.app.*",
	excludeFilters = {@Filter(classes = {Service. class})})

记得把User类添加@Component注解:

package com.smallrose.web.app.model;

import org.springframework.stereotype.Component;

@Component
public class User {

	private String id;
	private String userName;
	private String note;
 	// getter setter toString
	
}

运行测试类:

package com.smallrose.web.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.smallrose.web.app.model.User;
import com.smallrose.web.app.service.UserService;

public class IoCTest {
	
	private static Logger log = LoggerFactory.getLogger(IoCTest.class);

	public static void main(String[] args) {
		ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
		User user = ctx.getBean(User.class);
		System.out.println(user);
		
		UserService userservice = ctx.getBean(UserService.class);
		System.out.println(" userservice "+ userservice);
	}
}

运行结果:

17:18:43.019 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'appConfig'
17:18:43.030 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'user'
User [id=null, userName=null, note=null]
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.smallrose.web.app.service.UserService' available
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:352)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:343)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1127)
	at com.smallrose.web.config.IoCTest.main(IoCTest.java:20)

可以看到User类已经装配了,只是属性默认null值,而UserService则提示没有定义这个Bean,说明UserService 类根本没有进行装配。在加入了excludeFilters的配置,使标注了@Service的类将不被IoC容器扫描注入,这样就可以把UserService 类排除出 Spring IoC中了。

@SpringBootApplication注解中也注入了@ComponentScan,源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
//自定义排除扫描类
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
 	// 通过类型排除自动类型类
	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};
 	// 通过名称排除自动类型类
	@AliasFor(annotation = EnableAutoConfiguration.class)
	String[] excludeName() default {};
 	// 定义扫描包
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};

 	// 定义被扫描的类
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};

    // Bean 的名称生成器
	@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
 	
    //是否生成代理bean方法
	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;
}

excludeexcludeName 两个方法是对于其内部的自动配置类才会生效。如果要自己排除其他类,可以加入@ComponentScan达到目录,如上面的扫描User而不扫描UserService可以修改启动配置文件:

@SpringBootApplication
@ComponentScan(basePackages = "com.smallrose.web.app.*",
	excludeFilters = {@Filter(classes = {Service. class})})
public class SpringiocApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringiocApplication.class, args);
	}

}

2、自定义第三方Bean

有时候需要引入第三方的Bean,比如DataSource,使用@Bean注解即可。

示例:

引入DBCP数据源

<dependency>
    <groupid>org.apache.commons</groupid>
    <artifactid>commons-dbcp2</artifactid>
</dependency>
<dependency>
    <groupid>mysql</groupid>
    <artifactid>mysql-connector-java</artifactid>
</dependency>

然后在AppConfig.java 中添加到IoC容器即可:

@Bean(name="dataSource")
public DateSource getDataSource(){
	Properties props = new Properties();
    props.setProperty("driver","com.mysql.jdbc,Driver");
    props.setProperty("url","jdbc:mysql://localhost:3306/test");
    props.setProperty("username","root");
    props.setProperty("password","123456");
    DateSource dataSource = null;
    try{
        dataSource = BasicDataSourceFactory.creatDataSource(props);
    }catch(Exception e){
    }
    return dataSource;
}

三、依赖注入

1、@Autowired 注解

Spring 中常用注解之一。注入机制最基本的一条是根据属性的类型(by type)找到对应的Bean 进行注入。在Ioc的顶级接口BeanFactory 中就有getBean() 方法获取对应的Bean ,getBean() 支持根据类型或根据名称获取方式。

比较常见的就不举例了。在常见的操作里,一个接口一个实现类,实现类使用@Service 标注,然后使用接口注入的方式进行使用。

这里举个不一样的例子,模拟调用支付的,请求只调用支付接口,接口分布被用微信支付和支付宝支付实现。

接口:

package com.smallrose.web.app.service;

public interface PayServiceI {

	public void pay();
}

微信支付实现类:

package com.smallrose.web.app.service.impl;

import org.springframework.stereotype.Service;
import com.smallrose.web.app.service.PayServiceI;

@Service
public class WxPayImpl implements PayServiceI{

	@Override
	public void pay() {
		System.out.println("使用微信支付....");
	}
}

支付宝支付实现类:

package com.smallrose.web.app.service.impl;

import org.springframework.stereotype.Service;
import com.smallrose.web.app.service.PayServiceI;
@Service
public class AliPayImpl implements PayServiceI{

	@Override
	public void pay() {
		System.out.println("使用支付宝支付....");
	}

}

在controller 中注入调用:

package com.smallrose.web.app.controller;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import com.smallrose.web.app.service.PayServiceI;

@Controller
public class PayController {

	@Autowired
	private PayServiceI payservice ;
	
	@GetMapping("/pay")
	public void toPay(HttpServletRequest request){
		payservice.pay();	
	}
}

在示例中可以看到,输入值是一个支付操作,但是却有两种支付方式,SpringIoC 注入怎么办呢?启动程序发现会有以下错误信息:

Description:

Field payservice in com.smallrose.web.app.controller.PayController required a single bean, but 2 were found:
	- aliPayImpl: defined in file [D:\dev-toos\sts-bundle\stsWork\SpringBootLearn\target\classes\com\smallrose\web\app\service\impl\AliPayImpl.class]
	- wxPayImpl: defined in file [D:\dev-toos\sts-bundle\stsWork\SpringBootLearn\target\classes\com\smallrose\web\app\service\impl\WxPayImpl.class]

程序注入需要一个Bean 但是现在发现了两个匹配的Bean,所以就不知道到底使用哪一个了,怎么办呢?

将属性名称改成对应的实现类名称

package com.smallrose.web.app.controller;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import com.smallrose.web.app.service.PayServiceI;

@Controller
public class PayController {

	@Autowired
	private PayServiceI wxPayImpl ;
	
	@GetMapping("/pay")
	public void toPay(HttpServletRequest request){
		wxPayImpl.pay();	
	}
}

将属性名称从 payservice 改成 wxPayImp 为什么可以?

因为@Autowired 首先根据类型找对应的Bean,如果对应的类型的Bean 不是唯一的,那么会根据其属性名称和Bean 的名称进行匹配。如果匹配得上就会使用该Bean,如果还无法匹配,就会抛出异常。

注意,@Autowired 是一个默认必须找到对应Bean的注解,如果不能确定其标注属性一定会存在并且允许这个被标注的属性为null,那么可以配置@Autowired 的属性requiredfalse

@Autowired(required=false)

另外,@Autowired 除了可以标注属性外,还可以标注方法,如setPayservice方法:

@Autowired
public void setPayservice(Payservice payservice){
	this.payservice = payservice;
}

2、消除歧义

在上面,支付方式有两种的时候,为了使@Autowired 能继续使用,将属性名称 payservice 改成 wxPayImp 。

产生注入失败的问题根本是按类型查找时,发现了多个匹配Bean,造成IoC 容器注入的困扰,这个问题成为歧义性问题。

如果不修改属性名称,消除歧义的办法有哪些呢?

(1)使用 @Primary 注解,它是一个修改优先权的注解。当有微信支付和支付宝支付的时候,假设这个是使用微信支付,那么只需要在微信支付的类上加入 @Primary 注解即可。

package com.smallrose.web.app.service.impl;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;

import com.smallrose.web.app.service.PayServiceI;

@Service
@Primary
public class WxPayImpl implements PayServiceI{

	@Override
	public void pay() {
		System.out.println("使用微信支付....");
	}
}

@Primary 注解会告诉spring IoC 容器,放发现多个相同类型的Bean 时,请优先使用我进行注入。

这样通过优先级变换使得IoC 容器知道那个具体的实例满足依赖注入。

但是, @Primary 注解可以使用在多个类上,微信支付和支付宝支付都添加了该注解,那么IoC容器还是无法区分采用哪个Bean的实例进行注入。那么可以使用@Quelifier注解来灵活实现注入。它将和@Autowired 注解 组合在一起,通过类型和名称一起找到Bean。因为Bean名称在Spring IoC容器中是唯一标识。这样就可以消除歧义性。

package com.smallrose.web.app.controller;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import com.smallrose.web.app.service.PayServiceI;

@Controller
public class PayController {

	@Autowired
	@Qualifier("wxPayImpl")
	private PayServiceI payService ;
	
	@GetMapping("/pay")
	public void toPay(HttpServletRequest request){
		payService.pay();
	}
}

3、带参数的构造方法类装配

上面都是不带参数的构造方法下实现的依赖注入。但有时候有些累只有带参数的构造方法,可以使用@Autowired

注解对构造方法的参数进行注入。

@Component
public class PersonBean {
    
	private PayServiceI payService ; 
    
    public PersonBean(@Autowired @Qualifier("wxPayImpl") PayServiceI payService){
    	this.payService=payService;
    }
}

四、生命周期

IoC 容器装配和销毁Bean的过程,即Bean 的生命周期过程,大致分为Bean定义、Bean的初始化、Bean的生存期、Bean的销毁4个部分。

Bean定义过程:

(1)Spring 通过我们的配置,如@ComponentScan定义的扫描路径去找到带有@Component的类,这个过程就是资源定位的过程。

(2)找到资源之后,就开始解析,并且将定义的信息保存起来(保存到BeanDefinition的实例中)。(注意,此时没有初始化Bean,也没有Bean实例)

(3)接着会把Bean 定义发布到Spring IoC容器中。(注意,此时也只有Bean定义,没有初始化Bean,没有Bean实例)

完成3步只是资源定位并将Bean的定义发布到 IoC 容器的过程,还没有Bean 实例的生成,更没有完成依赖注入。

在默认情况下,Spring 会继续去完成Bean 的实例化和依赖注入,这样从 IoC容器中就可以得到一个依赖注入完成的Bean。

Spring Bean 的初始化流程:

资源定位       ——>       Bean定义          ——>     发布Bean定义    ——>    实例化   ——>  依赖注入(DI)
@ComponentScan    保存到BeanDefinition的实例     IoC容器装载Bean定义    创建Bean实例    @Autowired注入

ComponentScan 中还有一个配置项 lazyInit,只可以配置 Boolean 值,且默认值为 false ,也就是 默认不进行延迟初始化,因此在默认的情况下 Spring 会对 Bean 进行实例化和依赖注入对应的属性值。

使用的话在配置类 AppConfig 的@ComponentScan 中加入 lazylnit 配置,如下面的代码:

@ComponentScan(basePackages = "com.smallrose.web.app.*", lazyinit = true)    

这样IoC容器不会在发布Bean定义后马上完成实例化和依赖注入,而是在使用Bean的时候进行实例化和依赖注入。

Spring Bean 的生命周期

(1)这些接口和方法,在没有注释说明的情况下的流程节点都是针对单个Bean,但是BeanPostProcessor是针对所有Bean。

(2)即使定义了ApplicationContextAware 接口,有时并不会调用,这要根据IoC容器来决定。Spring IoC容器的最低要求是实现BeanFactory接口,而不是实现ApplicationContext 接口。对于没有实现ApplicationContextAware 接口的容器,在生命周期对于的ApplicationContextAware 定义的方法也是不会被调用的,只有实现了ApplicationContext 接口的容器,才好在生命周期调用ApplicationContextAware 所定义的setApplicationContext方法。

五、条件装配Bean

有时因为某些客观因素会使一些Bean无法进行初始化,如数据库连接池的配置中漏掉了一下配置会造成数据源不能连接上,这时IoC容器如果还进行数据源装配,系统将会抛出异常,导致应用无法继续,这时希望IoC容器不去装配数据源。

为了处理这种场景,Spring 提供了 @Conditional注解来实现,同时它需要配合另外一个Condition接口来完成对应的功能。(org.springframework .context.annotation.Condition)

如之前配置数据源:

package com.smallrose.web.config;

import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

import com.smallrose.web.condition.DatabaseConditional;

@Configuration
public class AppConfig {

	@Bean(name="dataSource",destroyMethod="close")
	@Conditional(DatabaseConditional.class)
	public DataSource getDataSource(
			@Value("${database.driverName}") String driver,
			@Value("${database. url}")  String url ,
			@Value("${database.username}") String username ,
			@Value("${database. password }") String password
			){
		Properties props = new Properties();
	    props.setProperty("driver",driver);
	    props.setProperty("url",url);
	    props.setProperty("username",username);
	    props.setProperty("password",password);
	    DataSource dataSource = null;
	    try{
	        dataSource = BasicDataSourceFactory.createDataSource(props);
	    }catch(Exception e){
	    }
	    return dataSource;
	}
}

添加@Conditional注解,并且配置类DatabaseConditional,这个类必须现实Condition接口。对于Condition接口则要求实现matches方法:

package com.smallrose.web.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class DatabaseConditional implements Condition{
	/**
	 * 数据库装配条件
	 * @param context 条件上下文
	 * @param metadata 注释类型的元数据
	 * @return true 装配 Bean ,否则不装配
	 */
	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		//取出环境配置
		Environment evn = context.getEnvironment();
		// 判断属性文件是否存在对应的数据库配置
		return evn.containsProperty("database.driverName") 
				&& evn.containsProperty("database.url") 
				&& evn.containsProperty("database.userName")
				&& evn.containsProperty("database.password");
	}
}

matches 方法首先读取其上下文环境 , 然后判定是否已经配置了对应的数据库信息。这样,当这 些都己经配置好后则返回 true。这个时候 Spring 会装配数据库连接池的 Bean ,否则是不装配的。

六、Bean的作用域

BeanFactory接口中,有isSingletonisPrototype 两个方法。其中, isSingleton 方法如果返回 true ,则 Bean 在 loC 容器中以单例存在,这也是 Spring IoC容器的默认值 ;如果 isPrototype 方法返回 true,则 当我们每次获取 Bean 的时候, IoC 容器都会创建一个新的 Bean,这显然存在很大的不同,这便是 Spring Bean 的作用域的问题。

在一般容器中,Bean 都会存在单例(Singleton)和原型(Prototype)两种作用域。而Web 容器,则存在页面(page)、请求( request )、会话 ( session )和应用( Application )4种作用域。对于页面(Page)是针对JSP当前页面而言,spring 无法支持。

Bean的作用域

作用域类型 使用范围 描述
singleton 所有spring应用 默认值,IoC容器值存在单例
prototype 所有spring应用 每当从IoC容器取出一个Bean,就会创建一个新的 Bean
session spring web应用 HTTP会话
application spring web应用 Web 工程生命周期
request spring web应用 Web 工程单词请求(request)
globalSession spring web应用 在一个全局的HTTP Session,一个Bean定义对应一个示例。
实践中基本不使用

常用的是加粗的4种,对于application 作用域,完全可以使用单例来代替。

单例 ( Singleton)和原型( prototype )的区别

不进行任何配置默认就是单例。单例就是每次获取的Bean实例都他相同的一个Bean。

如果要进行原型配置,则在需要配置的Bean上添加作用域注解@Scope

@Scope(ConfiguarebleBeanFactory.SCOPE_PROTOTYPE)

示例:

package com.smallrose.web.config;

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class BeanTest {

}

测试:

package com.smallrose.web.test;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.smallrose.web.config.AppConfig;
import com.smallrose.web.config.BeanTest;

public class IoCTest {

	private static Logger log = LoggerFactory.getLogger(IoCTest.class);

	public static void main(String[] args) {
		AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
		BeanTest bean1 = ctx.getBean(BeanTest.class);
		BeanTest bean2 = ctx.getBean(BeanTest.class);
		System.out.println(bean1==bean2);
	}
}

测试结果:false

注释掉@Scope 结果为 true 。

ConfigurableBeanFactory 只能提供 单 例 ( SCOPE_SINGLETON )和 原 型 ( SCOPE_ PROTOTYPE ) 两种作用域供选择 , 如果是在 SpringMVC 环境中,还可以使用 WebApp l icationContext 去定 义其他作用域 , 如请求( SCOPE REQUEST )、 会话 ( SCOPE_SESSION ) 和应用 ( SCOPE APPLICATION ) 。

如:

package com.smallrose.web.config;

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(WebApplicationContxt.SCOPE_REQUEST)
public class BeanTest {

}

这样同一个请求范围内去获取这个Bean的时候,只会共用同一个Bean,第二次请求就会产生新的Bean。

七、使用 @Profile

实际开发中,常常是多环境切换。每套环境可能一些上下文配置不一样、数据库连接不一样等。Spring 提供了 Profile 机制,可以在不同环境之间切换。

复习一下配置文件的方式的话之间使用以下格式:

application-{profile}.propertiesapplication-{profile}.yml

然后在 application.propertiesapplication.yml 中激活Profile使用机制:

如:

spring.profiles.active=dev

这里主要学习的是注解方式。

在 Spring 中存在两个 参数可以提供给配置,以修改启动 Profile 机制, 一个是spring.profiles.active , 另一个是 spring profiles.default 。在这两个属性都没有配置的情况下 , Spring 将不会启 动 Profile 机制,这就意味着被@Profile 标注的 Bean 将不会被 Spring 装配到 IoC 容器中 。 Spring是先判定是否存在spring.profiles.active配置后再去查找 spring profiles.default 配置的,所以spring.profiles.active的优先级要大于 spring profiles.default

在Java 启动项目时,如果不在配置文件激活也可以使用以下配置也可以启动Profile 机制:

JAVA_OPTS="-Dspring.profiles.active=dev"

如果在全局配置文件和profile配置文件有相同配置,根据Spring Boot规则,如果配置了profile激活,那么application- {profile} . properties 文件去代替原来默认的 app lication.properties 文件,相同属性会覆盖,不同属性则合并。

比如开发环境,对日志打印使用较多,但生产环境较少。

八、XML引入Bean

SpringBoot 建议使用注解和扫描配置Bean,但同样支持XML配置Bean。

在SpringBoot 中使用XML 对Bean 进行配置,需要使用@ImportResource注解引入对应的 XML 文件,用以价值Bean。 有些框架(如Dubbo等)是基于Spring XML 方式开发,这时就需要引入XML 方式来实现配置。

比如有个想加载的Bean,即使是个普通的POJO也行,然后建个spring-other.xml文件,接着配置Bean。

在java 配置文件中直接载入:

package com.smallrose.web.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;

@Configuration
@ComponentScan("com.smallrose.web.app.*")
@ImportResource(value= {"classpath:spring-other.xml"})
public class AppConfig {

 
}

九、Spring EL

为了装配Bean 更加灵活,Spring 提供了表达式语言 Spring EL 。主要作用如下:

(1)读取属性文件

如:

@Value("${database.driverName}")
String driver ;

@Value 中的${ } 代表占位符,它会去读上下文的属性值装配到属性中。

更多的属性与JavaBean 绑定参考 YAML 语法

(2)调用方法

如记录一个Bean 初始化时间:

@Value("#{T(System).currentTimeMillis()}")
private Long initTime = null ;

这里采用 #{ }代表启用 Spring 表达式,它将具有运算的功能 ; T(...)代表的是引入类;

System是java.lang.*包的类,这是Java 默认加载的包,因此可以不写全限定名,如果是其他的包,则需要写出全限定名才能引用类;currentTimeMillis 是它的静态( static )方法,也就是我们调用一次System.currentTimeMillis() 方法来为这个属性赋值。

(3)给属性直接赋值。

如:

@Value("#{'使用spring EL 赋值字符串'}") //字符串赋值
private String str = null;

@Value("#{9.3E3}") //科学计数法赋值
private double d ;

@Value("#{3.14}") // 浮点数赋值
private float pi ;

还可以获取其他Spring Bean 的属性来给当前 Bean 属性赋值,如:

@Value("#{beanName.str}")
private String otherBeanProp = null;

注意 ,这里的 beanName 是 Spring IoC 容器 Bean 的名称 。 str 是其属性,代表引用对应的 Bean 的属性给当前属性赋值。如果想把这个属性的字母全部变大写还可以这样:

@Value("#{beanName.str?.toUpperCase()}")
private String otherBeanProp = null ;

注意这里的 Spring EL。这里引用由属性后跟着是一个,这个符号 的含义是判断这个属性是否为空。如果不为空才会去执行 toUppercase 方法,进而把引用到的属性转换为大写,赋予当前属性。

(4)其他运算

可以使用 Spring EL 进行一定的运算。如:

//数学运算
@Value("#{1+2}")
private int addsum ; 

//浮点比较运算
@Value("#{beanName.pi == 3.14f}")
private int piFlag ; 

//字符串比较运算
@Value("#{ beanName.str eq 'Spring Boot' }")
pri.vate boolean strFlag ;

// 字符串连接
@Value("#{beanName.str + ' 连接字符串 '}"
private String strApp = null ;

//#三元运算
@Value("#{beanName.d > 1000 ? '大于' :'小于'}" 
private String resultDesc = null ;

Spring EL 能够支持的运算还有很多,其中等值比较如果是数字型的可 以使用==比较符,如果是字符串型的可以使用 eq 比较符。当然 , Spring EL 的内容远不止这些,只 是其他表达式的使用率没有那么高