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源碼解析干貨多多


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM