首先,什么是AOP?
AOP(Aspect Oriented Programming面向切面编程),它是对OOP(面向对象编程)的一种补充。在OOP中,我们把事物纵向抽象成一个个对象,而往往需要对这些对象进行一些横向操作(比如事务管理、日志记录、权限等)时,就需要AOP了。
Spring的AOP是基于JDK的动态代理和cglib动态代理实现的。JDK的动态代理是代理那些实现接口的类,而cglib动态代理是代理那些继承父类的类。
所谓的切面aspect,是由切入点pointcut和通知advice组成。pointcut是指需要拦截哪些类的哪些方法,advice是指在这些方法执行前后可以进行额外的操作。
先看看JDK的动态代理:
因为是代理实现接口的类,所以先定义一个接口和实现类:
public interface Hello {
String sayHello(String name);
}
public class HelloImpl implements Hello {
@Override
public String sayHello(String name) {
return name + "2333";
}
}
然后定义一个类,需要实现接口InvocationHandler
,而且持有代理的目标类的引用target,如下:
public class HelloProxy implements InvocationHandler {
private Object target;
public HelloProxy(Object target) {
this.target = target;
}
public Object getProxyBean() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object value = method.invoke(target, args);
return value;
}
}
最后写个测试类测试一下:
public class DynamicProxyTest {
public static void main(String[] args) {
Hello proxy = (Hello) new HelloProxy(new HelloImpl()).getProxyBean();
System.out.println(proxy.sayHello("Fuck"));
}
}
打印结果:Fuck2333
Java会动态生成一个代理类com.sun.proxy.$Proxy0
,通过反编译,可以看到$Proxy0
实现了Hello接口,持有HelloProxy的实例对象引用handler,而且会通过反射获取接口Hello的方法和参数,所以当执行proxy.sayHello("Fuck")
这行代码时,实际上调用的是$Proxy0
类中的sayHello
方法,该方法代码如下:
public final String sayHello(String string) {
try {
return (String)this.handler.invoke((Object)this, "sayHello", new Object[]{string});
}
catch (Error | RuntimeException v0) {
throw v0;
}
catch (Throwable var2_2) {
throw new UndeclaredThrowableException(var2_2);
}
}
所以最终调用的是HelloProxy
中的invoke
方法。
了解了动态代理之后,我们就可以实现一个简单的AOP工具了。
- 我们得明确切入点和通知,切入点就是测试类中bean调用的方法,通知如下:
public interface Advice {
void beforeMethod(Method method);
void afterMethod(Method method);
}
public class MyAdvice implements Advice {
@Override
public void beforeMethod(Method method) {
System.out.println("beforeMethod");
}
@Override
public void afterMethod(Method method) {
System.out.println("afterMethod");
}
}
- 创建一个代理工厂类,需持有代理目标类和通知Advice引用,对外提供getProxyBean方法获取代理类实例,如下:
public class ProxyBeanFactory implements InvocationHandler {
private Object target;
private Advice advice;
public Object getProxyBean() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//invoke方法执行前
advice.beforeMethod(method);
Object value = method.invoke(target, args);
//invoke方法执行后
advice.afterMethod(method);
return value;
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
public Advice getAdvice() {
return advice;
}
public void setAdvice(Advice advice) {
this.advice = advice;
}
}
- 创建一个BeanFactory类,对外提供getBean方法获取bean实例,配置的bean不一定是代理工厂类。
public class BeanFactory {
static Properties props = new Properties();
public BeanFactory(InputStream is) {
try {
props.load(is);
} catch (IOException e) {
e.printStackTrace();
}
}
public Object getBean(String name) {
Object bean = null;
try {
bean = Class.forName(props.get(name).toString()).newInstance();
if (bean instanceof ProxyBeanFactory) {
// 是代理工厂类
ProxyBeanFactory proxyBeanFactory = (ProxyBeanFactory) bean;
Object target = Class.forName(props.get(name + ".target").toString()).newInstance();
Advice advice = (Advice) Class.forName(props.get(name + ".advice").toString()).newInstance();
proxyBeanFactory.setTarget(target);
proxyBeanFactory.setAdvice(advice);
// 获取代理实例
bean = proxyBeanFactory.getProxyBean();
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return bean;
}
}
- 创建一个config.properties配置文件,配置Bean类、代理目标类和通知类。可以配置proxy来决定是否走代理。
proxy=cn.tl.aop.ProxyBeanFactory
#proxy=java.util.ArrayList
proxy.target=java.util.ArrayList
proxy.advice=cn.tl.aop.MyAdvice
测试类如下:
public class AopTest {
public static void main(String[] args) {
InputStream is = AopTest.class.getResourceAsStream("config.properties");
BeanFactory beanFactory = new BeanFactory(is);
Object bean = beanFactory.getBean("proxy");
List list = (List) bean;
list.add(233);
}
}
打印结果:
beforeMethod
afterMethod
说明通知Advice起作用了,当然这是一种很简略的实现方式。
以上。
网友评论