Spring之A:Spring Bean動態註冊、刪除

IoC容器的初始化包括BeanDefinition的Resource定位載入註冊這三個基本的過程。java

1、Resource定位。BeanDefinition的資源定位有resourceLoader經過統一的Resource接口來完成,這個Resource對各類形式的BeanDefinition的使用提供了統一接口。對於這些BeanDefinition的存在形式,相信不陌生,如:spring

FileSystemResource、ClassPathResource。這個過程相似於容器尋找數據的過程,就像用水桶裝水要把水找到同樣。apache

2、第二個關鍵的部分是BeanDefinition的載入,該載入過程把用戶定義好的Bean表示成IoC容器內部的數據結構,而這個容器內部的數據結構就是BeanDefinition。簡單說,BeanDefinition其實是POJO對象在IoC容器中的抽象,這個BeanDefinition定義了一系列的數據來使得IoC容器可以方便地對POJO對象也就是Spring的Bean進行管理。即BeanDefinition就是Spring的領域對象。數據結構

3、第三個過程是向IoC容器註冊這些BeanDefinition的過程。這個過程是經過調用BeanDefinitionRegistry接口的實現來完成的,這個註冊過程把載入過程當中解析獲得的BeanDefinition向IoC容器進行註冊。能夠看到,在IoC容器內部,是經過使用一個HashMap來持有BeanDefinition數據的。app

總結:框架

此處對於BeanPostProcessor接口的調用應該屬於高級應用了,該思路經常使用來解決擴展或集成Spring框架,其核心的思路能夠分爲如下幾步:ide

   一、自定義實現類路徑掃描類,決定哪些類應該被注入進Spring容器。post

   二、採用Java動態代理來動態實現對於聲明接口類的注入。測試

   三、實現BeanDefinitionRegistryPostProcessor,在Spring初始化初期將須要掃描導入Spring容器的類進行注入。ui

 四、經過代碼動態建立

 代碼動態建立

咱們經過getBean來得到對象,但這些對象都是事先定義好的,咱們有時候要在程序中動態的加入對象.由於若是採用配置文件或者註解,咱們要加入對象的話,還要重啓服務,若是咱們想要避免這一狀況就得采用動態處理bean,包括:動態注入,動態刪除。

本節大綱 :
(1)動態注入bean思路;
(2)動態注入實現代碼;
(3)屢次注入同一個bean的狀況;
(4)動態刪除;

       接下來咱們看下具體的內容:

(1)動態注入bean思路;

       在具體進行代碼實現的時候,咱們要知道,Spring管理bean的對象是BeanFactory,具體的是DefaultListableBeanFactory,在這個類當中有一個注入bean的方法:registerBeanDefinition,在調用registerBeanDefinition方法時,須要BeanDefinition參數,那麼這個參數怎麼獲取呢?Spring提供了BeanDefinitionBuilder能夠構建一個BeanDefinition,那麼咱們的問題就是如何獲取BeanFactory了,這個就很簡單了,只要獲取到ApplicationContext對象便可獲取到BeanFacory了。

(2)動態注入實現代碼;

綜上所述,若是咱們要編寫一個簡單裏的例子的話,那麼分以個幾個步驟進行編碼便可進行動態注入了:

<1>. 獲取ApplicationContext;
<2>. 經過ApplicationContext獲取到BeanFacotory;
<3>. 經過BeanDefinitionBuilder構建BeanDefiniton;
<4>. 調用beanFactory的registerBeanDefinition注入beanDefinition;
<5>. 使用ApplicationContext.getBean獲取bean進行測試;

       很明顯咱們須要先定義個類進行測試,好比TestService代碼以下:

package com.dxz.test;

public class TestService {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void print() {
        System.out.println("動態載入bean,name=" + name);
    }

}

 

注意:這裏沒有使用@Service和配置文件進行注入TestService。

       那麼下面咱們的目標就是動態注入TestService了,根據以上的分析,咱們進行編碼,具體代碼以下:

package com.dxz.test;

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        ApplicationContext ctx = SpringApplication.run(Application.class, args);

        // 獲取BeanFactory
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) ctx
                .getAutowireCapableBeanFactory();

        // 建立bean信息.
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(TestService.class);
        beanDefinitionBuilder.addPropertyValue("name", "張三");

        // 動態註冊bean.
        defaultListableBeanFactory.registerBeanDefinition("testService", beanDefinitionBuilder.getBeanDefinition());

        // 獲取動態註冊的bean.
        TestService testService = ctx.getBean(TestService.class);
        testService.print();
    }
}

 或者

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        ClassPathResource res = new ClassPathResource("beans.xml");

        // 獲取BeanFactory
        DefaultListableBeanFactory factory= new DefaultListableBeanFactory();

        // 建立bean信息.
        XmlBeanDefinitionReader beanDefinitionBuilder = new XmlBeanDefinitionReader(factory);
        beanDefinitionBuilder.loadBeanDefinitions(res);
        //...
    }
}

 

執行代碼咱們會在控制檯看到以下打印信息:

動態載入bean,name=張三

       到這裏,就證實咱們的代碼很成功了。

(3)屢次注入同一個bean的狀況;

       屢次注入同一個bean的,若是beanName不同的話,那麼會產生兩個Bean;若是beanName同樣的話,後面注入的會覆蓋前面的。

第一種狀況:beanName同樣的代碼:

beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(TestService.class);
beanDefinitionBuilder.addPropertyValue("name","李四");
defaultListableBeanFactory.registerBeanDefinition("testService", beanDefinitionBuilder.getBeanDefinition());

       運行看控制檯:

動態載入bean,name=李四

第二種狀況:beanName不同的代碼:

beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(TestService.class);
beanDefinitionBuilder.addPropertyValue("name","李四");
defaultListableBeanFactory.registerBeanDefinition("testService1",beanDefinitionBuilder.getBeanDefinition());

       此時若是沒有更改別的代碼直接運行的話,是會報以下錯誤的:

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.kfit.demo.service.TestService] is defined: expected single matching bean but found 2: testService1,testService

       大致意思就是在getBean的時候,找到了兩個bean,這時候就不知道要獲取哪一個了,因此在獲取的時候,咱們就要指定咱們是要獲取的testService仍是testService1,只須要修改一句代碼:

將代碼:

TestService testService =ctx.getBean(TestService.class);
修改成:
TestService testService =ctx.getBean("testService");

(4)動態刪除;

       相對於動態注入,動態刪除就很簡單了,直接奉上代碼:

//刪除bean.
defaultListableBeanFactory.removeBeanDefinition("testService");

實現BeanDefinitionRegistryPostProcessor,在Spring初始化初期將須要掃描導入Spring容器的類進行注入

"對於Spring框架,現實公司使用的很是普遍,可是因爲業務的複雜程度不一樣,瞭解到不少小夥伴們利用Spring開發僅僅是利用了Spring的IOC,即便是AOP也不多用,可是目前的Spring是一個你們族,造成了一個很大的生態,覆蓋了咱們平時開發的方方面面,拋開特殊的苛刻要求以外,Spring的生態其實已經很全面了,因此在此開個系列來研究下Spring提供給咱們的一些平時不太卻又很實用的內容。"

    上一篇咱們分析了BeanPostProcessor的基本使用,接下來咱們分析下如何使用該類實現動態的接口注入,示例說明:在BeetlSQL框架中,在使用自動掃描注入時,咱們一般只須要配置上要掃描的包路徑,而後在該路徑下聲明對應的Dao接口類,這些接口類都默認繼承BaseMapper接口類,而後咱們在使用這些Dao類的時候,直接根據類型注入(@Autowired)便可使用,這個其實和Mybatis的那一套類似,也和Spring自身的Spring-data框架也相似。這個常常用於框架的開發,那麼我就該部分的實現作相應的解釋,這三個框架具體實現可能有差距,感興趣的小夥伴自行去查看源碼,我會以一個很簡單的例子來說解大概的實現邏輯。

    問題描述:

      繼承Spring框架,實現聲明某個自定義接口(UserMapper),改接口繼承通用接口BaseMapper,(通用接口BaseMapper有默認的實現類),實現經過類型注入UserMapper類,而後經過Spring框架的上下文類(ApplicationContext實現類)的getBean()方法拿到UserMapper類來調用內部提供的方法。

 一、聲明BaseMapper接口類

package com.dxz.test;
public interface BaseMapper {

public void add(String value);

public void remove(String key);
}

public class CustomBaseMapper implements BaseMapper {
    private final Logger logger = Logger.getLogger(this.getClass().getName());
    private List<String> dataList = new CopyOnWriteArrayList<>();

    @Override
    public void add(String value) {
        logger.info("添加數據:" + value);
        dataList.add(value);
    }

    @Override
    public void remove(String key) {
        if (dataList.isEmpty())
            throw new IllegalArgumentException("Can't remove because the list is Empty!");
    }
}

 

接下來是繼承Spring的核心代碼

