一、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源碼解析(干貨多多)