前言:现今SpringBoot、SpringCloud技术非常火热,作为Spring之上的框架,他们大量使用到了Spring的一些底层注解、原理,比如@Conditional、@Import、@EnableXXX等。如果掌握这些底层原理、注解,那么我们对这些高层框架就能做到高度定制,使用的游刃有余

本篇主要内容:spring IOC 容器的组件添加、组件赋值、组件注入及生命周期

一、组件注册

1、@Configuration&@Bean给容器中注册组件:

   过去使用xml配置文件

	<bean id="person" class="com.atguigu.bean.Person"  scope="prototype" >
<property name="age" value="${}"></property>
<property name="name" value="zhangsan"></property>
</bean>

   现在使用@Configuration+@Bean注解

//配置类==配置文件
@Configuration //告诉Spring这是一个配置类
public class MainConfig {
//给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id
@Bean("person")
public Person person01(){
return new Person("lisi", 20);
}

  创建spring容器及获取容器中的bean: 

      //使用配置文件注册
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Person bean = (Person) applicationContext.getBean("person");
System.out.println(bean);
//使用注解
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
Person bean = applicationContext.getBean(Person.class);
System.out.println(bean);

2、组件注册-@ComponentScan-自动扫描组件&指定扫描规则:

    使用xml配置文件进行自动包扫描添加组件

        <!-- 包扫描、只要标注了@Controller、@Service、@Repository,@Component -->
<context:component-scan base-package="com.atguigu" use-default-filters="false"></context:component-scan>

    使用@ComponentScan注解

@Configuration //前提这个类必须是容器中的组件
@ComponentScans(
value = {
@ComponentScan(value="com.atguigu",includeFilters = {
@Filter(type=FilterType.ANNOTATION,classes={Controller.class}),
@Filter(type=FilterType.ASSIGNABLE_TYPE,classes={BookService.class}),
@Filter(type=FilterType.CUSTOM,classes={MyTypeFilter.class})
},useDefaultFilters = false)
}
)
//@ComponentScan value:指定要扫描的包
//excludeFilters = Filter[] :指定扫描的时候按照什么规则排除那些组件
//includeFilters = Filter[] :指定扫描的时候只需要包含哪些组件
//FilterType.ANNOTATION:按照注解
//FilterType.ASSIGNABLE_TYPE:按照给定的类型;
//FilterType.ASPECTJ:使用ASPECTJ表达式
//FilterType.REGEX:使用正则指定
//FilterType.CUSTOM:使用自定义规则
public class MainConfig {
@Bean("person")
public Person person01(){
return new Person("lisi", 20);
} }

  自定义TypeFilter指定过滤规则:

public class MyTypeFilter implements TypeFilter {

    /**
* metadataReader:读取到的当前正在扫描的类的信息
* metadataReaderFactory:可以获取到其他任何类信息的
*/
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
// TODO Auto-generated method stub
//获取当前类注解的信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
//获取当前正在扫描的类的类信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();
//获取当前类资源(类的路径)
Resource resource = metadataReader.getResource(); String className = classMetadata.getClassName();
System.out.println("--->"+className);
if(className.contains("er")){
return true;
}
return false;
}

3、@Scope-设置组件作用域 、@Lazy-bean懒加载:

