AOP(Aspect-Oriented-Programming,面向切面编程)是面向对象编程的一种补充,可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。它的主要实现技术有spring aop和aspectJ。
解释看起来比较抽象,我们先通过传统的方式模拟实现日志记录功能
/*计算接口*/
public interface Operation {
double calculate(double num1,double num2);
}
/*实现计算接口的加法类,还有减乘除三个方法这里不再赘述*/
public class Add implements Operation {
public double calculate(double num1, double num2) {
System.out.println("正在写计算之前日志");
double rtn=-1;
try {
rtn=num1+num2;//业务功能代码
System.out.println("正在写计算之后日志");
} catch (Exception e) {
System.out.println("正在写出现异常时的日志");
}
return rtn;
}
}
/*测试类*/
public class Main {
public static void main(String[] args) {
Operation operation=new Add();
double rtn=operation.calculate(8,9 );
System.out.println(rtn);
}
}
可以看出,日志的写入需要分别在各实现类中重复写入日志记录的代码,如果日志需求发生变化,必须修改所有的模块,但是实际的业务功能代码只有一句,这就导致核心业务逻辑变得复杂。
我们可以通过一种设计模式——代理模式,解决代码冗余。
/*计算接口*/
public interface Operation {
double calculate(double num1,double num2);
}
/*实现计算接口的加法类*/
public class Add implements Operation {
public double calculate(double num1, double num2) {
return num1+num2;
}
}
/*代理类*/
public class OperationProxy implements Operation {
Operation operation=null;
public OperationProxy(Operation operation) {
this.operation=operation;
}
@Override
public double calculate(double num1, double num2) {
System.out.println("正在写计算之前日志");
double num=-1;
try {
num=operation.calculate(num1, num2);//业务代码
System.out.println("正在写计算之后日志");
} catch (Exception e) {
System.out.println("正在写出现异常时的日志");
}
return num;
}
}
/*测试类*/
public class Main {
public static void main(String[] args) {
Operation add=new Add();
OperationProxy proxy=new OperationProxy(add);
double rtn=proxy.calculate(8, 9);
System.out.println(rtn);
}
}
但是这种方式要求代理类必须和它的代理目标有共同的接口,意思是这个代理类不能代理其它的。。。
/*计算接口*/
public interface Operation {
double calculate(double num1,double num2);
}
/*实现计算接口的加法类*/
public class Add implements Operation {
public double calculate(double num1, double num2) {
return num1+num2;
}
}
/**
* 动态代理
* 需要继承InvocationHandler接口
* @author yang
*
*/
public class DynamicProxy implements InvocationHandler {
private Object target;//被代理的对象,Operation
public DynamicProxy(Object target) {
this.target=target;
}
/*
* 反射调用该接口
* proxy:代理对象
* method:需要执行的方法
* args:需要执行的方法参数
* 返回方法的执行结果
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("正在写计算之前日志......");
Object rtn=-1;
try {
rtn=method.invoke(target, args);
System.out.println("正在写计算之后日志......");
} catch (Exception e) {
System.out.println("正在写出现异常时的日志......");
}
return rtn;
}
/*获取代理对象*/
public static Object createProxy(Object target) {
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),new DynamicProxy(target));
}
}
/*测试类*/
public class Main {
public static void main(String[] args) {
Operation add=new Add();
Operation op=(Operation) DynamicProxy.createProxy(add);
double num=op.calculate(8, 9);
System.out.println(num);
}
}
将完成业务功能的代码与辅助功能(记录日志)分开,业务逻辑更加清晰
spring-aspect(自动依赖aspectjweaver)和aopalliance
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<aop:aspectj-autoproxy>aop:aspectj-autoproxy>
<context:component-scan base-package="day0915.cal">context:component-scan>
beans>
@Component
public class Add implements Operation {
public double calculate(double num1, double num2) {
return num1+num2;
}
}
/**
* 切面类
* Aspect注解表示该类为切面类
* @author yang
*
*/
@Component
@Aspect
public class OperationAspect {
//前置通知
@Before("execution(* *.calculate(..))")
public void before(){
System.out.println("业务执行之前执行的方法")
}
}
/*测试类*/
public class Main {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("applictioncontext4.xml");
/*
* 这里使用了切面类,创建容器时new Add()还需要经过AOP
* 进入ioc容器的是实例类的动态代理类,而不是实例类
* 所以最好还是用id来获取
*/
// Operation operation=ac.getBean(Add.class);
Operation operation=(Operation) ac.getBean("add");//动态获取代理
double num=operation.calculate(8, 9);
System.out.println(num);
}
}
可见每个事务逻辑位于一个位置,代码不分散,便于维护和升级,并且对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AspectJ支持的五种通知注解:
1. @Before 前置通知,在业务代码执行前执行
2. @After 后置通知,在业务代码执行之后执行,不管业务代码是否正常执行(类似异常捕获时的finally,不管try中的方法是否异常都会执行)
3. @AfterRetruning 返回通知,业务代码执行成功之后才能执行
4. @AfterThrowing 异常通知,业务逻辑抛出异常之后执行
5. @Around 环绕通知,围绕着方法执行,包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。
spring用代理类将切面包裹住,然后将它们织入到ioc容器的bean中,通过动态代理的方式将代理类伪装成目标类(就是上面例子中的add类),代理类会截取对目标类中方法的调用,然后先执行切面,然后再把调用转发给目标类的bean。
要实现这个伪装还要躲避JVM的检查,有两种方式: