動態掃描第三方jar包中的bean

介紹

最近在看源碼的時候發現一個很有用的用法。現在描述一下某個場景:某個使用Spring構建系統需要動態增加,修改,刪除服務。而這個服務是由第三方jar包構成(比如統計服務)。在這些服務中需要使用系統中的bean,利用註解進行自動裝配。

這次主要是記錄掃描的代碼,接下里需要研究研究如何動態註冊和自動裝配的。

步驟:

  1. 主要利用classLoader讀取jar包,進行類的加載
  2. 在類中進行靜態初始化,將該jar包中的所有bean註冊到系統的Spring容器中,並進行自動裝配

測試項目構建

項目結構

構建一個spring主項目,建立一個服務接口,所有服務都實現這接口

public interface IUserInfoSerivce {
    void print();

    void doInit();
}

然後需要一個讀取jar包,初始化服務的方法

    public static <T> T initServices(String path, String jarName){
    T service = null;
        try{
            String sericeName = jarName.split("-")[0];
            String loadURL = "file:" + path + "/" + jarName;
            MyClassLoader classLoader = new MyClassLoader(new URL[]{new URL(loadURL)});
            Thread.currentThread().setContextClassLoader(classLoader);

            Class clz = Class.forName(sericeName, true, classLoader);
            try{
                service = (T)SpringUtil.getApx().getBean(clz);
            }catch (Exception e){
                System.out.println("Spring容器中未找到: " + clz.getName() + "類!");
            }

            if(service == null){
                service = (T) clz.newInstance();
            }
            //如果服務中有定時任務的時候可以使用
//            else {
//                ScheduledAnnotationBeanPostProcessor processor = (ScheduledAnnotationBeanPostProcessor)
//                        SpringUtil.getApx().getBean(AnnotationConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME);
//                if(processor != null){
//                    processor.onApplicationEvent(new ContextRefreshedEvent(SpringUtil.getApx()));
//                }
//            }
        }catch (Exception e){
            System.out.println(e);
        }
        return service;
    }

自定義一個URLClassLoader

public class MyClassLoader extends URLClassLoader {
    public MyClassLoader(){
        super(new URL[0]);
    }

    public MyClassLoader(URL[] urls){
        super(urls);
    }

    public void addJar(URL url){
        this.addURL(url);
    }

    public void loadJar(String strUrl){
        try{
            URL url = new URL(strUrl);
            addUrl(url);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Class<?> findClass(String name) throws ClassNotFoundException{
        return super.findClass(name);
    }

    public Class<?> loadClass(String name) throws ClassNotFoundException{
        return super.loadClass(name);
    }
}

 核心Spring工具類

public class SpringUtil {
    private static ApplicationContext apx = null;

    public static void initClassPathSpring(String configPath){
        apx = new ClassPathXmlApplicationContext(configPath);
    }

    public static void initFileSystemSpring(String configPath){
        apx = new FileSystemXmlApplicationContext("file:" + configPath);
    }

    public static ApplicationContext getApx(){
        return apx;
    }

    public static void scanBean(ClassLoader classLoader, String... basePackages){
        if(classLoader!=null && basePackages != null && apx != null){
            if(apx instanceof ClassPathXmlApplicationContext){
                ClassPathXmlApplicationContext classPathCtx = (ClassPathXmlApplicationContext)apx;
                BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) classPathCtx.getBeanFactory();
                new ClassPathBeanDefinitionScanner(beanDefinitionRegistry).scan(basePackages);
                classPathCtx.getBeanFactory().setBeanClassLoader(classLoader);
            }else {
                FileSystemXmlApplicationContext fileSystemCtx = (FileSystemXmlApplicationContext)apx;
                BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) fileSystemCtx.getBeanFactory();
                new ClassPathBeanDefinitionScanner(beanDefinitionRegistry).scan(basePackages);
                fileSystemCtx.getBeanFactory().setBeanClassLoader(classLoader);
            }
        }
    }

這裏需要注意,beanClassLoader如果不設置,會報錯。

最後就是構建一個主系統的測試服務,用於給第三方jar包使用

@Service
public class CheckService {
    public void print(){
        System.out.println("checkService");
    }
}

接下來就是服務項目

服務項目需要額外建立,然後在IDEA的Project Structure的Models中引入,在maven中依賴系統主包,就可以使用Spring註解了。構建的時候需要先install主包,否則會引用不到。服務包打完後放入主項目的api目錄下。

項目結構如下所示:

服務的對外的入口就是UserInfoService,打包的時候也是以這個類的全名爲名。

@Service
public class UserInfoService implements IUserInfoSerivce {

    static {
        SpringUtil.scanBean(Thread.currentThread().getContextClassLoader(), "com.xck.api");
    }

    @Autowired
    ConsumerTypeService consumerTypeService;

    @Autowired
    CheckService checkService;

    @Override
    public void print(){
        consumerTypeService.print();
        checkService.print();
    }

    @Override
    public void doInit(){
        System.out.println("UserInfoService init");
    }
}

 另外一個服務類似,都是打印類名。需要注意的是上面的這塊靜態快,掃描該jar包中的bean。

最後就是測試類

public class Main {
    public static void main(String[] args) throws Exception {
        SpringUtil.initClassPathSpring("applicationContext.xml");
        String path = System.getProperty("user.dir") + "/api/";
        IUserInfoSerivce userInfoSerivce userInfoSerivce = LoaderJarUtil.initServices(path,
                "com.xck.api.service.UserInfoService-V1.0.0_190313.jar");
        userInfoSerivce.doInit();
        userInfoSerivce.print();
        userInfoSerivce = LoaderJarUtil.initServices(path,
                "com.xck.api.service.UserInfoService-V1.0.0_190313.jar");
        userInfoSerivce.doInit();
        userInfoSerivce.print();
    }
}

 連續調用兩次,用於測試重複初始化是否也能成功。

輸出如下:

2019-3-15 14:16:25 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@7369ca65: startup date [Fri Mar 15 14:16:25 CST 2019]; root of context hierarchy
2019-3-15 14:16:25 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [applicationContext.xml]
2019-3-15 14:16:26 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1d766806: defining beans [org.springframework.context.annotation.internalAsyncAnnotationProcessor,org.springframework.context.annotation.internalScheduledAnnotationProcessor,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,checkService,org.springframework.context.annotation.ConfigurationClassPostProcessor$ImportAwareBeanPostProcessor#0]; root of factory hierarchy
UserInfoService init
ConsumerTypeService
checkService
Exception in thread "main" java.lang.NullPointerException
	at com.xck.api.service.UserInfoService.print(UserInfoService.java:25)
	at com.xck.main.Main.main(Main.java:21)
Spring容器中未找到: com.xck.api.service.UserInfoService類!
UserInfoService init

 可以看到,第一次可以,但是第二次卻不行,實驗證明,不能對同一個jar包初始化兩次。所以在使用的時候需要進行過濾。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章