    /**
*
* @Scope:调整作用域
* prototype:多实例的:ioc容器启动并不会去调用方法创建对象放在容器中。
* 每次获取的时候才会调用方法创建对象;
* singleton:单实例的(默认值):ioc容器启动会调用方法创建对象放到ioc容器中。
* 以后每次获取就是直接从容器(map.get())中拿,
* request:同一次请求创建一个实例
* session:同一个session创建一个实例
*
* 懒加载:
* 单实例bean:默认在容器启动的时候创建对象;
* 懒加载:容器启动不创建对象。第一次使用(获取)Bean创建对象,并初始化;
*
*/
@Lazy
@Scope("prototype")
@Bean("person")
public Person person(){
System.out.println("给容器中添加Person....");
return new Person("张三", 25);
}

4、@Conditional-按照条件注册bean

/**
* @Conditional({Condition}) : 按照一定的条件进行判断,满足条件给容器中注册bean
*
* 如果系统是windows,给容器中注册("bill")
* 如果是linux系统,给容器中注册("linus")
*/
  @Bean("bill")
@Conditional(WindowsCondition.class)
public Person person01(){
return new Person("Bill Gates",62);
} @Conditional(LinuxCondition.class)
@Bean("linus")
public Person person02(){
return new Person("linus", 48);
}

//判断是否linux系统
public class LinuxCondition implements Condition { /**
* ConditionContext:判断条件能使用的上下文(环境)
* AnnotatedTypeMetadata:注释信息
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// TODO是否linux系统
//1、能获取到ioc使用的beanfactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//2、获取类加载器
ClassLoader classLoader = context.getClassLoader();
//3、获取当前环境信息
Environment environment = context.getEnvironment();
//4、获取到bean定义的注册类
BeanDefinitionRegistry registry = context.getRegistry(); String property = environment.getProperty("os.name"); //可以判断容器中的bean注册情况,也可以给容器中注册bean
boolean definition = registry.containsBeanDefinition("person");
if(property.contains("linux")){
return true;
} return false;
} }

//判断是否windows系统
public class WindowsCondition implements Condition { @Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String property = environment.getProperty("os.name");
if(property.contains("Windows")){
return true;
}
return false;
} }
//注:标注在类上。满足当前条件,这个类中配置的所有bean注册才能生效;

5、@Import-给容器中快速导入一个组件

/**
* {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
* or regular component classes to import.
*/
@Import({Color.class,Red.class,MyImportSelector.class,MyImportBeanDefinitionRegistrar.class})
//@Import导入组件,id默认是组件的全类名
public class MainConfig2 { } //自定义逻辑返回需要导入的组件
public class MyImportSelector implements ImportSelector { //返回值,就是到导入到容器中的组件全类名
//AnnotationMetadata:当前标注@Import注解的类的所有注解信息
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// TODO Auto-generated method stub
//importingClassMetadata
//方法不要返回null值
return new String[]{"com.atguigu.bean.Blue","com.atguigu.bean.Yellow"};
} }
//使用ImportBeanDefinitionRegistrar
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { /**
* AnnotationMetadata:当前类的注解信息
* BeanDefinitionRegistry:BeanDefinition注册类;
* 把所有需要添加到容器中的bean;调用
* BeanDefinitionRegistry.registerBeanDefinition手工注册进来
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { boolean definition = registry.containsBeanDefinition("com.atguigu.bean.Red");
boolean definition2 = registry.containsBeanDefinition("com.atguigu.bean.Blue");
if(definition && definition2){
//指定Bean定义信息;(Bean的类型,Bean。。。)
RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
//注册一个Bean,指定bean名
registry.registerBeanDefinition("rainBow", beanDefinition);
}
}
}

6、使用FactoryBean注册组件

@Bean
public ColorFactoryBean colorFactoryBean(){
return new ColorFactoryBean();
}

//创建一个Spring定义的FactoryBean
public class ColorFactoryBean implements FactoryBean<Color> { //返回一个Color对象,这个对象会添加到容器中
@Override
public Color getObject() throws Exception {
// TODO Auto-generated method stub
System.out.println("ColorFactoryBean...getObject...");
return new Color();
} @Override
public Class<?> getObjectType() {
// TODO Auto-generated method stub
return Color.class;
} //是单例?
//true:这个bean是单实例,在容器中保存一份
//false:多实例,每次获取都会创建一个新的bean;
@Override
public boolean isSingleton() {
// TODO Auto-generated method stub
return false;
} }

   

二、生命周期

 bean的生命周期:
bean创建---初始化----销毁的过程
容器管理bean的生命周期:
    我们可以自定义初始化和销毁方法;容器在bean进行到当前生命周期的时候来调用我们自定义的初始化和销毁方法 创建(对象创建)
单实例:在容器启动的时候创建对象
多实例:在每次获取的时候创建对象

1、@Bean指定初始化和销毁方法  

@Configuration
public class MainConfigOfLifeCycle { @Bean(initMethod="init",destroyMethod="detory")
public Car car(){
return new Car();
}
}
public class Car { public Car(){
System.out.println("car constructor...");
} public void init(){
System.out.println("car ... init...");
} public void detory(){
System.out.println("car ... detory...");
} }

2、InitializingBean和DisposableBean

@Component
public class Cat implements InitializingBean,DisposableBean { public Cat(){
System.out.println("cat constructor...");
} @Override
public void destroy() throws Exception {
// TODO Auto-generated method stub
System.out.println("cat...destroy...");
} @Override
public void afterPropertiesSet() throws Exception {
// TODO Auto-generated method stub
System.out.println("cat...afterPropertiesSet...");
} }

3、@PostConstruct&@PreDestroy 

@Component
public class Dog implements ApplicationContextAware {
public Dog(){
System.out.println("dog constructor...");
} //对象创建并赋值之后调用
@PostConstruct
public void init(){
System.out.println("Dog....@PostConstruct...");
} //容器移除对象之前
@PreDestroy
public void detory(){
System.out.println("Dog....@PreDestroy...");
} }

4、BeanPostProcessor-后置处理器

**
* 后置处理器:初始化前后进行处理工作
* 将后置处理器加入到容器中
* @author lfy
*/
@Component
public class MyBeanPostProcessor implements BeanPostProcessor { @Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// TODO Auto-generated method stub
System.out.println("postProcessBeforeInitialization..."+beanName+"=>"+bean);
return bean;
} @Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// TODO Auto-generated method stub
System.out.println("postProcessAfterInitialization..."+beanName+"=>"+bean);
return bean;
} }

