Aop面向切面编程思想
我们都知道Java是一种面向对象的编程语言,强调用类和对象来实现一些功能,强调对象之间的组装思想,强调类与类之间封装、多态和继承的关系。
而在一些主业务逻辑之外,还有一些各类通用的业务代码,并不专属于某个类的功能,因此可能散落在工程的各个角落。这些功能可以做成切面,在任何需要业务增强的时候,插入到业务代码的前后,即可。
那么切面的插入是怎么实现的
切面的插入是说,要在某个对象执行某个方法的时候,在特定的点上插入增强功能,像是改变了这个方法,但实际上并没有改变原来的方法,这就是通过代理来实现的。
静态代理和动态代理
一般静态代理和被代理的普通类实现了同样的接口,但是静态代理可以添加额外的功能代码。但是静态代理是编译之前就写好的类。编译之后不能再变,要再添加业务代码需要重新编译。
动态代理也是为了实现业务代码的增强,但是,是在运行过程中代理一个具体类的实例,通过反射的思想,获取其class字节码,获取构造器,构造一个代理对象,并使用代理对象的方法,具体类的方法往往被代理类的方法增强。
生成动态代理的三个必要条件
看一下动态代理的生成方法
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)//调用器自己实现invoke
//newProxyInstance返回一个具体实例的代理类。
//具体过程:先获得接口的二进制字节码文件
Class<?> cl = getProxyClass0(loader, intfs);
//然后获取代理类的构造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
//然后返回一个代理实例
return cons.newInstance(new Object[]{h});
第一个必要条件是classLoader,第二个必要条件是接口class,第三个条件是InvocationHandler。
其中,InvocationHandler接口只有一个方法,就是invoke。实现了这个这个接口的类就可以作为某个对象的handler,当那个对象执行某个方法的时候,将被handler接手。
生成自己的动态代理
//首先得有一个Handler
//说明在对象执行invoke方法的时候,将会有哪些增强操作。
public class Handler implements InvocationHandler {
private Object target;
Handler(Object o){
super();
this.target=o;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("准备执行操作");
Object o=method.invoke(target,args);
System.out.println("操作执行完毕");
return o;
}
}
//然后需要有一个接口和具体实现类,用于被代理和增强
public interface Pser {
void show();
}
public class PserImpl implements Pser{
@Override
public void show(){
System.out.println("展示任务执行过程");
}
}
//最后写一个test类
public class Test {
public static void main(String[] args) {
Pser person=(Pser) Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(),
new Class[]{Pser.class},
new Handler(new PserImpl()));
person.show();
//person是一个代理实例
//这里的person已经不是普通对象Person了,而是代理后的对象person。
}
}
//看一下执行结果
准备执行操作
展示任务执行过程
操作执行完毕
为什么JdkDynamicAopProxy只能代理接口
我们可以看到,通过Proxy类的newProxyInstance方法生成的代理对象都是Proxy类的子类,根据Java单继承的原则,只能继承一个类,而实现多个接口,因此这里实现要代理的接口,对接口方法进行增强。
AOP的拦截器链
我们知道常用的增强方法有@After后置增强@Before前置增强@Around环绕增强。
为什么使用Before注解就能自动帮我们加到切点的前面呢?
这里因为AOP内部会获取一条拦截器链。(拦截器就是我们增强方法的实例化)
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
if (chain.isEmpty()) {
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
} else {
//这里会依次执行拦截器链的各个拦截器的方法。
MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
retVal = invocation.proceed();
}
看一下拦截器的执行过程
public Object proceed() throws Throwable {
//如果拦截器已经执行到最后一个,直接执行InvokeJoinPoint
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return this.invokeJoinpoint();
//底层就是method.invoke,直接使用反射调用方法
} else {
//说明还有拦截器等待执行
//拦截器链的机制,保证通知方法与目标方法的执行顺序;
Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice;
Class<?> targetClass = this.targetClass != null ? this.targetClass : this.method.getDeclaringClass();
//这一句选择执行拦截器的方法或者是执行下一个方法。
return dm.methodMatcher.matches(this.method, targetClass, this.arguments) ? dm.interceptor.invoke(this) : this.proceed();
} else {
return ((MethodInterceptor)interceptorOrInterceptionAdvice).invoke(this);
}
}
}
在看一下Before和After对应的类的invoke方法的实现,就能理解为什么一个在方法之前执行,一个在方法之后执行。
//AspectJAfterAdvice
public Object invoke(MethodInvocation mi) throws Throwable {
Object var2;
try {
//After是先执行业务方法
var2 = mi.proceed();
} finally {
//再执行advice方法。
this.invokeAdviceMethod(this.getJoinPointMatch(), (Object)null, (Throwable)null);
}
return var2;
}
//MethodBeforeAdviceInterceptor
public Object invoke(MethodInvocation mi) throws Throwable {
//先执行advice 的before回调
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() );
//再执行方法。
return mi.proceed();
}
最后说一下反射把
反射是再运行时获取对象的class、构造器的技术,一般在要求灵活性和扩展性较高的框架里面会用到反射技术,如果是普通Java项目,使用反射不如直接new来的快,但是new了的说明编译过了,不能变的。
利用反射获取对象的方法是:
获取class字节码、获取构造器、构造实例
String className = "charactor.Hero";
//类
Class pClass=Class.forName(className);
//构造器
Constructor c= pClass.getConstructor();
//通过构造器实例化
Hero h2= (Hero) c.newInstance();
h2.name="gareen";
System.out.println(h2);
使用反射调用类的方法
//使用反射对一个对象调用某个方法。
Method m = h.getClass().getMethod("setName", String.class);
// 对h对象,调用这个方法
m.invoke(h, "盖伦");
分享完啦~