Spring系列(二):Spring IoC应用
一、Spring IoC的核心概念
IoC(Inversion of Control 控制反转),详细的概念见Spring系列(一):Spring核心概念
二、Spring IoC的应用
1、定义Bean的信息
1.1 基于xml的形式定义Bean的信息
① 新建一个Bean:
package com.toby.ioc.component; /** * @desc: * @author: toby * @date: 2019/7/13 1:49 */ public class TobyBean{ public TobyBean(){ System.out.println("TobyBean Constructor"); } }
② 在resources下面新建一个spring.xml
xml配置如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="tobyBean" class="com.toby.ioc.component.TobyBean"/> </beans>
③ 写一个测试类进行测试
package com.toby.ioc.xml; import com.toby.ioc.component.TobyBean; import org.junit.Before; import org.junit.Test; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @desc: 基于xml * @author: toby * @date: 2019/8/6 17:36 */ public class XmlTest { private ClassPathXmlApplicationContext context; @Before public void before(){ context = new ClassPathXmlApplicationContext("spring.xml"); } @Test public void test(){ TobyBean tobyBean = context.getBean(TobyBean.class); System.out.println(tobyBean); } }
总结:由于现在基本基于spring boot 约定大于配置,而且大量的xml配置也不易于维护,所以这里就简单介绍下基于xml的原理:首先读取资源配置文件,然后解析成BeanDefinition,最后利用反射进行相应的实例化操作。我们接下来重点讲解基于注解的方式
1.2 基于读取配置类的形式定义Bean信息
① 同上面基于xml一样,需要一个Bean
② 新建一个配置类定义相应的Bean信息
package com.toby.ioc.config; import com.toby.ioc.component.TobyBean; import org.springframework.context.annotation.*; /** * @desc: ioc config 类 * @author: toby * @date: 2019/7/13 1:10 */ @Configuration public class IocConfig { @Bean public TobyBean tobyBean(){ return new TobyBean(); } }
③ 写一个测试类进行测试
package com.toby.ioc.configuration; import com.toby.ioc.config.IocConfig; import org.junit.Before; import org.junit.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; /** * @desc: 基于配置类 * @author: toby * @date: 2019/8/6 17:59 */ public class ConfigurationTest { private AnnotationConfigApplicationContext context; @Before public void before(){ context = new AnnotationConfigApplicationContext(IocConfig.class); } @Test public void test(){ System.out.println(context.getBean("tobyBean")); } }
2、Spring IoC常用注解使用
2.1 @Configuration 相当于 xml配置的 <beans/>
2.2 @Bean 相当于 xml配置的 <bean/>
默认(单实例 延迟加载)
package com.toby.ioc.config; import com.toby.ioc.component.TobyBean; import org.springframework.context.annotation.*; /** * @desc: ioc config 类 * @author: toby * @date: 2019/7/13 1:10 */ @Configuration public class IocConfig { @Bean public TobyBean tobyBean(){ return new TobyBean(); } }
配置Bean的作用域
① 在不指定@Scope的情况下,所有的bean都是单实例的bean,而且是饿汉加载(容器启动实例就创建好了)
② @Scope为prototype表示为多实例的,而且还是懒汉模式加载(IOC容器启动的时候,并不会创建对象,而是在每次使用的时候才会创建)注意:当指定多例的时候是无法解决循环依赖的后续源码会分析
@Configuration public class IocConfig { @Bean @Scope("prototype") public TobyBean tobyBean(){ return new TobyBean(); } }
如何测试是否多实例:
public class IocMain { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(IocConfig.class); TobyBean tobyBean1 = context.getBean(TobyBean.class); TobyBean tobyBean2 = context.getBean(TobyBean.class); //单例返回true 多例返回false System.out.println(tobyBean1 == tobyBean2); } }
③ @Scope指定的作用域取值:singleton 单实例的(默认),prototype 多实例的,request 同一次请求,session 同一个会话级别
Bean的懒加载@Lazy
Bean的懒加载@Lazy(主要针对单实例的bean在容器启动的时候,不创建对象,而在第一次使用的时候才会创建该对象,多实例bean没有懒加载一说)
@Configuration public class IocConfig { @Bean @Lazy public TobyBean tobyBean(){ return new TobyBean(); } }
2.3 @CompentScan 包扫描(重点)
在配置类上写@CompentScan注解来进行包扫描
① 常规用法:这样在basePackages包下面具有@Controller @Service @Repository @Component注解的组件都会被加载到spring容器中
@Configuration @ComponentScan(basePackages = {"com.toby.ioc"}) public class IocConfig { }
② 排除用法:excludeFilters(排除@Controller注解和TobyService)
@Configuration @ComponentScan(basePackages = {"com.toby.ioc"},excludeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class}), @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,value = {TobyService.class}) }) public class IocConfig { }
③ 包含用法:includeFilters,注意:若使用包含,需要把useDefaultFilters属性设置为false(true表示扫描全部的),后续源码解析会说到这个原因
@Configuration @ComponentScan(basePackages = {"com.toby.ioc"},includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class, Service.class}) },useDefaultFilters = false) public class IocConfig { }
④ 自定义Filter用法:
自定义一个TobyTypeFilter实现TypeFilter
public class TobyTypeFilter implements TypeFilter { @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { //获取当前类的class的源信息 ClassMetadata classMetadata = metadataReader.getClassMetadata(); //类名称中包含Dao就可以被扫描到 if(classMetadata.getClassName().contains("Dao")) { return true; } return false; } }
配置类:
@Configuration @ComponentScan(basePackages = {"com.toby.ioc"},includeFilters = { @ComponentScan.Filter(type = FilterType.CUSTOM,value = TobyTypeFilter.class) },useDefaultFilters = false) public class IocConfig { }
2.4 @Conditional 条件注解(spring boot中大量用到)
① 新建2个Bean TobyA和TobyB 如下:
public class TobyA { public TobyA() { System.out.println("TobyA Constructor"); } }
public class TobyB { public TobyB() { System.out.println("TobyB Constructor"); } }
② 新建一个TobyCondition实现Condition接口
public class TobyCondition implements Condition { private static final String TOBY_A_BEAN_NAME = "tobyA"; @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { //判断容器中是否有TobyA组件 if(context.getBeanFactory().containsBean(TOBY_A_BEAN_NAME)){ return true; } return false; } }
③ 配置类 只有当容器中有TobyA的时候才实例化TobyB
@Configuration public class IocConfig { @Bean public TobyA tobyA(){ return new TobyA(); } @Bean @Conditional(TobyCondition.class) public TobyB tobyB(){ return new TobyB(); } }
2.5 往IOC容器中添加组件的方式
① 通过@ComponentScan包扫描 + @Controller、@Service、@Repository、@Component 针对我们自己写的组件可以通过该方式来加载到容器中
② 通过@Bean的方式来导入组件(适用于导入第三方组件)
③ 通过@Import
Ⅰ 通过@Import直接导入组件(导入组件的id为全限定类名)
配置类:
@Configuration @Import({TobyBean.class}) public class IocConfig { }
Ⅱ 通过@Import的ImportSelector类实现组件的导入(导入组件的id为全限定类名),自定义的TobyImportSelector需要实现ImportSelector接口。
public class TobyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { //返回全限定类名的数组 return new String[]{"com.toby.ioc.component.TobyBean"}; } }
配置类:
@Configuration @Import({TobyImportSelector.class}) public class IocConfig { }
Ⅲ 通过@Import的ImportBeanDefinitionRegistrar导入组件 (可以指定bean的名称),自定义TobyImportBeanDefinitionRegistrar实现ImportBeanDefinitionRegistrar。
public class TobyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { //创建一个bean定义对象 RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(TobyBean.class); //把bean定义对象导入到容器中 registry.registerBeanDefinition("tobyBean",rootBeanDefinition); } }
配置类:
@Configuration @Import({TobyImportBeanDefinitionRegistrar.class}) public class IocConfig { }
④ 通过实现FactoryBean接口来实现注册组件
创建一个FactoryBean,注意要获取FactoryBean本身需要在beanName前面加上&
@Component public class TobyBeanFactoryBean implements FactoryBean<TobyBean> { @Override public TobyBean getObject() throws Exception { return new TobyBean(); } @Override public Class<?> getObjectType() { return TobyBean.class; } @Override public boolean isSingleton() { return false; } }
单元测试:
public class FactoryBeanTest { private AnnotationConfigApplicationContext context; @Before public void before(){ context = new AnnotationConfigApplicationContext(IocConfig.class); } @Test public void test(){ //获取TobyBean System.out.println(context.getBean("tobyBeanFactoryBean")); //如何获取TobyBeanFactoryBean System.out.println(context.getBean("&tobyBeanFactoryBean")); } }
2.6 Bean的生命周期
由容器管理Bean的生命周期,我们可以指定bean的初始化方法和bean的销毁方法
① 通过@Bean的initMethod和destroyMethod属性
新建一个LifeCycleBean1 Bean:
package com.toby.ioc.beanlifecycle; /** * @desc: bean生命周期1 * @author: toby * @date: 2019/7/13 1:26 */ public class LifeCycleBean1 { public LifeCycleBean1(){ System.out.println("LifeCycleBean1 Constructor"); } public void init(){ System.out.println("LifeCycleBean1 Init"); } public void destroy(){ System.out.println("LifeCycleBean1 Destroy"); } }
配置类:
@Configuration public class IocConfig { @Bean(initMethod = "init",destroyMethod = "destroy") public LifeCycleBean1 lifeCycleBean1(){ return new LifeCycleBean1(); } }
②通过实现InitializingBean, DisposableBean2个接口
新建一个LifeCycleBean2
package com.toby.ioc.beanlifecycle; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.stereotype.Component; /** * @desc: bean生命周期2 通过实现2个接口 * @author: toby * @date: 2019/7/13 1:30 */ @Component public class LifeCycleBean2 implements InitializingBean, DisposableBean { public LifeCycleBean2(){ System.out.println("LifeCycleBean2 Constructor"); } @Override public void destroy() throws Exception { System.out.println("LifeCycleBean2 destroy"); } @Override public void afterPropertiesSet() throws Exception { System.out.println("LifeCycleBean2 afterPropertiesSet"); } }
③ 通过JSR250规范提供的注解@PostConstruct和@PreDestroy标注的方法
新建一个LifeCycleBean3
package com.toby.ioc.beanlifecycle; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; /** * @desc: bean生命周期3 通过2个注解 * @author: toby * @date: 2019/7/13 1:30 */ @Component public class LifeCycleBean3{ public LifeCycleBean3(){ System.out.println("LifeCycleBean3 Constructor"); } @PostConstruct public void init(){ System.out.println("LifeCycleBean3 init"); } @PreDestroy public void destroy(){ System.out.println("LifeCycleBean3 destroy"); } }
2.7 后置处理器(很重要,后面源码解析会讲)
① BeanPostProcessor:也称为Bean后置处理器,它是Spring中定义的接口,在Spring容器的创建过程中(具体为Bean初始化前后)会回调BeanPostProcessor中定义的两个方法。分别是postProcessBeforeInitialization(初始化之前)和postProcessAfterInitialization(初始化之后)
自定义TobyBeanPostProcessor后置处理器:
package com.toby.ioc.processor; import com.toby.ioc.component.TobyBean; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.stereotype.Component; /** * @desc: bean的后置处理器 * @author: toby * @date: 2019/7/13 2:08 */ @Component public class TobyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if(bean instanceof TobyBean){ System.out.println("马上开始初始化TobyBean了,注意下"); } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if(bean instanceof TobyBean){ System.out.println("初始化完成TobyBean了,注意下"); } return bean; } }
② BeanFactoryPostProcessor:Bean工厂的后置处理器,触发时机bean定义注册之后bean实例化之前
自定义TobyBeanFactoryPostProcessor Bean工厂的后置处理器:
package com.toby.ioc.processor; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.stereotype.Component; /** * @desc: bean工厂的后置处理器 触发时机 bean定义注册之后 bean实例化之前 * @author: toby * @date: 2019/7/21 23:04 */ @Component public class TobyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { System.out.println("调用了TobyBeanFactoryPostProcessor的postProcessBeanFactory方法"); for(String beanName : beanFactory.getBeanDefinitionNames()){ if("tobyBean".equals(beanName)){ BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); beanDefinition.setLazyInit(true); } } } }
③ BeanDefinitionRegistryPostProcessor:Bean定义的后置处理器,它继承了BeanFactoryPostProcessor,触发时机,在bean的定义注册之前
自定义TobyBeanDefinitionRegistryPostProcessor Bean定义的后置处理器
package com.toby.ioc.processor; import com.toby.ioc.component.TobyBean; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.stereotype.Component; /** * @desc: bean定义的后置处理器 * @author: toby * @date: 2019/7/21 23:11 */ @Component public class TobyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { System.out.println("调用TobyBeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法"); System.out.println("bean定义的数据量:"+registry.getBeanDefinitionCount()); RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(TobyBean.class); registry.registerBeanDefinition("tobyBean",rootBeanDefinition); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { System.out.println("调用TobyBeanDefinitionRegistryPostProcessor的postProcessBeanFactory方法"); System.out.println(beanFactory.getBeanDefinitionCount()); } }
2.8 Aware接口
Spring提供了大量的Aware接口,使得我们可以使用Spring的一些底层提供的容器,资源比如获取ApplicationContext就可以实现ApplicationContextAware接口,获取BeanFactory就可以实现BeanFactoryAware,这些Aware接口的回调是在Bean初始化 initializeBean() 方法中进行回调的
比如我们要使用Spring底层的ApplicationContext,则需要实现ApplicationContextAware如下:
package com.toby.ioc.aware; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; /** * @desc: 应用中需要获取spring的上下文 * @author: toby * @date: 2019/7/13 1:15 */ @Component public class TobyApplicationContextAware implements ApplicationContextAware { /** * spring上下文 */ private ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { System.out.println("应用程序获取到了spring 容器"); this.applicationContext = applicationContext; } }
2.9 Lifecycle接口
每个对象都有自己生命周期的需求,主要方法:isAutoStartup()返回true时,Spring容器启动时会去执行start()方法。isRunning()返回true的时候,容器销毁时会调用stop()方法。比如eruaka启动的入口就是通过实现SmartLifecycle接口来实现
自定义TobyLifecycle实现SmartLifecycle接口:
package com.toby.ioc.lifecycle; import org.springframework.context.SmartLifecycle; import org.springframework.stereotype.Component; /** * @desc: 每个对象都有自己生命周期的需求,比如eruaka启动的入口就是用这个实现的 * @author: toby * @date: 2019/7/13 2:00 */ @Component public class TobyLifecycle implements SmartLifecycle { @Override public boolean isAutoStartup() { return true; } @Override public void stop(Runnable callback) { } @Override public void start() { System.out.println("TobyLifecycle start"); } @Override public void stop() { } @Override public boolean isRunning() { return false; } @Override public int getPhase() { return 0; } }
2.10 自动装配
① @Autowired 默认情况下:首先是按照类型进行装配,若在IOC容器中发现了多个相同类型的组件,那么就按照属性名称来进行装配。
② @Autowired 假设我们需要指定特定的组件来进行装配,我们可以通过使用@Qualifier("tobyDao")来指定装配的组件或者在配置类上的@Bean加上@Primary注解
@Autowired + @Qualifier:
@Service public class TobyService { @Autowired @Qualifier("tobyDao") private TobyDao tobyDao; public TobyDao getTobyDao(){ return this.tobyDao; } }
@Bean + @Primary:
@Configuration public class IocConfig { @Bean @Primary public TobyDao tobyDao(){ return new TobyDao(); } @Bean public TobyDao tobyDao2(){ return new TobyDao(); } }
③ 假设我们指定Autowire.BY_TYPE,这时候容器出现2个及以上,那么在装配的时候就会抛出异常
@Configuration public class PrincipleConfig { @Bean public PrincipleBean principleBean(){ return new PrincipleBean(); } @Bean(autowire = Autowire.BY_TYPE) public PrincipleAspect principleAspect(){ return new PrincipleAspect(); } @Bean public PrincipleLog principleLog(){ return new PrincipleLog(); } @Bean public PrincipleLog principleLog2(){ return new PrincipleLog(); } }
④ @Resource(JSR250规范)功能和@AutoWired的功能差不多一样,但是不支持@Primary和@Qualifier的支持
⑤ @Inject(JSR330规范)需要导入jar包依赖功能和支持@Primary功能,但是没有Require=false的功能
总结:通过上面的示例,对Spring IoC常用注解以及接口有一定了解,Spring系列完整代码在码云:spring系列,接下来将进入:Spring系列(三):Spring IoC源码解析(干货多多)