BeanPostProcessor原理分析:

  在postProcessBeforeInitialization打个断点,然后debug运行,方法栈如下:

    

  由上而下分析方法栈中的主要方法,先来看applyBeanPostProcessorsBeforeInitialization方法:

    public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
throws BeansException { Object result = existingBean;
//遍历所有的BeanPostProcessor 并执postProcessBeforeInitialization方法,如果返回null,则停止遍历直接返回;
for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
result = beanProcessor.postProcessBeforeInitialization(result, beanName);
if (result == null) {
return result;
}
}
return result;
}

  接着往下看initializeBean方法中的部分源码:

protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
     ...
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
        //前一步
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
} try {
       //这里调用了所有初始化方法
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
} if (mbd == null || !mbd.isSynthetic()) {
        //在上面执行了所有初始化方法后 最后再遍历执行所有BeabPostProcessors的postProcessAfterInitialization方法
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}

  再往下看doCreateBean方法中的部分代码:

// Initialize the bean instance.
Object exposedObject = bean;
try {
        //给bean进行属性赋值(getter)
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}

大概的过程:                                                                                                      

* populateBean(beanName, mbd, instanceWrapper);在初始化开始之前 给bean进行属性赋值
* initializeBean
* {
* applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
* invokeInitMethods(beanName, wrappedBean, mbd);执行自定义初始化
* applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);

}

定制bean的生命周期: 

* 1)、指定初始化和销毁方法;
* 通过@Bean指定init-method和destroy-method;
* 2)、通过让Bean实现InitializingBean(定义初始化逻辑),
* DisposableBean(定义销毁逻辑);
* 3)、可以使用JSR250;
* @PostConstruct:在bean创建完成并且属性赋值完成;来执行初始化方法
* @PreDestroy:在容器销毁bean之前通知我们进行清理工作
* 4)、BeanPostProcessor【interface】:bean的后置处理器;
* 在每个bean初始化前后进行一些处理工作;
* postProcessBeforeInitialization:在初始化之前工作(其他初始化方法调用之前调用)
* postProcessAfterInitialization:在初始化之后工作(其他初始化方法调用结束以后调用)
 

三、属性赋值

1、@Value赋值

//使用@Value赋值;
//1、基本数值
//2、可以写SpEL; #{}
//3、可以写${};取出配置文件【properties】中的值(在运行环境变量里面的值) @Value("张三")
private String name;
@Value("#{20-2}")
private Integer age; @Value("${person.nickName}")
private String nickName;

2、@PropertySource加载外部配置文件

//使用@PropertySource读取外部配置文件中的k/v保存到运行的环境变量中;加载完外部的配置文件以后使用${}取出配置文件的值
@PropertySource(value={"classpath:/person.properties"})
@Configuration
public class MainConfigOfPropertyValues { @Bean
public Person person(){
return new Person();
} }

四、自动装配

  Spring利用依赖注入(DI),完成对IOC容器中中各个组件的依赖关系赋值

1、@Autowired&@Qualifier&@Primary

  @Autowired:自动注入:默认优先按照类型去容器中找对应的组件:applicationContext.getBean(BookDao.class);找到就赋值,如果找到多个相同类型的组件,再将属性的名称作为组件的id去容器中查找applicationContext.getBean("bookDao")

  @Qualifier("bookDao"):使用@Qualifier指定需要装配的组件的id,而不是使用属性名,自动装配默认一定要将属性赋值好,没有就会报错, 使用@Autowired(required=false);

  @Primary:让Spring进行自动装配的时候,默认使用首选的bean也可以继续使用,@Qualifier指定需要装配的bean的名字     

   @Primary
@Bean("bookDao2")
public BookDao bookDao(){
BookDao bookDao = new BookDao();
bookDao.setLable("2");
return bookDao;
}

