需求背景
因业务需要,公司将某项目改造,故需要将业务逻辑层service实现类中所有的私有private方法改为public,以便在项目打包成jar包后,供子类复用父类的私有方法。考虑到业务类比较多,手动改比较麻烦,现采用ASM字节码操作技术编写maven插件,利用插件在项目编译compile阶段进行修改。
maven 插件开发
新建插件项目
1、将项目的pom文件打包方式配置为如下:
<packaging>maven-plugin</packaging>
2、新增maven依赖
<properties>
<maven-plugin-annotations.version>3.5.2</maven-plugin-annotations.version>
<maven-project.version>2.2.1</maven-project.version>
<asm.version>9.0</asm.version>
<hutool-all.version>5.8.0</hutool-all.version>
<maven-plugin-api.version>3.5.2</maven-plugin-api.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>${maven-plugin-api.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>${maven-plugin-annotations.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-project</artifactId>
<version>${maven-project.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool-all.version}</version>
</dependency>
</dependencies>
3、打包plugin引入如下
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
<version>3.5</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
4、插件代码开发
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
@Mojo(name = "modify", defaultPhase = LifecyclePhase.COMPILE)
public class PluginAware extends AbstractMojo {
// 入参输出目录,${project.build.directory} 表示采用maven的默认配置,为项目下的target目录
@Parameter(name = "output", defaultValue = "${project.build.directory}")
private File output;
public void execute() {
File f = output;
if (!f.exists()) {
f.mkdirs();
}
try {
insertPile(f);
} catch (Exception e) {
e.printStackTrace();
}
}
private void insertPile(File root) throws IOException {
if (root.isDirectory()) {
for (File file : root.listFiles()) {
insertPile(file);
}
}
String className = root.getName();
if (filter(className)) {
FileOutputStream fos = null;
try {
final byte[] data = doInsertPile(root);
fos = new FileOutputStream(root);
fos.write(data);
fos.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
fos.close();
}
}
}
}
private byte[] doInsertPile(File file) {
try {
ClassReader cr = new ClassReader(new FileInputStream(file));
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cr.accept(new MethodClassVisitor(cw), ClassReader.SKIP_DEBUG);
return cw.toByteArray();
} catch (Exception e) {
e.printStackTrace();
return new byte[0];
}
}
/**
* 只处理业务逻辑层的的类
* @param className 类名
* @return true or false
*/
private boolean filter(String className) {
String suffix = "ServiceImpl";
return className.contains(suffix) && className.length() > suffix.length();
}
}
import cn.hutool.core.util.StrUtil;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
public class MethodClassVisitor extends ClassVisitor {
/**
* 构造器
*/
private static final String CONSTRUCTOR = "<init>";
public MethodClassVisitor(ClassVisitor classVisitor) {
super(Opcodes.ASM9, classVisitor);
}
/**
* 除了构造方法外,将所有带有private修饰符的方法改为public修饰符
*/
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
// 过滤掉构造器方法
if (StrUtil.equals(name, CONSTRUCTOR)) {
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
if (access == ACC_PRIVATE) {
access = ACC_PUBLIC;
}
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
}
使用插件
1、将插件打包安装到本地
切换到项目根目录,执行
mvn clean install
2、在项目中引入已打包好的插件
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>{输入你项目的插件版本}</version>
<configuration>
<source>8</source>
<target>8</target>
<encoding>utf8</encoding>
</configuration>
</plugin>
<!-- 这是你编写的插件 -->
<plugin>
<groupId>com.xxxx.framework.plugin</groupId>
<artifactId>method-modify-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<executions>
<execution>
<goals>
<!-- 执行目标 需要与 PluginAware 类上name参数保持一致 -->
<goal>modify</goal>
</goals>
<!-- 执行这个目标所在的生命周期 -->
<phase>compile</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
相关文章参考连接
ASM字节码插桩技术参考1
ASM字节码插桩技术参考2
maven 内置变量说明参考
maven插件开发参考