一、为什么需要AOP
情景实例:假设你现在有一个计算类ComputerService实现了一些简单的加减乘除功能,你想在返回结果前输出一些提示性语句,因此你写了如下的代码:
ComputerService.java
public class ComputerService implements IComputerService {
public int add(int a, int b) {
System.out.println(this.getClass().getName()+":The add method begins.");
System.out.println(this.getClass().getName()+":Parameters of the add method: ["+a+","+b+"]");
return a+b;
}
public int div(int a, int b) {
System.out.println(this.getClass().getName()+":The div method begins.");
System.out.println(this.getClass().getName()+":Parameters of the div method: ["+a+","+b+"]");
return a/b;
}
}
你稍加思考似乎这些提示性语句太过繁琐,似乎有种方法可以简化ComputerService中的代码,这时你希望有个“人”如果能帮你先输出这些提示性语句就方便多了。你开始意识到,你需要使用AOP技术获得一个代理来帮你执行这些操作。
二、什么是AOP?
介绍:AOP(Aspect Oriented Programming 面向切面编程)是一种通过运行期动态代理实现代码复用的机制,是对传统OOP(Object Oriented Programming,面向对象编程 )的补充。目前,Aspect J是Java社区里最完整最流行的AOP框架,在Spring 2.0以上版本中可以通过Aspect J注解或基于XML配置AOP。
原理图:
三、使用Aspect J注解实现AOP(自动代理)
1、添加所需jar类库:
2、修改Spring配置文件
(1)向xml文件中添加<aop:aspectj-autoproxy></aop:aspectj-autoproxy>配置文件,其作用为如果创建目标对象的目标类中的方法与AspectJ切面中切入点表达式(execution注释)匹配,则自动为该目标对象生成动态代理对象。
(2)向xml文件中添加添加<context:component-scan base-package="com.test"></context:component-scan>配置文件,其作用为扫描指定包及其子包中有特定注释的类并为此类创建对象(详见博客——详述context:component-scan作用)。
<?xml version="1.0" encoding="UTF-8"?>
<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">
<context:component-scan base-package="com.jd"></context:component-scan>
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
</beans>
3、自定义一个@Aspect修饰的切面类并将其创建的对象保存于Spring IOC容器
ComputerAOP.java
@Aspect
@Component
public class ComputerAOP {
}
4、自定义增强方法(也称为通知方法),指有@Before、@AfterRunning、@AfterThrowing、@After或@Around注解修饰的Java方法
(1)@Before:目标方法执行前执行的方法
ComputerAOP.java
@Aspect
@Component
public class ComputerAOP {
//时机:目标方法执行前
@Before("execution(public int com.jd.computer.service.ComputerService.*(..))")
public void before(JoinPoint jp) {
Object [] args = jp.getArgs();//获取目标类中符合execution限定方法的参数
Signature signature = jp.getSignature();//获取方法名
String name = signature.getName();
System.out.println(this.getClass().getName()+":The "+name+" method begins.");
System.out.println(this.getClass().getName()+":Parameters of the "+name+" method: ["+args[0]+","+args[1]+"]");
}
}
(2)@After:目标方法执行完成后执行的方法
ComputerAOP.java
@Aspect
@Component
public class ComputerAOP {
//时机:目标方法执行完成;无论目标方法是否出现异常,都会执行
@After("execution(public int com.jd.computer.service.ComputerService.*(..))")
public void after(JoinPoint jp) {
Signature signature = jp.getSignature();//获取方法名
String name = signature.getName();
System.out.println(this.getClass().getName()+":The "+name+" method ends.");
}
}
(3)@AfterReturing:目标方法返回结果后执行
ComputerAOP.java
@Aspect
@Component
public class ComputerAOP {
//时机:目标方法返回结果;如果目标方法出现异常无返回数据,则该方法不执行
@AfterReturning(value = "execution(public int com.jd.computer.service.ComputerService.*(..))",returning="result")//returning指定获取目标方法return返回值的变量名
public void afterReturning(JoinPoint jp,Object result) {
Signature signature = jp.getSignature();//获取方法名
String name = signature.getName();
System.out.println(this.getClass().getName()+":Result of the "+name+" method:"+result);
}
}
(4)@AfterThrowing:目标方法返回异常后执行
ComputerAOP.java
@Aspect
@Component
public class ComputerAOP {
//时机:目标方法返回异常;
@AfterThrowing(value = "execution(public int com.jd.computer.service.ComputerService.*(..))",throwing="e")//returning指定获取目标方法return返回值的变量名
public void afterThrowing(JoinPoint jp,Exception e) {
System.out.println(e.getMessage());
}
}
(5)@Around:在@Around修饰的方法中可以实现@Before,@After,@AfterReturning和@AfterThrowing增强效果,可以实现动态代理全过程
ComputerAOP.java
@Aspect
@Component
public class ComputerAOP {
@Around("execution(public int com.jd.computer.service.ComputerService.*(..))")
public Object around(ProceedingJoinPoint pjp) {
Object [] args = pjp.getArgs();//传入目标方法参数
Signature signature = pjp.getSignature();//获取方法名
String name = signature.getName();
//目标方法执行前
System.out.println(this.getClass().getName()+":The "+name+" method begins.");
System.out.println(this.getClass().getName()+":Parameters of the "+name+" method: ["+args[0]+","+args[1]+"]");
try {
Object result = null;
try {
Object object = pjp.getTarget();//创建目标类对象
System.out.println(object.getClass().getName());
result = pjp.proceed();//调用目标方法并返回目标方法结果(没有此行代码将不会调用目标方法)
}finally {
//目标方法执行完成
System.out.println(this.getClass().getName()+":The "+name+" method ends.");
}
//目标方法返回结果
System.out.println(this.getClass().getName()+":Result of the "+name+" method:"+result);
return result;
} catch (Throwable e) {
System.out.println(e.getMessage());
}
return -1;
}
}
5、测试
我们在按上述方法创建Aspect切面类后,原来情景实例中的代码可简化为如下:
ComputerService.java
public class ComputerService implements IComputerService {
public int add(int a, int b) {
return a+b;
}
public int div(int a, int b) {
return a/b;
}
}
此时可以创建Test.java类来测试
Test.java
public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
IComputerService computerService = applicationContext.getBean(IComputerService.class);
System.out.println(computerService.getClass().getName());
System.out.println(computerService.div(1, 1));
applicationContext.close();
}
}
五、Pointcut注解(配置切点)
通过单独自定义一个@Pointcut注解修饰的空方法,通过该方法可以简化@Before,@After,@AfterReturning、@AfterThrowing和@Around注解中的切入点表达式
@Aspect
@Component
public class ComputerAOP {
@Pointcut("execution(public int com.jd.computer.service.ComputerService.*(..))")
public void t() {
}
@Before("t()")
public void before(JoinPoint jp) {
Object [] args = jp.getArgs();
Signature signature = jp.getSignature();
String name = signature.getName();
System.out.println(this.getClass().getName()+":The "+name+" method begins.");
System.out.println(this.getClass().getName()+":Parameters of the "+name+" method: ["+args[0]+","+args[1]+"]");
}
}
六、使用XML配置实现AOP(手动代理)
使用Spring框架的XML配置AOP,则不需要在AOP切面类添加注解。
MethodAOP.java
public class MethodAOP {
public void before(JoinPoint jp) {
//获取参数列表
Object [] args = jp.getArgs();
//获取方法名
Signature signature = jp.getSignature();
String name = signature.getName();
System.out.println("The "+name+" method begins.");
System.out.println("The div method params ["+args[0]+","+args[1]+"].");
}
}
application.xml
<?xml version="1.0" encoding="UTF-8"?>
<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">
<context:component-scan base-package="com.jd"></context:component-scan>
<!-- 装配AOP切面类Bean对象 -->
<bean id="method" class="com.jd.aop.MethodAOP"/>
<aop:config proxy-target-class="false">
<!-- 配置切点表达式 -->
<aop:pointcut expression="execution(public int com.jd.computer.service.ComputerService.*(..))" id="t"/>
<!-- 配置切面及增强类型:可以有多个切面,每个切面又可以配置多个增强类型-->
<aop:aspect ref="method">
<aop:before method="before" pointcut-ref="t"/>
</aop:aspect>
</aop:config>
</beans>
注意<aop:config>标签中有一属性proxy-target-class="false",该属性值为false时代表使用JDK代理,而当值为true代表使用CGLib代理,两者代理区分详见博客:详述JDK代理与CGLib代理区别