2022-06-25_项目热部署学习笔记

20220625_项目热部署学习笔记

1概述

本文主要是结合代码,学习一下spring中Bean的动态加载,具体实现看代码,主要分以下几种情况:

  1. 本地项目中的类加载到当前项目的spring 容器。
  2. 外部磁盘中的单个clazz文件基于自定义加载器的方式加载到当前项目spring 容器中。
  3. 外部磁盘中的单个clazz文件基于URLClassLoader加载器的方式加载到当前项目spring 容器中
  4. 外部磁盘jar中的clazz文件基于注解的方式加载到当前项目spring 容器中。
  5. 外部磁盘jar中的clazz文件基于反射的方式加载当前项目中。

2代码示例

2.1本地项目中的类加载到当前项目的spring 容器

/**
 * @author kikop
 * @version 1.0
 * @project myspringhotdeploydemo
 * @file DynamicInjectManager
 * @desc 项目热部署示例学习
 * @date 2022/06/25
 * @time 8:30
 * @by IDE: IntelliJ IDEA
 */
public class DynamicInjectManager {
    
/**
     * 本地项目中的类加载到当前项目的spring 容器2
     * @param defaultListableBeanFactory
     */
    public static void dynamicInjectBeanByConstructor(DefaultListableBeanFactory defaultListableBeanFactory) {

        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(CurrentAnimal.class);
//        BeanDefinitionBuilder beanDefinitionBuilder2 = BeanDefinitionBuilder.genericBeanDefinition(CurrentAnimal.class);

        // 1.方式1,by constructorArgIndex
        beanDefinitionBuilder.addConstructorArgValue("北极熊")
                .addConstructorArgValue("白色")
                .addConstructorArgValue(3);

        String beanName = "myAnimal";
        defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition());

        Object bean = defaultListableBeanFactory.getBean(beanName);
        CurrentAnimal animal = (CurrentAnimal) bean;
        System.out.println("animal.getName():" + animal.getName());
    }


    /**
     * 本地项目中的类加载到当前项目的spring 容器1
     * @param defaultListableBeanFactory
     */
    public static void dynamicInjectBeanByProperty(DefaultListableBeanFactory defaultListableBeanFactory) {

        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(CurrentAnimal.class);

        // 2.方式2
        beanDefinitionBuilder.addPropertyValue("name", "北极熊")
                .addPropertyValue("color", "白色")
                .addPropertyValue("age", 3);
        String beanName = "myAnimal";
        defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition());

        Object bean = defaultListableBeanFactory.getBean(beanName);
        CurrentAnimal animal = (CurrentAnimal) bean;
        System.out.println("animal.getName():" + animal.getName());
    }

2.2外部磁盘中的单个clazz文件基于自定义加载器反射的方式加载到当前项目spring 容器中

2.2.1MyClassLoader

package com.kikop.cloader;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * @author kikop
 * @version 1.0
 * @project myspringhotdeploydemo
 * @file ICalculator
 * @desc 项目热部署示例学习
 * @date 2022/06/25
 * @time 8:30
 * @by IDE: IntelliJ IDEA
 * @reference https://mp.weixin.qq.com/s/8JNecuJA9n07Ob7XzLPQTA
 */
public class MyClassLoader extends ClassLoader {

    private String dirPath;

    //  是否保留类的完整包名路径,
    // true:保留时,类要放在原来的包路径下
    // false:类直接放在 dirPath下
    private boolean isKeepPacketName = true;

    /**
     * @param dirPath D:/mqexperiment/hotdeploy
     */
    public MyClassLoader(String dirPath, boolean isKeepPacketName) {

        if (!dirPath.endsWith("/") && !dirPath.endsWith("\\")) {
            dirPath += "/";
        }
        this.dirPath = dirPath;
        this.isKeepPacketName = isKeepPacketName;
    }

