一、AOP的概念
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务进行内聚性的开发。
二、AOP的相关术语
JoinPoint(连接点):可以被拦截到的方法;
Pointcut(切入点):在开发中真正被拦截到的方法;
Advice(通知/增强):在切入点执行的时候触发的其他方法,如权限校验、事务管理等;
Introduction(引介):与Advice是方法层面的增强不同的是,Introduction是类层面的增强;
Target(目标):被增强的对象;
Weaving(织入):将通知(Advice)应用到目标(Target)的过程;
Proxy(代理):将通知(Advice)应用到目标(Target)之后产生的代理对象;
Aspect(切面):多个通知(Advice)和切入点(Pointcut)的组合。
三、AOP作用
AOP可以用于权限校验、日志输出、性能监控、异常处理和事务管理等。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,简化代码的同时提高了开发的效率。假如不使用AOP,例如下面的业务逻辑层代码:
package cn.jingpengchong.calculator.service;
import org.springframework.stereotype.Service;
@Service
public class CalculatorService implements ICalculatorService {
public int div(int a, int b) {
System.out.println("日志:The div method begins");
System.out.println("日志:The div method args:["+a+","+b+"]");
return a/b;
}
}
该例没有使用Spring的AOP功能,因此其主要方法只有“return a/b;”这一句,然而为了打印日志信息,却需要两行代码,显得特别臃肿。并且如果要是再扩展一个乘法功能,那么仍然需要写这两行代码,这是非常繁琐的。那么如何解决这么繁琐的事情呢?当然就是使用spring提供的AOP功能了。
使用Spring的基于Aspectj的AOP开发需要的jar包有:
1、Spring所需的基本的jar包:
2、Spring日志输出规范包:
3、Log4j的相关jar包:
4、Spring的AOP的jar包:
5、AOP联盟的jar包:
6、Aspectj的jar包:
7、Spring与Aspectj的整合包:
四、利用注解方式使用AOP
将所需要的jar包导入当前工程之后,首先在xml配置文件中加入“<aop:aspectj-autoproxy/>”标签,其次编写切面Aspect和通知Advice,在类前面添加@Aspect注解就告知了Spring该类是一个切面,在方法前面添加对应通知的注解就表明该方法是一个通知(或叫做增强),该注解的value属性声明的表达式匹配到的方法被称为切入点,当这些方法得执行时会触发其所对应的通知。
1、切入点前的注解有以下几种:
- @Before(value=""):前置处理,用于在该注解匹配的方法执行前进行一些操作;
- @After(value=""):后置处理,用于在该注解匹配的方法执行后进行一些操作;
- @AfterReturning(value="",returning=""):返回处理,用于在该注解匹配的方法返回结果后进行一些操作;
- @AfterThrowing(value="",throwing=""):异常处理,用于在该注解匹配的方法返回异常后进行一些操作;
- @Around(value=""):环绕处理,可以根据需求自编代码在相关环节进行处理。
注:
- value属性用于匹配要处理的方法;
- returning属性用于接收返回值;
- throwing属性用于接收异常对象;
- @After与@AfterReturning的区别:
- @After注解的方法先于@AfterReturning注解的方法执行;
- @在遇到异常时,After注解的方法仍执行,而@AfterReturning注解的方法不执行。
2、例:分别使用@Before、@After、@AfterReturning和@Around注解对上面的service层方法执行的各个环节进行处理
a.将service层方法打印日志的代码删了
package cn.jingpengchong.calculator.service;
import org.springframework.stereotype.Service;
@Service
public class CalculatorService implements ICalculatorService {
public int div(int a, int b) {
return a/b;
}
}
b.在spring xml文件中添加该标签:自动代理
<aop:aspectj-autoproxy/>
c.新建独立模块“cn.jingpengchong.aspect”,在其中新建文件“CalculatorAspect.java”如下:
package cn.jingpengchong.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class CalculatorAspect {
//前置处理
@Before("execution(public int cn.jingpengchong.calculator.service.CalculatorService.*(..))")
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 " + name + " method args:[" + args[0] + "," + args[1] + "]");
}
//后置处理
@After("execution(public int cn.jingpengchong.calculator.service.CalculatorService.*(..))")
public void after(JoinPoint jp) {
Signature signature = jp.getSignature();
String name = signature.getName();
System.out.println("日志:The " + name + " method ends");
}
//返回处理
@AfterReturning(value = "execution(public int cn.jingpengchong.calculator.service.CalculatorService.*(..))", returning = "result")
public void afterReturning(JoinPoint jp, Object result) {
Signature signature = jp.getSignature();
String name = signature.getName();
System.out.println("日志:The " + name + " method result:" + result);
}
//异常处理
@AfterThrowing(value = "execution(public int cn.jingpengchong.calculator.service.CalculatorService.*(..))", throwing = "e")
public void afterThrowing(JoinPoint jp, Exception e) {
Signature signature = jp.getSignature();
String name = signature.getName();
System.out.println("日志:The " + name + " method exception:" + e);
}
}
d.编写测试类:
package cn.jingpengchong.test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.jingpengchong.calculator.service.ICalculatorService;
public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
ICalculatorService calculatorService = applicationContext.getBean(ICalculatorService.class);
System.out.println(calculatorService.div(6, 2));
applicationContext.close();
}
}
运行结果如下:
附:
1、上面“CalculatorAspect.java”文件中每个方法都要写带有很长参数的注解,我们可以在该文件中将切入点的表达式提取出来放在一个空方法上,其作用类似于一个全局变量:
@Pointcut("execution(public int cn.jingpengchong.calculator.service.CalculatorService.*(..))")
public void pointCut() {
}
这样的话上面的注解的参数就可以写成“pointCut()”,进一步简化了代码。
2、如果觉得在“CalculatorAspect.java”文件中定义四个方法太繁琐,那么可以用环绕注解@Around将处理过程封装在一个方法中:
//环绕处理
@Around("pointCut()")
public Object around(ProceedingJoinPoint pjp) {
Object result = null;
Object[] args = pjp.getArgs();
String name = pjp.getSignature().getName();
try {
try {
//前置处理
System.out.println("The " + name + " method begins");
System.out.println("The " + name + " method args:[" + args[0] + "," + args[1] + "]");
result = pjp.proceed();
}finally {
//后置处理
System.out.println("The " + name + " method ends");
}
//返回处理
System.out.println("The " + name + " method result:" + result);
} catch (Throwable e) {
//异常处理
System.out.println("The " + name + " method exception:" + e);
}
return result;
}
五、利用Spring的xml配置文件使用AOP
同样需要将所需jar包导入当前工程
1、仍然以处理该service层方法的日志为例:
package cn.jingpengchong.calculator.service;
import org.springframework.stereotype.Service;
@Service
public class CalculatorService implements ICalculatorService {
public int div(int a, int b) {
return a/b;
}
}
2、新建独立模块“cn.jingpengchong.aspect”,在其中新建文件“LogAspect.java”如下:
package cn.jingpengchong.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
public class LogAspect {
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 " + name + " method args:[" + args[0] + "," + args[1] + "]");
}
}
3、在spring的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="cn.jingpengchong"></context:component-scan>
<bean id = "logAspect" class = "cn.jingpengchong.aspect.LogAspect"/>
<aop:config>
<!--该标签是为了简化代码,指定一个id来表示该表达式,下面再引用时,直接用该id就行了-->
<aop:pointcut expression="execution(public int cn.jingpengchong.calculator.service.CalculatorService.*(..))" id="pointCut"/>
<aop:aspect ref="logAspect">
<!--以前置处理为例,其他处理类似:
method表明触发哪个通知;
pointcut-ref引入表达式;
pointcut生命表达式-->
<aop:before method="before" pointcut-ref="pointCut"/>
</aop:aspect>
</aop:config>
</beans>
4、编写测试类:
package cn.jingpengchong.test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.jingpengchong.calculator.service.ICalculatorService;
public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
ICalculatorService calculatorService = applicationContext.getBean(ICalculatorService.class);
System.out.println(calculatorService.div(6, 2));
applicationContext.close();
}
}
运行结果如下: