自动生成带Optional方法的派生Bean类,对Mybatis反向工程结果的加强

本文展示了如何通过自动生成带有Optional方法的JavaBean派生类来加强Mybatis的反向工程结果。内容包括效果展示,原JavaBean类与派生类的区别,以及提供了一段工具代码的使用说明,详细解释了如何指定文件生成位置、包名以及构造文件的字节码对象数组。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

效果展示

原JavaBean类

package collection;

public class TestBean {
    private String name;
    private String[] attributes;
    private int num;
    private int[] map;
    private int[][] maps;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String[] getAttributes() {
        return attributes;
    }
    public void setAttributes(String[] attributes) {
        this.attributes = attributes;
    }
    public int getNum() {
        return num;
    }
    public void setNum(int num) {
        this.num = num;
    }
    public int[] getMap() {
        return map;
    }
    public void setMap(int[] map) {
        this.map = map;
    }
    public int[][] getMaps() {
        return maps;
    }
    public void setMaps(int[][] maps) {
        this.maps = maps;
    }
}

生成的JavaBean派生类

package collection;

import java.util.Optional;
import java.lang.String;

public class TestBeanOpt extends TestBean {
    public Optional<String> optName() {
        return Optional.ofNullable(getName());
    }
    public Optional<String[]> optAttributes() {
        return Optional.ofNullable(getAttributes());
    }
    public Optional<int[]> optMap() {
        return Optional.ofNullable(getMap());
    }
    public Optional<int[][]> optMaps() {
        return Optional.ofNullable(getMaps());
    }
}

工具代码,使用方法见注释

package collection;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;

/**
 * 功能描述:自动生成带Optional方法的派生类 <br/>
 * 注意事项:不会生成getClass名称的opt方法,不会生成返回8种基本类型的方法<br/>
 * 创建:JRZ<br/>
 * 创建时间:2017/9/13 16:01<br/>
 */
public class OptionalClassBuilder {
    private String filePath;
    private List<Class<?>> list = new ArrayList<>();
    private String packageName;
    private String className;
    private Set<String> importNames = new HashSet<>();
    private List<MethodCell> methodCells = new ArrayList<>();
    private Set<String> filterMethodSet;
    private Set<String> filterReturnSet;
    private String view;

    /**
     * 构造 Optional对象返回类 生成器<br/>
     * 使用这个结构的原因是,能力有限,做不出文本解析器,只能曲线使用字节码对象实现<br/>
     * 如果我要对cn.es.bean包下的文件进行opt返回值加强,那就需要将rootPath设置为java编译后class根路径<br/>
     * 就是不需要包含/cn/es/bean的路径
     *
     * @param filePath    文件生成位置
     * @param rootPath    目标项目class文件所在的根路径
     * @param packageName 要对哪个包下的class文件进行生成
     * @throws Exception 发生异常时
     */
    public OptionalClassBuilder(String filePath, String rootPath, String packageName) throws Exception {
        this.filePath = filePath;
        Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
        method.setAccessible(true);//开启访问权限
        URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
        //将目标环境根路径加入运行环境
        method.invoke(classLoader, new File(rootPath).toURI().toURL());

        File dir = new File(rootPath + "/" + packageName.replace('.', File.separatorChar));
        File[] files = dir.listFiles();
        if (files == null) {
            throw new RuntimeException("目录不包含java文件");
        }
        for (File file : files) {
            String fileName = file.getName();
            //不是class文件,或者是已经生成的class文件
            if (!fileName.endsWith(".class") || fileName.endsWith("Opt.class")) {
                continue;
            }
            // 文件名称
            String className = file.getAbsolutePath();
            className = className.substring(rootPath.length() + 1, className.length() - 6);
            className = className.replace(File.separatorChar, '.');
            // 加载Class类
            Class<?> cls = Class.forName(className);
            list.add(cls);
        }
    }

    /**
     * 构造 Optional对象返回类 生成器<br/>
     * 推荐使用这种方法,这种方法是可控的,出错几率也小
     *
     * @param filePath    文件生成位置
     * @param packageName 包名
     * @param cs          要构造文件的字节码对象数组
     */
    public OptionalClassBuilder(String filePath, String packageName, Class... cs) {
        this.filePath = filePath;
        this.packageName = packageName;
        Arrays.stream(cs).forEach(list::add);
    }