    /**
     * 触发被 loadClass-->findClass-->loadClass
     *
     * @param name com.kikop.model.CurrentAnimal
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class findClass(String name) throws ClassNotFoundException {

        String filePath; // 磁盘路径
        if (isKeepPacketName) {
            // 前提:包名要保留,因为是根据 className进行分割的
            // file:///D:/mqexperiment/hotdeploy/com/kikop/model/CurrentAnimal.class
            filePath = dirPath + name.replace('.', '/') + ".class";
        } else {
            // file:///D:/mqexperiment/hotdeploy/CurrentAnimal.class
            filePath = dirPath + name.substring(name.lastIndexOf('.') + 1) + ".class";
        }
        byte[] b;
        Path path;
        try {
            String strIgnore = "Customizer";
            if (name.lastIndexOf(strIgnore) != -1) { // ignore for check beaninfo
                return null;
            }
            path = Paths.get(new URI(filePath));

//            b = MyClassLoaderUtil.getBytesByFilePath(filePath);
            b = Files.readAllBytes(path);
            // defineClass将字节数组转换成Class对象
            return defineClass(name, b, 0, b.length);
        } catch (IOException | URISyntaxException e) {
            e.printStackTrace();
            return null;
        }
    }

}

2.2.2dynamicInjectBeanByCustomCloader

/**
 * @author kikop
 * @version 1.0
 * @project myspringhotdeploydemo
 * @file DynamicInjectManager
 * @desc 项目热部署示例学习
 * @date 2022/06/25
 * @time 8:30
 * @by IDE: IntelliJ IDEA
 */
public class DynamicInjectManager {
    
public static void dynamicInjectBeanByCustomCloader(DefaultListableBeanFactory defaultListableBeanFactory
            , Map reqParam) throws ClassNotFoundException, MalformedURLException {

        // 1.解析
        String beanName = reqParam.get("beanName");
        try {
            if (null != defaultListableBeanFactory.getBean(beanName)) {
                System.out.println(String.format("%s 容器中已经存在", beanName));
                return;
            }
        } catch (Exception ex) {
            // ignore
        }

        String strLocation = reqParam.get("localtion");
        String strClazz = reqParam.get("clazz");
        String strPath = "file:///" + strLocation; // URL
        MyClassLoader myClassLoader = new MyClassLoader(strPath, false);
        Class aClass = myClassLoader.loadClass(strClazz); // com.kikop.model.CurrentAnimal
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(aClass);

        // 2.组装
        for (int i = 0; i < aClass.getDeclaredFields().length; i++) {
            String fieldName = aClass.getDeclaredFields()[i].getName();
            if (null != reqParam.get(fieldName)) {
                beanDefinitionBuilder.addPropertyValue(fieldName, reqParam.get(fieldName));
            } else {
                beanDefinitionBuilder.addPropertyValue(fieldName, "default" + fieldName);
            }
        }
        defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition());

        // 3.输出字段
        // beanName的加载器:myClassLoader
        // type.getClassLoader():myClassLoader
        // 默认会变相加载该类的 XCustomizer,肯定没有啊
        Object animalBean = defaultListableBeanFactory.getBean(beanName);
        List fields = Arrays.asList(aClass.getDeclaredFields());
        fields.stream().forEach(field -> {
            System.out.println(field);
        });
    }

2.3外部磁盘中的单个clazz文件基于URLClassLoader加载器的方式加载到当前项目spring 容器中

/**
 * @author kikop
 * @version 1.0
 * @project myspringhotdeploydemo
 * @file DynamicInjectManager
 * @desc 项目热部署示例学习
 * @date 2022/06/25
 * @time 8:30
 * @by IDE: IntelliJ IDEA
 */
public class DynamicInjectManager {

    public static void dynamicInjectBeanByReflect(DefaultListableBeanFactory defaultListableBeanFactory
            , Map reqParam) throws ClassNotFoundException, MalformedURLException {

        // 1.解析
        String beanName = reqParam.get("beanName");
        try {
            if (null != defaultListableBeanFactory.getBean(beanName)) {
                System.out.println(String.format("%s 容器中已经存在", beanName));
                return;
            }
        } catch (Exception ex) {
            // ignore
        }

        String strLocation = reqParam.get("localtion");
        // 使用file协议在本地寻找指定.class文件,file:///Users/fa1c0n/codeprojects/IdeaProjects/misc-classes/src/main/java/
        // 使用http协议到远程地址寻找指定.class文件, http://127.0.0.1:8000/
        String strPath = "file:///" + strLocation; // URL
        String strClazz = reqParam.get("clazz");

        URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL(strPath)});
        // 注意:
        // 类名 com.kikop.model.User
        Class aClass = urlClassLoader.loadClass(strClazz);

        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(aClass);

