一、AOP的概念
AOP(Aspect Oriented Programming)即面向切面编程,它是指将业务流程中通用的功能单独抽取出来,形成“切面”,在合适的时机将这些切面切入到业务板块中。
AOP是对OOP的补充,它大大降低了企业开发中的代码耦合度,使得业务板块更加简洁,令功能的复用性也得以提升。
二、AOP的术语
术语 | 含义 |
---|---|
JoinPoint(连接点) | 所有能被拦截的方法 |
Pointcut(切入点) | 特指被拦截的方法 |
Advice(通知) | 拦截后要执行的操作 |
Target(目标) | 特指被AOP操作的对象 |
Weaving(织入) | 将Advice应用到Target的过程 |
Proxy(代理) | AOP完成后产生的代理对象 |
Aspect(切面) | Pointcut + Advice |
三、AOP的原理
1. JDK动态代理
JDK在底层通过字节码技术完成代理操作,它只能对实现接口的类进行代理。若类实现了接口,Spring框架将采用JDK的方式实现AOP。
请看示例:
public interface UserDao {
void add();
void update();
void delete();
void find();
}
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("===增加数据===");
}
@Override
public void update() {
System.out.println("===修改数据===");
}
@Override
public void delete() {
System.out.println("===删除数据===");
}
@Override
public void find() {
System.out.println("===查找数据===");
}
}
假设现在要对add方法增加权限校验模板,具体操作如下:
public class MyJdkProxy implements InvocationHandler {
private UserDao userDao;
public MyJdkProxy(UserDao userDao) {
this.userDao = userDao;
}
/**
* 创建代理类对象的方法
* @return 代理类对象
*/
public Object createProxy() {
Object proxy = Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), this);
return proxy;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("add".equals(method.getName())) {
System.out.println("————权限校验————");
return method.invoke(userDao, args);
}
return method.invoke(userDao, args);
}
}
最终执行代理类对象中的add方法:
UserDao userDao = new UserDaoImpl();
UserDao proxy = (UserDao) new MyJdkProxy(userDao).createProxy();
proxy.add(); // 权限校验 + 增加数据
2. CGLib动态代理
CGLib包含在Spring框架中,采用了类继承的方式实现动态代理,没有接口实现类的限制。若类没有实现任何接口,Spring框架将采用CGLib的方式实现AOP。
public class UserDao {
public void add() {
System.out.println("===增加数据===");
}
public void update() {
System.out.println("===修改数据===");
}
public void delete() {
System.out.println("===删除数据===");
}
public void find() {
System.out.println("===查找数据===");
}
}
假设现在要对add方法增加权限校验模板,具体操作如下:
public class MyCglibProxy implements MethodInterceptor {
private UserDao userDao;
public MyCglibProxy(UserDao userDao) {
this.userDao = userDao;
}
/**
* 创建代理类对象的方法
*
* @return 代理类对象
*/
public Object createProxy() {
// 1.创建核心类
Enhancer enhancer = new Enhancer();
// 2.设置父类
enhancer.setSuperclass(userDao.getClass());
// 3.设置回调
enhancer.setCallback(this);
// 4.创建代理
Object proxy = enhancer.create();
return proxy;
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if ("add".equals(method.getName())) {
System.out.println("————权限校验————");
return methodProxy.invokeSuper(proxy, args);
}
return methodProxy.invokeSuper(proxy, args);
}
}
UserDao userDao = new UserDao();
UserDao proxy = (UserDao) new MyCglibProxy(userDao).createProxy();
proxy.add(); // 权限校验 + 增加数据
四、传统Spring AOP
暂时省略,可参考其它博客
五、基于AspectJ框架
1. 注解方式
注解 | 含义 |
---|---|
@Before | 前置通知(BeforeAdvice) |
@AfterReturning | 后置通知(AfterReturningAdvice) |
@Around | 环绕通知(MethodInterceptor) |
@AfterThrowing | 异常抛出通知(ThrowAdvice) |
@After | 最终通知(类似try-catch中的finally) |
配置文件需要使用AOP的约束,并且开启AspectJ的注解扫描
<?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: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/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here -->
<!-- 开启AspectJ的注解扫描 -->
<aop:aspectj-autoproxy/>
</beans>
现定义一个UserDao类:
public class UserDao {
public void add() {
System.out.println("===增加数据===");
}
public String update() {
System.out.println("===修改数据===");
return "finished!";
}
public void delete() {
System.out.println("===删除数据===");
}
public void find() {
System.out.println("===查找数据===");
}
}
通过execution函数可以完成切入点的定义,具体语法如下:
execution(<返回类型> <包名.类名.方法名>(参数))
现定义一个切面类:
@Aspect
public class MyAspectAnno {
/**
* 前置增强的方法
* @param joinPoint 切入点信息
*/
@Before(value = "execution(* aspect1.UserDao.add(..))")
public void before(JoinPoint joinPoint) {
System.out.println("————前置通知————" + joinPoint);
}
/**
* 后置增强的方法
* @param returning 目标方法的返回值
*/
@AfterReturning(value = "execution(* aspect1.UserDao.update(..))", returning = "returning")
public void afterReturning(Object returning) {
System.out.println("————后置通知————" + returning);
}
/**
* 环绕增强的方法
* @param joinPoint 用于控制目标方法执行的对象
* @return 目标方法的返回值
* @throws Throwable
*/
@Around(value = "execution(* aspect1.UserDao.delete(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("————环绕前通知————");
Object obj = joinPoint.proceed();
System.out.println("————环绕后通知————");
return obj;
}
/**
* 异常抛出增强的方法
* @param e 异常信息
*/
@AfterThrowing(value = "execution(* aspect1.UserDao.find(..))", throwing = "e")
public void afterThrowing(Throwable e) {
System.out.println("————异常抛出通知————" + e);
}
/**
* 最终增强的方法
*/
@After(value = "execution(* aspect1.UserDao.find(..))")
public void after() {
System.out.println("————最终通知————");
}
}
完成相关配置后,依次运行UserDao的各个方法,结果如下:
(注:异常抛出通知仅在目标方法发生异常时会作用,而最终通知即使发生异常也会作用)
附:也可以通过@Pointcut单独定义切点,以方便注解的书写与维护:
/**
* 自定义切点1
*/
@Pointcut(value = "execution(* aspect1.UserDao.add(..))")
private void myPointcut1() {
}
/**
* 前置增强的方法
*/
@Before(value = "myPointcut1()")
public void before() {
System.out.println("————前置通知————");
}
2. XML方式
沿用例1中的UserDao,并实现一个无注解的切面类
public class MyAspectXml {
/**
* 前置增强的方法
*/
public void before(JoinPoint joinPoint) {
System.out.println("————前置通知————" + joinPoint);
}
/**
* 后置增强的方法
* @param returning 目标方法的返回值
*/
public void afterReturning(Object returning) {
System.out.println("————后置通知————" + returning);
}
/**
* 环绕增强的方法
* @param joinPoint 用于控制目标方法执行的对象
* @return 目标方法的返回值
* @throws Throwable
*/
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("————环绕前通知————");
Object obj = joinPoint.proceed();
System.out.println("————环绕后通知————");
return obj;
}
/**
* 异常抛出增强的方法
*/
public void afterThrowing(Throwable e) {
System.out.println("————异常抛出通知————" + e);
}
/**
* 最终增强的方法
*/
public void after() {
System.out.println("————最终通知————");
}
}
完成XML配置文件:
<!-- 配置目标类 -->
<bean id="userDao" class="aspect2.UserDaoImpl"/>
<!-- 配置切面类 -->
<bean id="myAspectXml" class="aspect2.MyAspectXml"/>
<!-- 配置AOP -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut id="pointcut1" expression="execution(* aspect2.UserDao.add(..))"/>
<aop:pointcut id="pointcut2" expression="execution(* aspect2.UserDao.update(..))"/>
<aop:pointcut id="pointcut3" expression="execution(* aspect2.UserDao.delete(..))"/>
<aop:pointcut id="pointcut4" expression="execution(* aspect2.UserDao.find(..))"/>
<!-- 配置切面 -->
<aop:aspect ref="myAspectXml">
<!-- 配置前置通知 -->
<aop:before method="before" pointcut-ref="pointcut1"/>
<!-- 配置后置通知 -->
<aop:after-returning method="afterReturning" pointcut-ref="pointcut2" returning="returning"/>
<!-- 配置环绕通知 -->
<aop:around method="around" pointcut-ref="pointcut3"/>
<!-- 配置异常抛出通知 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="e"/>
<!-- 配置最终通知 -->
<aop:after method="after" pointcut-ref="pointcut4"/>
</aop:aspect>
</aop:config>
最终效果与例1完全一致
附:
- 要使用Spring AOP,需要引入spring-aop、aopalliance
- 要使用SpringJUnit以及Context注解,需要引入spring-test
- 要使用AspectJ,需要在AOP的基础上引入aspectjweaver、spring-aspects