Spring boot 业务插件开发

Spring boot 动态加载class, 注册Bean 插件开发简单实现

  1. 使用场景
    项目运行,在不重启服务器的前提下,动态增加业务逻辑。业务逻辑以插件的形式加入。

  2. 技术分析
    虚拟机在启动时已经把class文件加载到虚拟机,要考虑如何把外部的 class 文件加载到虚拟机中。
    class 文件加载到虚拟机后,要考虑如何把类动态的加到 spring中,注册成bean.
    如何调用加载到spring容器中的 bean.

  3. 技术实现

    外部class文件加载到JVM

    public static List<Class> LoadClass(String filePath, ClassLoader beanClassLoader) {
        List<Class> classNameList = new ArrayList<>();
        File clazzPath = new File(filePath);
        int clazzCount = 0;
        if (clazzPath.exists() && clazzPath.isDirectory()) {
            int clazzPathLen = clazzPath.getAbsolutePath().length() + 1;
            Stack<File> stack = new Stack<>();
            stack.push(clazzPath);
            while (stack.isEmpty() == false) {
                File path = stack.pop();
                File[] classFiles = path.listFiles(new FileFilter() {
                    public boolean accept(File pathname) {
                        return pathname.isDirectory() || pathname.getName().endsWith(".class");
                    }
                });
                for (File subFile : classFiles) {
                    if (subFile.isDirectory()) {
                        stack.push(subFile);
                    } else {
                        if (clazzCount++ == 0) {
                            Method method = null;
                            try {
                                method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
                            } catch (NoSuchMethodException e) {
                                e.printStackTrace();
                            }
                            boolean accessible = method.isAccessible();
                            try {
                                if (accessible == false) {
                                    method.setAccessible(true);
                                }
                                URLClassLoader classLoader = (URLClassLoader) beanClassLoader;
                                try {
                                    URL url = clazzPath.toURI().toURL();
                                    method.invoke(classLoader, url);
                                } catch (IllegalAccessException e) {
                                    e.printStackTrace();
                                } catch (InvocationTargetException e) {
                                    e.printStackTrace();
                                } catch (MalformedURLException e) {
                                    e.printStackTrace();
                                }
                            } finally {
                                method.setAccessible(accessible);
                            }
                        }
                        String className = subFile.getAbsolutePath();
                        className = className.substring(clazzPathLen, className.length() - 6);
                        className = className.replace(File.separatorChar, '.');
                        try {
                            classNameList.add(Class.forName(className));
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
        return classNameList;
    }

注:filePath是外部class 文件的根目录(例如,插件包名为com.test 类为Plugin. java ,根目录为d:/aaa ,Plugin.java全目录应为:d:/aaa/com/test/Plugin. java)

动态注册bean.

    @RequestMapping("/beanLoad")
    public String beanLoad(){
        ApplicationContext applicationContext = CommonContextUtils.getApplicationContext();
        ConfigurableApplicationContext context = (ConfigurableApplicationContext)applicationContext;
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory)context.getBeanFactory();
        ClassLoader beanClassLoader = beanFactory.getBeanClassLoader();
        Class aClass = ClassUtil.LoadClass("D:/classtest",beanClassLoader).get(0);
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(aClass);
        beanFactory.registerBeanDefinition(aClass.getName(),beanDefinitionBuilder.getRawBeanDefinition());
        Object aaa = (applicationContext.getBean(aClass.getName()));
        Method m = null;
        Method m2 = null;
        try {
            m2 = aClass.getMethod("setApplicationContext", ApplicationContext.class);
            m = aClass.getMethod("getString", null);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        Object ret = null;
        try {
            m2.invoke(aaa,applicationContext);
            ret = m.invoke(aaa, null);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return ret.toString();
    }

此处涉及一个知识点,如果取得ApplicationContext

@SuppressWarnings({ "rawtypes", "unchecked" })
@Component
public class CommonContextUtils implements ApplicationContextAware {
    
    

    private static ApplicationContext applicationContext;

    public void setApplicationContext(ApplicationContext arg0)
            throws BeansException {
        applicationContext = arg0;
    }

    /**
     * 获取applicationContext对象
     * @return
     */
    public static ApplicationContext getApplicationContext(){
        return applicationContext;
    }

    /**
     * 根据bean的id来查找对象
     * @param id
     * @return
     */
    public static Object getBeanById(String id){
        return applicationContext.getBean(id);
    }

    /**
     * 根据bean的class来查找对象
     * @param c
     * @return
     */
    public static Object getBeanByClass(Class c){
        return applicationContext.getBean(c);
    }

    /**
     * 根据bean的class来查找所有的对象(包括子类)
     * @param c
     * @return
     */
    public static Map getBeansByClass(Class c){
        return applicationContext.getBeansOfType(c);
    }
}

注:此时已经实现了class加载和bean的注册

bean的调用

        Object aaa = (applicationContext.getBean(aClass.getName()));
        Method m = null;
        try {
            m = aClass.getMethod("getString", null);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        Object ret = null;
        try {
            ret = m.invoke(aaa, null);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return ret.toString();

注:此处的主要知识点是反射。

这样就实现了我们所要的功能。不重启服务,又能增加业务逻辑,是不是很牛。

原码下载地址

猜你喜欢

转载自blog.csdn.net/zhou8622/article/details/79389450