        // 2.组装
        for (int i = 0; i < aClass.getDeclaredFields().length; i++) {
            String fieldName = aClass.getDeclaredFields()[i].getName();
            if (null != reqParam.get(fieldName)) {
                beanDefinitionBuilder.addPropertyValue(fieldName, reqParam.get(fieldName));
            } else {
                beanDefinitionBuilder.addPropertyValue(fieldName, "default" + fieldName);
            }
        }
        defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition());

        // 3.输出字段
        Object animalBean = defaultListableBeanFactory.getBean(beanName);
        List fields = Arrays.asList(aClass.getDeclaredFields());
        fields.stream().forEach(field -> {
            System.out.println(field);
        });
    }

2.4外部磁盘jar中的clazz文件基于注解的方式加载到当前项目spring 容器中

package com.kikop.deploymethod;

import com.kikop.calculator.ICalculator;
import com.kikop.utils.MyJarUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.net.URL;
import java.net.URLClassLoader;
import java.util.Set;

/**
 * @author kikop
 * @version 1.0
 * @project myspringhotdeploydemo
 * @file DeployByAnnotation
 * @desc 项目热部署示例学习
 * @date 2022/06/25
 * @time 8:30
 * @by IDE: IntelliJ IDEA
 * @reference https://mp.weixin.qq.com/s/8JNecuJA9n07Ob7XzLPQTA
 */
@Component
public class DeployByAnnotation implements ApplicationContextAware {


    private static ApplicationContext applicationContext;


    // jar:file:/
    private static String jarAddress = "D:\\mqexperiment\\hotdeploy\\mycalculatorbusinesscomponent-0.0.1-SNAPSHOT.jar";
    private static String jarPath = "file:/" + jarAddress;

    /**
     * 加入jar包后 动态注册bean到spring容器,包括bean的依赖
     */
    public static void hotDeployWithSpring() throws Exception {
        // com.kiko.calculator.impl.CalculatorImpl
        Set classNameSet = MyJarUtils.readJarFile(jarAddress);
        URLClassLoader urlClassLoader = new URLClassLoader(
                new URL[]{new URL(jarPath)}, Thread.currentThread().getContextClassLoader());

        for (String className : classNameSet) {
            Class clazz = urlClassLoader.loadClass(className);

            if (MyJarUtils.isSpringBeanClass(clazz)) { //  是需要的注解
                BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);

                ((BeanDefinitionRegistry) applicationContext).registerBeanDefinition(
                        MyJarUtils.transformName(className), beanDefinitionBuilder.getBeanDefinition());
            }
        }
        ICalculator calculator = applicationContext.getBean(ICalculator.class);
        System.out.println(calculator.calculate(4, 5));
    }

    /**
     * 删除jar包时 需要在spring容器删除注入
     */
    public static void delete() throws Exception {
        Set classNameSet = MyJarUtils.readJarFile(jarAddress);
        URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL(jarPath)}, Thread.currentThread().getContextClassLoader());
        for (String className : classNameSet) {
            Class clazz = urlClassLoader.loadClass(className);
            if (MyJarUtils.isSpringBeanClass(clazz)) {
                ((BeanDefinitionRegistry) applicationContext).removeBeanDefinition(MyJarUtils.transformName(className));
            }
        }
    }


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

2.5外部磁盘jar中的clazz文件基于反射的方式加载当前项目中

package com.kikop.deploymethod;

import com.kikop.calculator.ICalculator;

import java.net.URL;
import java.net.URLClassLoader;

