为了避免文章过长,针对AOP的理解分为两篇,第一篇介绍Spring AOP的具体实现,本篇将从AOP在Java中的具体实现记录。
Java实现AOP的底层原理
AOP的实现方式主要有三种:
第一种,JVM本身提供了动态代理组件,可以通过它任意对象的代理模式,在处理代理的过程中可以插入切面的逻辑1
首先编写Operator和OperatorImpl
package zmqc.iceyung.aoptool;
public interface Operator {
int getAllStudentNum();
}
package zmqc.iceyung.aoptool;
public class OperatorImpl implements Operator {
@Override
public int getAllStudentNum() {
return 2;
}
}
整个程序的运行过程中,原始为:
Operator operator = new OperatorImpl();
operator.getAllStudentNum()
若想将该类的操作添加我们想要的内容,不去改变其编码,而是采用动态代理的方式,在运行该业务代码的时候执行我们想要的操作。
首先创建我们自己的InvocationHandler,我们想添加的具体操作,在该类的invoke中进行:
package zmqc.iceyung.aoptool;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyHandler implements InvocationHandler {
private Object targetObject;
public Object getTargetObject() {
return targetObject;
}
public void setTargetObject(Object targetObject) {
this.targetObject = targetObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(targetObject ,args);
System.out.println("invoke " + targetObject.getClass());
return result;
}
}
创建代理工厂:
package zmqc.iceyung.aoptool;
import java.lang.reflect.Proxy;
public class ProxyFactory {
public static Object getProxy(Object targetObject,
MyHandler handlers) {
Object proxyObject = null;
if (handlers != null) {
//传入目标object
proxyObject = targetObject;
handlers.setTargetObject(proxyObject);
proxyObject = Proxy.newProxyInstance(targetObject.getClass()
.getClassLoader(), targetObject.getClass()
.getInterfaces(), handlers);
return proxyObject;
} else {
return targetObject;
}
}
}
测试:
package zmqc.iceyung.aoptool;
public class ProxyTest {
public static void main(String[] args){
//声明业务类实例
Operator operator = new OperatorImpl();
//使用代理工厂生成业务类的代理实例
Operator operatorProxy = (Operator)ProxyFactory.getProxy(operator,new MyHandler());
//运行
int num = operatorProxy.getAllStudentNum();
System.out.println("student num is "+ num);
}
}
运行结果为:
invoke class zmqc.iceyung.aoptool.OperatorImpl
student num is 2
若要实现doAfer,doBefor等参考1
第二种,对Java字节码进行重新编译,将切面插入字节码的某些点和面上,可以使用cglib库来实现
package zmqc.iceyung.aoptool;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibTest implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz){
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("前置代理");
//通过代理类调用父类中的方法
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("后置代理");
return result;
}
public static void main(String[] args) {
CglibTest proxy = new CglibTest();
//通过生成子类的方式创建代理类
OperatorImpl operator = (OperatorImpl)proxy.getProxy(OperatorImpl.class);
int num = operator.getAllStudentNum();
System.out.println("学生人数为:"+ num);
}
}
输出为:
前置代理
后置代理
学生人数为:2
operator:class zmqc.iceyung.aoptool.OperatorImpl$$EnhancerByCGLIB$$268278e0
可以看到,此时的operator实例为EnhancerByCGLIB,OperatorImpl为其父类,等于是增强了OperatorImpl的功能,添加了一些功能代码。2
第三种,定制类加载器,在类加载的时,对字节码进行补充,在字节码中插入切面。Java的agent就是在加载类字节码的时候,通过增加切面来实现AOP的3 4
根据引文中的代码进行编写:
下面为测试类,用来使用我们自定义的加载器加载,注意将该类放到某个目录中,用javac命令编译,切乎直接放到项目中,这样项目若是编译了该类,默认可能直接使用的是ClassLoader,即已经加载过了,不会再重新加载
public class Test {
public int getAllStudentNum(){
System.out.println("getAllStudentNum");
return 2;
}
}
自定义加载器:
package zmqc.iceyung.aoptool;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class MyClassLoader extends ClassLoader {
//加载类的目录
private String classpath;
public MyClassLoader(String classpath) {
this.classpath = classpath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
//获取类的字节码
byte [] classDate = getDate(name);
if(classDate!=null){
//defineClass方法将字节码转化为类
return defineClass(name,classDate,0,classDate.length);
}
} catch (IOException e) {
e.printStackTrace();
}
return super.findClass(name);
}
//返回类的字节码
private byte[] getDate(String className) throws IOException{
InputStream in = null;
ByteArrayOutputStream out = null;
//拼接字节码的地址和名称
String path = classpath + File.separatorChar +
className.replace('.',File.separatorChar)+".class";
try {
in = new FileInputStream(path);
out = new ByteArrayOutputStream();
byte[] buffer = new byte[2048];
int len = 0;
while((len=in.read(buffer))!=-1){
out.write(buffer,0,len);
}
return out.toByteArray();
}
catch (FileNotFoundException e) {
e.printStackTrace();
}
finally{
in.close();
out.close();
}
return null;
}
}
测试加载器的加载效果
package zmqc.iceyung.aoptool;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException,
SecurityException, IllegalArgumentException, InvocationTargetException {
//自定义类加载器的加载路径
MyClassLoader myClassLoader = new MyClassLoader("E:\\CodeLibrary\\Java\\iceyung");
//包名+类名
Class c = myClassLoader.loadClass("Test");
if(c!=null){
Object obj = c.newInstance();
Method method = c.getMethod("getAllStudentNum", null);
method.invoke(obj, null);
System.out.println(c.getClassLoader().toString());
}
}
}
最终的运行结果:
getAllStudentNum
zmqc.iceyung.aoptool.MyClassLoader@27c170f0
可以看出加载器已经改成我们自己的了,那么如何在加载这个类的时候,去自定义一些我们的切面程序呢?
可以通过在字节码加载的时候进行添加,Java的agent也是类似的技术,本文已经比较多了,若有时间将继续探讨关于字节码增强的内容。详情可以看引文3和5
此外还有AspectJ,其是静态代理的增强。所谓的静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强
例如:ajc -d . Hello.java TxAspect.aj 详细见引文6
本文的代码在Spring AOP的具体实现中进行扩充,地址为:
https://gitee.com/iceyung/SpringAOPDemo.git /aoptool
手把手教你用Java实现AOP, https://blog.csdn.net/GarfieldEr007/article/details/80615537,https://github.com/debjava/aopusingjdkdynamicproxy ↩︎ ↩︎
Java实现AOP的两种方式, https://blog.csdn.net/feigeswjtu/article/details/78741245 ↩︎
Java探针-Java Agent技术,https://www.cnblogs.com/aspirant/p/8796974.html ↩︎ ↩︎
Java类加载机制及自定义加载器,https://www.cnblogs.com/gdpuzxs/p/7044963.html ↩︎
Java学习之javassist,https://www.cnblogs.com/sunfie/p/5154246.html ↩︎
Java AOP的底层原理,https://blog.csdn.net/spring_lws/article/details/81031564 ↩︎