在Java 语言中,织入切面分为:编译期织入(CTW: Compile-Time Weaving)、类加载期织入(LTW: Load-Time Weaving)和运行期织入(JDK代理和CGLIB)。编译期织入是指在Java编译期,将切面织入到类字节码中;而类加载期织入则指在类字节码加载到JVM时,织入切面;运行期织入则是采用CGLIB工具或JDK动态代理进行切面的织入。
什么是 Java Agent
Java Agent 又叫做 Java 探针,从JDK1.5 开始引入,可在类加载前修改class字节码。
Jvm启动参数 -javaagent 参数 俗称java探针技术(Premain方式)
使用方法:-javaagent:xxx.jar, 在main方法执行前查找jar中MANIFEST.MF文件的Premain-Class配置项,Premain-Class对应一个java类,然后执行方法premain(String options, Instrumentation instrumentation) 和premain(String options),如果同时存在,执行前者。
示例说明:
一个普通业务类:
package org.aop.service;
public class UserService {
public void say() {
System.out.println("Hello World!!");
}
}
Premain-Class类:CustomPremainAgent,方法premain只执行一次
package org.aop.agent;
import java.lang.instrument.Instrumentation;
public class CustomPremainAgent {
public static void premain(String agentArgs, Instrumentation inst) {
if (inst == null) {
throw new UnsupportedOperationException("JDK5才开始支持!!! preMain -javaagent");
}
System.out.println("Running==>premain(String agentArgs, Instrumentation inst)");
System.out.println("agentArgs : " + agentArgs);
//加入自定义转换器
inst.addTransformer(new CustomClassFileTransformer(), true);
}
public static void premain(String agentArgs) {
System.out.println("Running==>premain(String agentArgs)");
}
}
Instrumentation:premain方法参数,由jvm虚拟机自动传入,class字节码载入jvm时触发执行transform方法(只执行一次)。
ClassFileTransformer类:CustomPremainAgent,拦截UserService类,利用javassist工具修改UserService的class字节码,修改say()方法添加切面功能
package org.aop.agent;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
public class CustomClassFileTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (className.equals("org/aop/service/UserService")) {
ClassPool classPool = ClassPool.getDefault();
try {
CtClass ctClass = classPool.get("org.aop.service.UserService");
CtMethod ctMethod = ctClass.getDeclaredMethod("say");
ctMethod.insertBefore("System.out.println(\"-javassist-- before say() ---\");");
ctMethod.insertAfter("System.out.println(\"-javassist-- after say() ---\");");
ctClass.detach();
return ctClass.toBytecode();
} catch (Exception e) {
e.printStackTrace();
}
}
return new byte[0];
}
}
MANIFEST.MF:在类路径META-INF下创建MANIFEST.MF文件
Manifest-Version: 1.0
Premain-Class: org.aop.agent.CustomPremainAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Class-Path: javassist-3.20.0-GA.jar
Build-Jdk-Spec: 1.8
Created-By: Maven Jar Plugin 3.2.0
然后将CustomPremainAgent.java、CustomClassFileTransformer.java、META-INF/MANIFEST.MF编译后打包.
如果用maven打包可以不用手动创建MANIFEST.MF文件
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<includes>
<include>org/aop/agent/CustomPremainAgent.class</include>
<include>org/aop/agent/CustomClassFileTransformer.class</include>
</includes>
<archive>
<!--自动添加META-INF/MANIFEST.MF -->
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<Premain-Class>org.aop.agent.CustomPremainAgent</Premain-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
最后打包路径:D:/workspace/demo/target/user-service-agent.jar
测试代码:
public static void main(String[] args) { UserService service = new UserService(); service.say(); }
在vm参数中加入:-javaagent:D:/workspace/demo/target/user-service-agent.jar
运行结果:
从JDK6开始,引入一种新的方式:Agentmain方式.
Premain只能在类加载之前修改字节码,类加载之后无能为力。而Agentmain恰恰可以弥补这缺点。Agentmain 可以在类加载之后再次加载这个类。
使用方法:VirtualMachine.loadAgent("XXX.jar", agentArgs), 在main方法执行中查找jar中MANIFEST.MF文件的Agent-Class配置项,Agent-Class对应一个java类,然后执行方法agentmain(String options, Instrumentation instrumentation) 和agentmain(String options),如果同时存在,执行前者。
示例说明:
Agent-Class类:CustomAgentmainAgent,方法agentmain只执行一次
package org.aop.agent;
import org.aop.service.UserService;
import java.lang.instrument.Instrumentation;
public class CustomAgentmainAgent {
public static void agentmain(String agentArgs, Instrumentation inst) {
if (inst == null) {
throw new UnsupportedOperationException("JDK6才开始支持!!! agentmain -javaagent");
}
System.out.println("Running==>agentmain(String agentArgs, Instrumentation inst)");
System.out.println("agentArgs : " + agentArgs);
//加入自定义转换器
inst.addTransformer(new CustomClassFileTransformer(), true);
try {
//允许修改字节码
inst.retransformClasses(UserService.class);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void agentmain(String agentArgs) {
System.out.println("Running==>agentmain(String agentArgs)");
}
}
MANIFEST.MF:在类路径META-INF下创建MANIFEST.MF文件
Manifest-Version: 1.0
Agent-Class: org.aop.agent.CustomAgentmainAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Class-Path: javassist-3.20.0-GA.jar
Build-Jdk-Spec: 1.8
Created-By: Maven Jar Plugin 3.2.0
然后将CustomAgentmainAgent .java、CustomClassFileTransformer.java、META-INF/MANIFEST.MF编译后打包.
最后打包路径:D:/workspace/demo/target/user-service-agent.jar
测试代码:
import com.sun.tools.attach.VirtualMachine;
import org.aop.service.UserService;
import java.lang.management.ManagementFactory;
import java.util.concurrent.TimeUnit;
public class AgentmainTest {
public static void main(String[] args) throws Exception {
taskThread.start();
try {
TimeUnit.SECONDS.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
String pid = jvmPid();
System.out.println("进程pid==>" + pid);
agent(pid);
}
/**
* 获取jvm进程ID
* 还可以通过jdk内置命令:jps -l 获取jvm进程ID
* @return
*/
private static String jvmPid() {
String thisJvmName = ManagementFactory.getRuntimeMXBean().getName();
String thisJvmPid = thisJvmName.split("@")[0];
return thisJvmPid;
}
/**
* 模拟运行中的系统
*/
private static Thread taskThread = new Thread() {
@Override
public void run() {
while (true) {
try {
TimeUnit.SECONDS.sleep(3);
UserService service = new UserService();
service.say();
System.out.println("\n");
} catch (Exception e) {}
}
}
};
/**
* agent attach
*
* @param pid
*/
public static void agent(String pid) {
try {
System.out.println("=========准备增强类=========");
VirtualMachine vm = VirtualMachine.attach(pid);
vm.loadAgent("D:/workspace/demo/target/user-service-agent.jar", "org.aop.service.UserService");
vm.detach();
} catch (Exception e) {
e.printStackTrace();
}
}
}
编译时,VirtualMachine可能无法编译,可以pom.xml添加如下依赖解决
<dependency> <groupId>jdk.tools</groupId> <artifactId>jdk.tools</artifactId> <version>1.8</version> <scope>system</scope> <!--<systemPath>${java.home}/lib/tools.jar</systemPath>--> <systemPath>C:/Program Files/Java/jdk-1.8/lib/tools.jar</systemPath> </dependency>
运行结果:
什么是 AspectJ
AspectJ是一个面向切面的框架(AOP框架)。利用 -javaagent 实现的类加载期切面织入(LTW: Load-Time Weaving)
官网地址: Intro to AspectJ | Baeldung
源码地址:GitHub - eclipse-aspectj/aspectj
查看AspectJ jar包中的MANIFEST.MF可得知AspectJ的Premain-Class是 org.aspectj.weaver.loadtime.Agent
AspectJ从1.8.7版本开始支持Agentmain方式。
简单demo示例:
业务类:
package org.aop.service;
public class UserService {
public void say() {
System.out.println("Hello World!!");
}
}
增强类:
package org.aop.aspectj;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class AspectConfig {
@Pointcut("execution(* org.aop.service..*.*(..))")
public void callAt() {
}
@Around("callAt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("Around==>before");
Object object = pjp.proceed();
System.out.println("Around==>after");
return object;
}
public static AspectConfig aspectOf() {
return new AspectConfig();
}
}
META-INF新增aop.xml文件
<aspectj>
<aspects>
<aspect name="org.aop.aspectj.AspectConfig"/>
<weaver options="-verbose -showWeaveInfo">
<include within="org.aop.service.*"/>
</weaver>
</aspects>
</aspectj>
测试代码:
package org.aop;
import org.aop.service.UserService;
/*
AspectJ官网地址:https://www.baeldung.com/aspectj
在类路径META-INF下创建 aop.xml文件
内容:
<aspectj>
<aspects>
<aspect name="org.spring.aop.aspectj.AspectConfig"/>
<weaver options="-verbose -showWeaveInfo">
<include within="org.spring.aop.service.*"/>
</weaver>
</aspects>
</aspectj>
jvm启动参数:-javaagent:C:/Users/用户目录/.m2/repository/org/aspectj/aspectjweaver/1.7.4/aspectjweaver-1.7.4.jar
*/
public class AspectJMain {
public static void main(String[] args) {
UserService service = new UserService();
service.say();
}
}
执行测试:
添加vm参数:-javaagent:XXXXX/aspectjweaver-1.7.4.jar
可以重写AspectJ的Premain-Class和ClassFileTransformer,将修改后的类字节码写入文件,查看生成情况。
package org.aop.agent;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
public class AspectAgent {
/**
* The instrumentation instance
*/
private static Instrumentation s_instrumentation;
/**
* The ClassFileTransformer wrapping the weaver
*/
private static ClassFileTransformer s_transformer = new ClassPreProcessorAgentAdapter();
/**
* JSR-163 preMain Agent entry method
*
* @param options
* @param instrumentation
*/
public static void premain(String options, Instrumentation instrumentation) {
/* Handle duplicate agents */
if (s_instrumentation != null) {
return;
}
s_instrumentation = instrumentation;
s_instrumentation.addTransformer(s_transformer);
}
/**
* Returns the Instrumentation system level instance
*/
public static Instrumentation getInstrumentation() {
if (s_instrumentation == null) {
throw new UnsupportedOperationException("Java 5 was not started with preMain -javaagent for AspectJ");
}
return s_instrumentation;
}
}
package org.aop.agent;
import org.aspectj.weaver.loadtime.Aj;
import org.aspectj.weaver.loadtime.ClassPreProcessor;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.lang.instrument.ClassFileTransformer;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.ProtectionDomain;
/**
* Java 1.5 adapter for class pre processor
*
* @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur</a>
*/
public class ClassPreProcessorAgentAdapter implements ClassFileTransformer {
/**
* Concrete preprocessor.
*/
private static ClassPreProcessor s_preProcessor;
static {
try {
s_preProcessor = new Aj();
s_preProcessor.initialize();
} catch (Exception e) {
throw new ExceptionInInitializerError("could not initialize JSR163 preprocessor due to: " + e.toString());
}
}
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] bytes) {
if (classBeingRedefined != null) {
System.err.println("INFO: (Enh120375): AspectJ attempting reweave of '" + className + "'");
}
byte[] newByte = s_preProcessor.preProcess(className, bytes, loader, protectionDomain);
if (className.equals("org/aop/service/UserService")) { //写入文件
this.writeFile(newByte);
}
return newByte;
}
//写入文件
private void writeFile(byte[] newByte) {
try {
String dirPath = System.getProperty("user.dir") + "/src/proxy";
Path outDirPath = Paths.get(dirPath);
Files.createDirectories(outDirPath);
Path outPath = Paths.get(dirPath + "/UserService$Proxy01.class");
Files.write(outPath, newByte, new OpenOption[0]);
} catch (Exception e) {
e.printStackTrace();
}
}
}
记得重新生成MANIFEST.MF配置内容,重新打包,修改 -javaagent:XXX/自己的.jar.