/**
 * @author kikop
 * @version 1.0
 * @project myspringhotdeploydemo
 * @file DeployByReflect
 * @desc 项目热部署示例学习
 * @date 2022/06/25
 * @time 8:30
 * @by IDE: IntelliJ IDEA
 * @reference https://mp.weixin.qq.com/s/8JNecuJA9n07Ob7XzLPQTA
 */
public class DeployByReflect {

    // 位置目录,不能是文件
    private static String jarAddress = "D:\\mqexperiment\\hotdeploy\\mycalculatorbusinesscomponent-0.0.1-SNAPSHOT.jar";
    private static String jarPath = "file:/" + jarAddress;


    // ClassLoader(最底层)-->SecureClassLoader(中间态)-->URLClassLoader(最简单)
    // SecureClassLoader 默认: this(checkCreateClassLoader(), null, getSystemClassLoader());

    /**
     * 热加载Calculator接口的实现 反射方式
     *
     * @throws Exception
     */
    public static void hotDeployWithReflect() throws Exception {

        URLClassLoader urlClassLoader = new URLClassLoader(
                new URL[]{new URL(jarPath)},
                Thread.currentThread().getContextClassLoader());
        Class clazz = urlClassLoader.loadClass("com.kikop.calculator.impl.CalculatorImpl");
        ICalculator iCalculator = (ICalculator) clazz.newInstance();
        int result = iCalculator.add(18, 1);
        System.out.println(result);
    }

}

2.6工具类

2.6.1MyJarUtils

package com.kikop.utils;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @author kikop
 * @version 1.0
 * @project myspringhotdeploydemo
 * @file MyJarUtils
 * @desc 项目热部署示例学习
 * @date 2022/06/25
 * @time 8:30
 * @by IDE: IntelliJ IDEA
 * @reference https://mp.weixin.qq.com/s/8JNecuJA9n07Ob7XzLPQTA
 */
public class MyJarUtils {

    /**
     * 读取jar包中所有类文件
     */
    public static Set readJarFile(String jarAddress) throws IOException {

        Set classNameSet = new HashSet<>();
        JarFile jarFile = new JarFile(jarAddress);
        Enumeration entries = jarFile.entries();// 遍历整个jar文件
        while (entries.hasMoreElements()) {
            JarEntry jarEntry = entries.nextElement();
            String name = jarEntry.getName();
            if (name.endsWith(".class")) { // 过滤 .class
                // com/kikop/calculator/impl/XxxImpl.class
                // com/kikop/calculator/impl/XxxImpl
                // 最终的classNamecom.kikop.calculator.impl.XxxImpl
                String className = name.replace(".class", "").replaceAll("/", ".");
                classNameSet.add(className);
            }
        }
        return classNameSet;
    }

    /**
     * 方法描述 判断class对象是否带有spring的注解
     */
    public static boolean isSpringBeanClass(Class cla) {
        if (cla == null) {
            return false;
        }
        //是否是接口
        if (cla.isInterface()) {
            return false;
        }
        // 是否是抽象类
        if (Modifier.isAbstract(cla.getModifiers())) {
            return false;
        }
        if (cla.getAnnotation(Component.class) != null) {
            return true;
        }
        if (cla.getAnnotation(Repository.class) != null) {
            return true;
        }
        if (cla.getAnnotation(Service.class) != null) {
            return true;
        }
        return false;
    }

    /**
     * 类名首字母小写 作为 spring容器beanMap的key
     */
    public static String transformName(String className) {
        String tmpstr = className.substring(className.lastIndexOf(".") + 1);
        return tmpstr.substring(0, 1).toLowerCase() + tmpstr.substring(1);
    }
}

2.6.2MyClassLoaderUtil

package com.kikop.utils;


import com.sun.xml.internal.ws.util.ByteArrayBuffer;

import java.io.*;
import java.net.URL;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @author kikop
 * @version 1.0
 * @project myjdkclazzloader1demo
 * @file MyClassLoaderUtil
 * @desc 获取类的字节码
 * @date 2021/6/13
 * @time 18:00
 * @by IDE: IntelliJ IDEA
 */