三、首先咱們要先定義一個掃描某路徑下的類,該類繼承ClassPathBeanDefinitionScanner,自定義掃描類:DefaultClassPathScanner

package com.dxz.test;

import java.io.IOException;
import java.util.Arrays;
import java.util.Set;

import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;

public class DefaultClassPathScanner extends ClassPathBeanDefinitionScanner {

    private final String DEFAULT_MAPPER_SUFFIX = "Mapper";

    public DefaultClassPathScanner(BeanDefinitionRegistry registry) {
        super(registry, false);
    }

    private String mapperManagerFactoryBean;

    /**
     * 掃描包下的類-完成自定義的Bean定義類
     *
     * @param basePackages
     * @return
     */
    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
        // 若是指定的基礎包路徑中不存在任何類對象,則提示
        if (beanDefinitions.isEmpty()) {
            logger.warn("系統沒有在 '" + Arrays.toString(basePackages) + "' 包中找到任何Mapper,請檢查配置");
        } else {
            processBeanDefinitions(beanDefinitions);
        }
        return beanDefinitions;
    }

    /**
     * 註冊過濾器-保證正確的類被掃描注入
     */
    protected void registerFilters() {
        addIncludeFilter(new TypeFilter() {
            @Override
            public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
                    throws IOException {
                String className = metadataReader.getClassMetadata().getClassName();
                // TODO 這裏設置包含條件-此處是個擴展點,能夠根據自定義的類後綴過濾出須要的類
                return className.endsWith(DEFAULT_MAPPER_SUFFIX);
            }
        });
        addExcludeFilter(new TypeFilter() {
            @Override
            public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
                    throws IOException {
                String className = metadataReader.getClassMetadata().getClassName();
                return className.endsWith("package-info");
            }
        });
    }

    /**
     * 重寫父類的判斷是否可以實例化的組件-該方法是在確認是否真的是isCandidateComponent 原方法解釋:
     * 肯定給定的bean定義是否有資格成爲候選人。 默認實現檢查類是否不是接口,也不依賴於封閉類。 以在子類中重寫。
     *
     * @param beanDefinition
     * @return
     */
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        // 原方法這裏是判斷是否爲頂級類和是不是依賴類(即接口會被排除掉-因爲咱們須要將接口加進來,因此須要覆蓋該方法)
        return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
    }

    /**
     * 擴展方法-對掃描到的含有BeetlSqlFactoryBean的Bean描述信息進行遍歷
     *
     * @param beanDefinitions
     */
    void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        GenericBeanDefinition definition;
        for (BeanDefinitionHolder holder : beanDefinitions) {
            definition = (GenericBeanDefinition) holder.getBeanDefinition();
            String mapperClassName = definition.getBeanClassName();
            // 必須在這裏加入泛型限定,要否則在spring下會有循環引用的問題
            definition.getConstructorArgumentValues().addGenericArgumentValue(mapperClassName);
            // 依賴注入
            definition.getPropertyValues().add("mapperInterface", mapperClassName);
            // 根據工廠的名稱建立出默認的BaseMapper實現
            definition.getPropertyValues().add("mapperManagerFactoryBean",
                    new RuntimeBeanReference(this.mapperManagerFactoryBean));
            definition.setBeanClass(BaseMapperFactoryBean.class);
            // 設置Mapper按照接口組裝
            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
            logger.info("已開啓自動按照類型注入 '" + holder.getBeanName() + "'.");
        }
    }

    public void setMapperManagerFactoryBean(String mapperManagerFactoryBean) {
        this.mapperManagerFactoryBean = mapperManagerFactoryBean;
    }
}

四、核心的接口實現類:BaseMapperFactoryBean

package com.dxz.test;

import java.lang.reflect.Proxy;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;