2、@Resource&@Inject

  Spring还支持使用@Resource(JSR250)和@Inject(JSR330)[java规范的注解]

    @Resource:可以和@Autowired一样实现自动装配功能;默认是按照组件名称进行装配的,没有能支持@Primary功能没有支持@Autowired(reqiured=false);

  @Inject:需要导入javax.inject的包,和Autowired的功能一样。没有required=false的功能;

3、方法、构造器位置的自动装配 

@Autowired:构造器,参数,方法,属性;都是从容器中获取参数组件的值
* 1)、[标注在方法位置]:@Bean+方法参数;参数从容器中获取;默认不写@Autowired效果是一样的;都能自动装配
* 2)、[标在构造器上]:如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略,参数位置的组件还是可以自动从容器中获取
* 3)、放在参数位置:

/**
* @Bean标注的方法创建对象的时候,方法参数的值从容器中获取
* @param car
* @return
*/
  @Bean
public Color color(Car car){
Color color = new Color();
color.setCar(car);
return color;
}

4、Aware注入Spring底层组件&原理

  自定义组件想要使用Spring容器底层的一些组件(ApplicationContext,BeanFactory,xxx),自定义组件实现xxxAware;在创建对象的时候,会调用接口规定的方法注入相关组件;Aware接口把Spring底层一些组件注入到自定义的Bean中,xxxAware:功能底层使用了xxxProcessor,ApplicationContextAware==》ApplicationContextAwareProcessor;(后续分析源码,会细讲)

5、@Profile根据环境注册bean 

Profile:Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能: 

1)、加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中。默认是default环境
  2)、写在配置类上,只有是指定的环境的时候,整个配置类里面的所有配置才能开始生效
  3)、没有标注环境标识的bean在,任何环境下都是加载的;

    @Profile("test")
@Bean("testDataSource")
public DataSource dataSourceTest(@Value("${db.password}")String pwd) throws Exception{
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setDriverClass(driverClass);
return dataSource;
} @Profile("dev")
@Bean("devDataSource")
public DataSource dataSourceDev(@Value("${db.password}")String pwd) throws Exception{
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssm_crud");
dataSource.setDriverClass(driverClass);
return dataSource;
} @Profile("prod")
@Bean("prodDataSource")
public DataSource dataSourceProd(@Value("${db.password}")String pwd) throws Exception{
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(pwd);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/scw_0515"); dataSource.setDriverClass(driverClass);
return dataSource;
}

激活环境方式:

  1)使用命令行动态参数: 在虚拟机参数位置加载 -Dspring.profiles.active=test

  2)代码的方式激活某种环境:   

AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext();
//1、创建一个applicationContext
//2、设置需要激活的环境
applicationContext.getEnvironment().setActiveProfiles("dev");
//3、注册主配置类
applicationContext.register(MainConfigOfProfile.class);
//4、启动刷新容器
applicationContext.refresh();

  

     

 

最新文章

  1. Centos6---Fail2ban
  2. Xamarin基础命名空间Microsoft.SqlServer.Server
  3. 数据库之mysql存储程序
  4. nodejs+express+jade给我baby做个小相册
  5. 关于body/documentElement ----&gt;clientHeight, offsetHeight, scrollHeight
  6. Request.ServerVariables完整参考
  7. POJ 3261 可重叠的 k 次最长重复子串【后缀数组】
  8. 64位win7旗舰版搭建apache+php+mysql开发环境[转]
  9. c++中string类型可以直接进行比较
  10. arcgis 瓦片图加载规则(转载)
  11. scss 初学笔记 二 混合宏
  12. [Bash]LeetCode192. 统计词频 | Word Frequency
  13. 20164301 Exp4 恶意代码分析
  14. 百度前端代码规范:CSS
  15. 4.93Python数据类型之(8)集合
  16. JAVA核心:内存、比较和Final
  17. Grails 第二课
  18. eslint 代码检测工具
  19. bulk
  20. 设计模式之命令模式(Command Pattern)

热门文章

  1. 4448: [Scoi2015]情报传递|主席树|离线操作
  2. 深度学习笔记之使用Faster-Rcnn进行目标检测 (原理篇)
  3. Android经常使用设计模式(二)
  4. css3最新版中文参考手册在线浏览
  5. 注入式开发(二):.NET 匿名函数
  6. C++ 函数部分(1)
  7. POJ 3104 Drying (二分+精度)
  8. Adding Security to an Existing Website
  9. FOUNDATION OF ASYNCHRONOUS PROGRAMMING
  10. Jackson 框架的高阶应用