public class MyClassLoaderUtil {

    private static String convertFilePath(String ignoreValue, String filePath) {

        if (filePath.indexOf(ignoreValue) != -1) {
            filePath = filePath.substring(filePath.indexOf(ignoreValue) + ignoreValue.length());
        }
        return filePath;
    }

    /**
     * getBytesByFilePath
     * 

* 返回类的字节码 * * @param filePath D:/test/com.kikop.model.User.class * @return */ public static byte[] getBytesByFilePath(String filePath) { filePath = convertFilePath("file:///", filePath); filePath = convertFilePath("file:/", filePath); byte[] resultBytes = null; InputStream inputStream = null; // 借助 byteArrayOutputStream暂存字节流 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); try { File file = new File(filePath); if (!file.exists()) { return resultBytes; } inputStream = new FileInputStream(file); int c = 0; while ((c = inputStream.read()) != -1) { byteArrayOutputStream.write(c); } resultBytes = byteArrayOutputStream.toByteArray(); } catch (FileNotFoundException ex) { ex.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { byteArrayOutputStream.close(); inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } return resultBytes; } /** * getBytesByJarFile * 返回 Jar包中类的字节码 * * @param jarDirectory D:\workdirectory\mqexperiment\clloader\ * @param jarName xx.jar * @param classFullName java.lang.String * @return * @throws IOException */ public static byte[] getBytesByJarFile(String jarDirectory, String jarName, String classFullName) throws IOException { byte[] resultBytes = null; InputStream inputStream = null; ByteArrayOutputStream byteArrayOutputStream = null; try { // replaceAll:要加斜杠转义,replace:不需要 // java.lang.String --> java/lang/String String tmpClassFullName = classFullName.replaceAll("\\.", "/").concat(".class"); // com.kikop.AddressService-->com/kikop/service/AddressService // String tmpClassFullName2 = classFullName.replace(".", "/").concat(".class"); String jarFullName = jarDirectory + "/" + jarName; JarFile jar = new JarFile(jarFullName); JarEntry entry = jar.getJarEntry(tmpClassFullName); // java/lang/String.class if (null == entry) { // 增加异常判断,文件不存在 System.out.println("tmpClassFullName:" + tmpClassFullName); return null; } inputStream = jar.getInputStream(entry); byteArrayOutputStream = new ByteArrayOutputStream(); int nextValue = inputStream.read(); while (-1 != nextValue) { byteArrayOutputStream.write(nextValue); nextValue = inputStream.read(); } // byte[] buffer=new byte[2048]; // int len=0; // while((len=in.read(buffer))!=-1){ // out.write(buffer,0,len); // } resultBytes = byteArrayOutputStream.toByteArray(); } catch (Exception ex) { ex.printStackTrace(); } finally { try { if (null != byteArrayOutputStream) { byteArrayOutputStream.close(); } if (null != inputStream) { inputStream.close(); } } catch (IOException e) { e.printStackTrace(); } } return resultBytes; } /** * getBytesByJarFile 返回 Jar包中类的字节码 * * @param jarDirectory D:\workdirectory\mqexperiment\clloader\ * @param jarName xx.jar * @param classFullName java.lang.String * @return * @throws IOException */ public static byte[] getBytesByJarFile2(String jarDirectory, String jarName, String classFullName) throws IOException { // com.kikop.AddressService-->com/kikop/service/AddressService.class String tmpClassFullName = classFullName.replace(".", "/").concat(".class"); InputStream inputStream; ByteArrayBuffer byteArrayBuffer = new ByteArrayBuffer(); byte[] resultBytes = null; int code; URL fileUrl; try { // jar:file:\ // D:\workdirectory\mqexperiment\clloader\mymoduleva-1.0-SNAPSHOT.jar!/ // com/kikop/service/AddressService.class String jarFullName = jarDirectory + "/" + jarName; String strSpec = "jar:file:\\" + jarFullName + "!/" + tmpClassFullName; fileUrl = new URL(strSpec); inputStream = fileUrl.openStream(); while ((code = inputStream.read()) != -1) { byteArrayBuffer.write(code); } resultBytes = byteArrayBuffer.toByteArray(); } catch (Exception e) { e.printStackTrace(); } return resultBytes; } }

2.7测试

package com.kikop;

import com.kikop.cloader.MyClassLoader;
import com.kikop.deploymethod.DeployByAnnotation;
import com.kikop.deploymethod.DeployByReflect;
import com.kikop.deploymethod.DynamicInjectManager;
import com.kikop.model.CurrentAnimal;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author kikop
 * @version 1.0
 * @project myspringhotdeploydemo
 * @file MySpringHotDeployDemoApplication
 * @desc 项目热部署示例学习
 * @date 2022/06/25
 * @time 8:30
 * @by IDE: IntelliJ IDEA
 * @reference https://mp.weixin.qq.com/s/8JNecuJA9n07Ob7XzLPQTA
 */
@SpringBootApplication
public class MySpringHotDeployDemoApplication implements CommandLineRunner {


    private static DefaultListableBeanFactory defaultListableBeanFactory;

    public static void main(String[] args) throws ClassNotFoundException, MalformedURLException {

        ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(MySpringHotDeployDemoApplication.class, args);

        AutowireCapableBeanFactory autowireCapableBeanFactory = configurableApplicationContext.getAutowireCapableBeanFactory();
        defaultListableBeanFactory = (DefaultListableBeanFactory) autowireCapableBeanFactory;

        Map reqParam = new HashMap<>();

        reqParam.put("name", "北极熊");
        reqParam.put("color", "白色");
        reqParam.put("age", "3");
        reqParam.put("localtion", "D:/mqexperiment/hotdeploy/");
        reqParam.put("clazz", "com.kikop.model.Animal");
        reqParam.put("beanName", "myAnimal");
        DynamicInjectManager.dynamicInjectBeanByCustomCloader(defaultListableBeanFactory, reqParam);
    }


    @Override
    public void run(String... args) throws Exception {

        // 1.基于JDK反射包热部署
        DeployByReflect.hotDeployWithReflect();


        // 2.基于Spring注解Jar包热部署
//        DeployByAnnotation.hotDeployWithSpring();

    }
}

总结

1.1JDKintrospector

当我们把这个类交给 spring 的时候,问题就出现了: spring 是一个已有的框架, 它并不知道 User 这个类,也并不知道它有哪些方法、哪些属性。

public void introspector(String clazz, Map properties) throws Exception {
    //反射创建实例
    Class target = Class.forName(clazz);
    Object bean = target.newInstance();

    BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
    PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();

    for (PropertyDescriptor pd : pds) {
        Method setMethod = pd.getWriteMethod();
        String fieldName = pd.getName();

        if ("name".equalsIgnoreCase(fieldName)) {
            setMethod.invoke(bean, properties.get(fieldName));
        } else if ("age".equalsIgnoreCase(fieldName)){
            setMethod.invoke(bean, properties.get(fieldName));
        }
    }

参考

1动态上传jar包热部署实战

https://mp.weixin.qq.com/s/8JNecuJA9n07Ob7XzLPQTA

2SpringBoot动态注入Bean

http://www.caotama.com/1398966.html

3SpringBoot动态注入bean (系统注入漏洞原理)

https://blog.csdn.net/weixin_45943597/article/details/124176226

4Java中动态加载字节码的方法 (持续补充)Good

https://blog.csdn.net/mole_exp/article/details/122768814

5java Introspector(内省) 的使用场景以及为什么使用

https://www.jianshu.com/p/418122d84e6e

6[深入理解Java:内省(Introspector)

https://www.cnblogs.com/peida/archive/2013/06/03/3090842.html)

  • public interface **Customizer**

customizer 类提供一个用来自定义目标 Java Bean 的完全自定义 GUI。

每个 customizer 都应该从 java.awt.Component 类继承,因此它们可以在 AWT 对话框或面板中被实例化。

每个 customizer 都应该有一个 null 构造方法。

你可能感兴趣的:(2022-06-25_项目热部署学习笔记)