public class BaseMapperFactoryBean<T>
        implements FactoryBean<T>, InitializingBean, ApplicationListener<ApplicationEvent>, ApplicationContextAware {
    /**
     * 要注入的接口類定義
     */
    private Class<T> mapperInterface;

    /**
     * Spring上下文
     */
    private ApplicationContext applicationContext;

    // 也因該走工廠方法注入得來

    private BaseMapper mapperManagerFactoryBean;

    public BaseMapperFactoryBean(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    @Override
    public T getObject() throws Exception {
        // 採用動態代理生成接口實現類,核心實現
        return (T) Proxy.newProxyInstance(applicationContext.getClassLoader(), new Class[] { mapperInterface },
                new MapperJavaProxy(mapperManagerFactoryBean, mapperInterface));
    }

    @Override
    public Class<?> getObjectType() {
        return this.mapperInterface;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        // TODO 判斷屬性的注入是否正確-如mapperInterface判空
        if (null == mapperInterface)
            throw new IllegalArgumentException("Mapper Interface Can't Be Null!!");
    }

    /**
     * Handle an application event.
     *
     * @param event
     *            the event to respond to
     */
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        // TODO 可依據事件進行擴展
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public void setMapperInterface(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public void setMapperManagerFactoryBean(BaseMapper mapperManagerFactoryBean) {
        this.mapperManagerFactoryBean = mapperManagerFactoryBean;
    }
}

五、定義默認的BaseMapper的FactoryBean-MapperManagerFactoryBean

package com.dxz.test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MapperJavaProxy implements InvocationHandler {

    private BaseMapper baseMapper;

    private Class<?> interfaceClass;

    public MapperJavaProxy(BaseMapper baseMapper, Class<?> interfaceClass) {
        this.baseMapper = baseMapper;
        this.interfaceClass = interfaceClass;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (!interfaceClass.isInterface()) {
            throw new IllegalArgumentException("mapperInterface is not interface.");
        }

        if (baseMapper == null) {
            baseMapper = new CustomBaseMapper();
        }
        return method.invoke(baseMapper, args);
    }
}

 

七、調用時的核心配置類:DefaultClassRegistryBeanFactory

package com.dxz.test;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;

public class DefaultClassRegistryBeanFactory
        implements ApplicationContextAware, BeanDefinitionRegistryPostProcessor, BeanNameAware {

    private String scanPackage;

    private String beanName;

    private String mapperManagerFactoryBean;

    private ApplicationContext applicationContext;

    public String getScanPackage() {
        return scanPackage;
    }

    public void setScanPackage(String scanPackage) {
        this.scanPackage = scanPackage;
    }

    public String getMapperManagerFactoryBean() {
        return mapperManagerFactoryBean;
    }

    public void setMapperManagerFactoryBean(String mapperManagerFactoryBean) {
        this.mapperManagerFactoryBean = mapperManagerFactoryBean;
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        if (StringUtils.isEmpty(this.scanPackage)) {
            throw new IllegalArgumentException("scanPackage can't be null");
        }
        String basePackage2 = this.applicationContext.getEnvironment().resolvePlaceholders(this.scanPackage);
        String[] packages = StringUtils.tokenizeToStringArray(basePackage2,
                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        DefaultClassPathScanner defaultClassPathScanner = new DefaultClassPathScanner(beanDefinitionRegistry);
        defaultClassPathScanner.setMapperManagerFactoryBean(mapperManagerFactoryBean);
        defaultClassPathScanner.registerFilters();

        defaultClassPathScanner.doScan(packages);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory)
            throws BeansException {

    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void setBeanName(String name) {
        this.beanName = name;
    }
}

八、調用測試

   8.一、假設你在包目錄:colin.spring.basic.advanced.inject.dao下聲明自定義的類UserMapper

public interface UserMapper extends BaseMapper {
}

8.二、聲明配置類:ClassRegistryBeanScannerConfig

package com.dxz.test;

@Configuration
public class ClassRegistryBeanScannerConfig {

    @Bean(name = "mapperManagerFactoryBean")
    public MapperManagerFactoryBean configMapperManagerFactoryBean() {
        MapperManagerFactoryBean mapperManagerFactoryBean = new MapperManagerFactoryBean();
        return mapperManagerFactoryBean;
    }

    @Bean
    public DefaultClassRegistryBeanFactory configDefaultClassRegistryBeanFactory() {
        DefaultClassRegistryBeanFactory defaultClassRegistryBeanFactory = new DefaultClassRegistryBeanFactory();
        defaultClassRegistryBeanFactory.setScanPackage("colin.spring.basic.advanced.inject.dao");
        defaultClassRegistryBeanFactory.setMapperManagerFactoryBean("mapperManagerFactoryBean");
        return defaultClassRegistryBeanFactory;
    }
}

8.三、測試調用

package com.dxz.test;

public class Snippet {
    public static void main(String[] args) {
    AnnotationConfigApplicationContext acApplicationCOntext = new AnnotationConfigApplicationContext("colin.spring.basic.advanced.inject");
    UserMapper userMapper = acApplicationCOntext.getBean(UserMapper.class);
    userMapper.add("lalaldsf");
    acApplicationCOntext.stop();
    }
}
相關文章
相關標籤/搜索