目前数据治理服务中有众多治理任务,当其中任一治理任务有改动需要升级或新增一个治理任务时,都需要将数据治理服务重启,会影响其他治理任务的正常运行。
2、目标能够动态启动、停止任一治理任务能够动态升级、添加治理任务启动、停止治理任务或升级、添加治理任务不能影响其他任务3、方案为了支持业务代码尽量的解耦,把部分业务功能通过动态加载的方式加载到主程序中,以满足可插拔式的加载、组合式的部署。配合xxl-job任务调度框架,将数据治理任务做成xxl-job任务的方式注册到xxl-job中,方便统一管理。二、动态加载1、自定义类加载器URLClassLoader 是一种特殊的类加载器,可以从指定的 URL 中加载类和资源。它的主要作用是动态加载外部的 JAR 包或者类文件,从而实现动态扩展应用程序的功。为了便于管理动态加载的jar包,自定义类加载器继承URLClassloader。
/** * 自定义类加载器 * * @author lijianyu * @date 2023/04/03 17:54 **/public class MyClassLoader extends URLClassLoader { private Map<String, Class<?>> loadedClasses = new ConcurrentHashMap<>(); public Map<String, Class<?>> getLoadedClasses() { return loadedClasses; } public MyClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { // 从已加载的类集合中获取指定名称的类 Class<?> clazz = loadedClasses.get(name); if (clazz != null) { return clazz; } try { // 调用父类的findClass方法加载指定名称的类 clazz = super.findClass(name); // 将加载的类添加到已加载的类集合中 loadedClasses.put(name, clazz); return clazz; } catch (ClassNotFoundException e) { e.printStackTrace(); return null; } } public void unload() { try { for (Map.Entry<String, Class<?>> entry : loadedClasses.entrySet()) { // 从已加载的类集合中移除该类 String className = entry.getKey(); loadedClasses.remove(className); try{ // 调用该类的destory方法,回收资源 Class<?> clazz = entry.getValue(); Method destory = clazz.getDeclaredMethod("destory"); destory.invoke(clazz); } catch (Exception e ) { // 表明该类没有destory方法 } } // 从其父类加载器的加载器层次结构中移除该类加载器 close(); } catch (Exception e) { e.printStackTrace(); } }}自定义类加载器中,为了方便类的卸载,定义一个map保存已加载的类信息。key为这个类的ClassName,value为这个类的类信息。同时定义了类加载器的卸载方法,卸载方法中,将已加载的类的集合中移除该类。由于此类可能使用系统资源或调用线程,为了避免资源未回收引起的内存溢出,通过反射调用这个类中的destroy方法,回收资源。最后调用close方法。2、动态加载
软件设计 53); font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif; font-size: 16px; text-align: left; text-indent: 0em; text-wrap: wrap; word-spacing: 0.8px; background-color: rgb(255, 255, 255); line-height: 1.75; letter-spacing: 0em;">由于此项目使用spring框架,以及xxl-job任务的机制调用动态加载的代码,因此要完成以下内容
将动态加载的jar包读到内存中将有spring注解的类,通过注解扫描的方式,扫描并手动添加到spring容器中。将@XxlJob注解的方法,通过注解扫描的方式,手动添加到xxljob执行器中。/** * @author lijianyu * @date 2023/04/29 13:18 **/@Componentpublic class DynamicLoad { private static Logger logger = LoggerFactory.getLogger(DynamicLoad.class); @Autowired private ApplicationContext applicationContext; private Map<String, MyClassLoader> myClassLoaderCenter = new ConcurrentHashMap<>(); @Value("${dynamicLoad.path}") private String path; /** * 动态加载指定路径下指定jar包 * @param path * @param fileName * @param isRegistXxlJob 是否需要注册xxljob执行器,项目首次启动不需要注册执行器 * @return map<jobHander, Cron> 创建xxljob任务时需要的参数配置 */ public void loadJar(String path, String fileName, Boolean isRegistXxlJob) throws ClassNotFoundException, InstantiationException, IllegalAccessException { File file = new File(path +"/" + fileName); Map<String, String> jobPar = new HashMap<>(); // 获取beanFactory DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory(); // 获取当前项目的执行器 try { // URLClassloader加载jar包规范必须这么写 URL url = new URL("jar:file:" + file.getAbsolutePath() + "!/"); URLConnection urlConnection = url.openConnection(); JarURLConnection jarURLConnection = (JarURLConnection)urlConnection; // 获取jar文件 JarFile jarFile = jarURLConnection.getJarFile(); Enumeration<JarEntry> entries = jarFile.entries(); // 创建自定义类加载器,并加到map中方便管理 MyClassLoader myClassloader = new MyClassLoader(new URL[] { url }, ClassLoader.getSystemClassLoader()); myClassLoaderCenter.put(fileName, myClassloader); Set<Class> initBeanClass = new HashSet<>(jarFile.size()); // 遍历文件 while (entries.hasMoreElements()) { JarEntry jarEntry = entries.nextElement(); if (jarEntry.getName().endsWith(".class")) { // 1. 加载类到jvm中 // 获取类的全路径名 String className = jarEntry.getName().replace('/', '.').substring(0, jarEntry.getName().length() - 6); // 1.1进行反射获取 myClassloader.loadClass(className); } } Map<String, Class<?>> loadedClasses = myClassloader.getLoadedClasses(); XxlJobSpringExecutor xxlJobExecutor = new XxlJobSpringExecutor(); for(Map.Entry<String, Class<?>> entry : loadedClasses.entrySet()){ String className = entry.getKey(); Class<?> clazz = entry.getValue(); // 2. 将有@spring注解的类交给spring管理 // 2.1 判断是否注入spring Boolean flag = SpringAnnotationUtils.hasSpringAnnotation(clazz); if(flag){ // 2.2交给spring管理 BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz); AbstractBeanDefinition beanDefinition = builder.getBeanDefinition(); // 此处beanName使用全路径名是为了防止beanName重复 String packageName = className.substring(0, className.lastIndexOf(".") + 1); String beanName = className.substring(className.lastIndexOf(".") + 1); beanName = packageName + beanName.substring(0, 1).toLowerCase() + beanName.substring(1); // 2.3注册到spring的beanFactory中 beanFactory.registerBeanDefinition(beanName, beanDefinition); // 2.4允许注入和反向注入 beanFactory.autowireBean(clazz); beanFactory.initializeBean(clazz, beanName); /*if(Arrays.stream(clazz.getInterfaces()).collect(Collectors.toSet()).contains(InitializingBean.class)){ initBeanClass.add(clazz); }*/ initBeanClass.add(clazz); } // 3. 带有XxlJob注解的方法注册任务 // 3.1 过滤方法 Map<Method, XxlJob> annotatedMethods = null; try { annotatedMethods = MethodIntrospector.selectMethods(clazz, new MethodIntrospector.MetadataLookup<XxlJob>() { @Override public XxlJob inspect(Method method) { return AnnotatedElementUtils.findMergedAnnotation(method, XxlJob.class); } }); } catch (Throwable ex) { } // 3.2 生成并注册方法的JobHander for (Map.Entry<Method, XxlJob> methodXxlJobEntry : annotatedMethods.entrySet()) { Method executeMethod = methodXxlJobEntry.getKey(); // 获取jobHander和Cron XxlJobCron xxlJobCron = executeMethod.getAnnotation(XxlJobCron.class); if(xxlJobCron == null){ throw new CustomException("500", executeMethod.getName() + "(),没有添加@XxlJobCron注解配置定时策略"); } if (!CronExpression.isValidExpression(xxlJobCron.value())) { throw new CustomException("500", executeMethod.getName() + "(),@XxlJobCron参数内容错误"); } XxlJob xxlJob = methodXxlJobEntry.getValue(); jobPar.put(xxlJob.value(), xxlJobCron.value()); if (isRegistXxlJob) { executeMethod.setAccessible(true); // regist Method initMethod = null; Method destroyMethod = null; xxlJobExecutor.registJobHandler(xxlJob.value(), new CustomerMethodJobHandler(clazz, executeMethod, initMethod, destroyMethod)); } } } // spring bean实际注册 initBeanClass.forEach(beanFactory::getBean); } catch (IOException e) { logger.error("读取{} 文件异常", fileName); e.printStackTrace(); throw new RuntimeException("读取jar文件异常: " + fileName); } }}
以下是判断该类是否有spring注解的工具类软件设计
apublic class SpringAnnotationUtils { private static Logger logger = LoggerFactory.getLogger(SpringAnnotationUtils.class); /** * 判断一个类是否有 Spring 核心注解 * * @param clazz 要检查的类 * @return true 如果该类上添加了相应的 Spring 注解;否则返回 false */ public static boolean hasSpringAnnotation(Class<?> clazz) { if (clazz == null) { return false; } //是否是接口 if (clazz.isInterface()) { return false; } //是否是抽象类 if (Modifier.isAbstract(clazz.getModifiers())) { return false; } try { if (clazz.getAnnotation(Component.class) != null