    /**
     * 执行文件生成
     */
    public void build() {
        for (Class<?> cls : list) {
            init();
            parserObject(cls);
            viewClass();
            saveFile();
        }
    }

    /**
     * 初始化
     */
    private void init() {
        importNames.clear();
        importNames.add("java.util.Optional");
        methodCells.clear();
        //构造符合get条件,但不想生成opt的方法名列表
        if (filterMethodSet == null) {
            filterMethodSet = new HashSet<>();
            filterMethodSet.add("getClass");
        }
        //对以下类型的返回值,不进行方法生成,不导包
        if (filterReturnSet == null) {
            filterReturnSet = new HashSet<>();
            filterReturnSet.add("boolean");
            filterReturnSet.add("byte");
            filterReturnSet.add("short");
            filterReturnSet.add("char");
            filterReturnSet.add("int");
            filterReturnSet.add("float");
            filterReturnSet.add("long");
            filterReturnSet.add("double");
        }
    }

    /**
     * 将对象解析为中间态
     *
     * @param cls 要解析的字节码对象
     */
    private void parserObject(Class<?> cls) {
        Package pag = cls.getPackage();
        if (pag != null) {
            packageName = pag.getName();
        }
        className = cls.getSimpleName();
        Method[] methods = cls.getMethods();
        for (Method method : methods) {
            //输入参数不为空,去掉
            if (method.getParameterCount() > 0) {
                continue;
            }
            //过滤以get为方法名开头,去掉(boolean类型,将不会返回null,不支持is;Boolean是get)
            String name = method.getName();
            if (!name.startsWith("get")) {
                continue;
            }
            //过滤一些自定义的方法,例如:getClass
            if (filterMethodSet.contains(name)) {
                continue;
            }
            //过滤基本返回类型
            Class<?> returnType = method.getReturnType();
            if (filterReturnSet.contains(returnType.getSimpleName())) {
                continue;
            }
            //返回类型是数组的情况
            if (returnType.isArray()) {
                Class<?> componentType = returnType;
                //循环处理多维数组
                while (componentType.isArray()) {
                    componentType = componentType.getComponentType();
                }
                //不是基本类型,加入导包
                if (!filterReturnSet.contains(componentType.getSimpleName())) {
                    importNames.add(componentType.getName());
                }
            } else {
                //是对象类型,直接加入导包
                importNames.add(returnType.getName());
            }
            MethodCell methodCell = new MethodCell();
            methodCell.returnValue = returnType.getSimpleName();
            methodCell.name = method.getName();
            methodCells.add(methodCell);
        }
    }

    /**
     * 将中间态解析为文本
     */
    private void viewClass() {
        StringBuilder sb = new StringBuilder();
        //解析包名
        sb.append("package ").append(packageName).append(";\n");
        sb.append("\n");
        //解析导入的包
        for (String name : importNames) {
            sb.append("import ").append(name).append(";\n");
        }
        sb.append("\n");
        //解析类名,并继承原Bean对象
        sb.append("public class ").append(className).append("Opt extends ").append(className).append(" {\n");
        //解析方法
        for (MethodCell cell : methodCells) {
            sb.append("\n");
            sb.append("    public Optional<").append(cell.returnValue).append("> ")
                    .append(cell.name.replaceFirst("get", "opt")).append("() {\n");
            sb.append("        return Optional.ofNullable(").append(cell.name).append("());\n");
            sb.append("    }\n");
        }
        sb.append("\n}\n");
        view = sb.toString();
    }

    /**
     * 将文本保存
     */
    private void saveFile() {
        File file = new File(filePath + "/" + className + "Opt.java");
        //同名文件已存在时,不覆盖
        if (file.exists()) {
            System.err.println("同名文件已存在,请手动处理:file=" + file.getAbsolutePath());
            return;
        }
        try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)))) {
            bw.write(view);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("生成文件:" + file.getName());
    }

    private class MethodCell {
        private String returnValue;
        private String name;
    }
}

使用方法,
* @param filePath 文件生成位置,直接到包就好
* 例如 F:\Workspace\IdeaProjects\Scala\src\main\java\bean
* @param packageName 包名,就是上面目录下文件的包
* 例如 bean
* @param cs 要构造文件的字节码对象数组
* 最简单的方式,new Object().getClass()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值