1. 为什么需要切面编程
如果要重复通用功能的话,最常见的面向对象的技术就是继承或委托。
继承的缺点:整个工程中都是用相同的基类,会导致一个脆弱的对象体系。脆弱的简单例子,你修改基类中的方法名,则需要在每一个派生类中去修改调用。
委托的缺点:可能需要对委托对象进行复杂的调用。
切面提供了一种更加清晰简洁重复通用功能的功能。例:有几个类都需要在一个点执行一个相同的方法,可以把这个方法抽象成一个切面类,然后通过声明的方法,在那几个类运行到指定点的时候调用切面类中指定的方法。如图所示:
2. AOP专业术语
AOP , Aspect - Oriented - Programming,面向切面编程。
Advice (通知,增强处理):AOP 在特定的切入点执行的增强处理,即要在定义好的切入点所要执行的程序代码。通知有5种类型,前置通知、后置通知、返回通知、异常通知、环绕通知。
(1)前置通知(Before):目标方法被调用之前调用通知。
(2)后置通知(After):目标方法完成之后或抛出异常后调用通知,方法的输出对调用通知无影响。
(3)返回通知(After-returning):目标方法成功执行之后调用通知。
(4)异常通知(After-throwing):目标方法抛出异常之后调用通知
(5)环绕通知(Around):在目标方法调用之前和调用之后执行自定义行为。
JoinPoint (连接点): 程序执行的一个点,如:方法的调用和异常抛出。
Pointcut ( 切入点 ) :切面和程序的交叉点,就是需要处理的连接点。 切点通常类或者方法名,如要通知到所有以 add 开头的 方法中,所有满足这一条件的都是切入点。
配置切入点: execution(修饰符 返回值类型 类路径 匹配方法名 (参数) 抛出异常的类型)
红色的不能被省略,各部分都支持通过 “*” 来匹配。
参数:支持两种通配符,”*“ , ”..“。
“*” 匹配任意一个参数。
“..” 匹配任意数量的参数和类型的方法。
下面这个切入点表达式,com.study.spring.dao 下所有的方法都是切入点。
// 定义切入点表达式
@Pointcut("execution(* com.study.spring.dao.*.*(..))")
private void myPointCut(){};
Aspect(切面):通知和切面,一个类,封装好的用于横向插入系统功能,如事务、日志等。
Target Object (目标对象) : 所有被通知的对象,也称为增强对象。
Proxy (代理):将通知应用到目标对象之后,动态创建的对象。
Weaving(织入): 将切面代码插入到目标对象上,从而生成代理对象的过程。
3. 代码实例
工程目录如下图所示,习惯性的创建了一个Web项目,web.xml和index.jsp 为空。
lib 中jar包:链接:https://pan.baidu.com/s/1abpL6_WT3XW7Aqh9QYzzEA ,提取码:qxif 。
目标对象,IUserDao.java,UserDAOImpl.java:
IUserDao.java:
package com.study.spring.dao;
public interface IUserDao {
public void addUser();
public void deleteUser();
}
UserDAOImpl.java:
package com.study.spring.dao.impl;
import com.study.spring.dao.IUserDao;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
@Repository("userDao")
public class UserDAOImpl implements IUserDao {
@Override
public void addUser() {
System.out.println("目标方法执行中:添加用户 ...");
int i = 1;
//i = i/0;
}
@Override
public void deleteUser() {
System.out.println("目标方法执行中:删除用户...");
}
}
切面类,MyAspect.java:
package com.study.spring.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MyAspect {
// 定义切入点表达式
@Pointcut("execution(* com.study.spring.dao.*.*(..))")
private void myPointCut(){};
/**
* 前置通知
* @param joinpoint
*/
@Before("myPointCut()")
public void myBefore(JoinPoint joinpoint) {
System.out.print("前置通知, @Before");
System.out.print("目标类是 " + joinpoint.getTarget());
System.out.println("目标方法为 " + joinpoint.getSignature().getName());
}
/**
* 返回通知
* @param joinPoint
*/
@AfterReturning("myPointCut()")
public void myAfterReturning(JoinPoint joinPoint) {
System.out.print("返回通知:@AfterReturning");
System.out.println("目标方法 " + joinPoint.getSignature().getName());
}
/**
* 环绕通知
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
@Around("myPointCut()")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕开始,@Around");
Object obj = proceedingJoinPoint.proceed();
System.out.println("环绕结束,@Around");
return obj;
}
/**异常通知
*
* @param joinPoint
* @param throwable
*/
@AfterThrowing(value = "myPointCut()", throwing = "throwable")
public void myAfterThrowing(JoinPoint joinPoint, Throwable throwable) {
System.out.println("异常通知 @AfterThrowing:" + "错误如下" + throwable.getMessage());
}
/**
* 后置通知
*/
@After("myPointCut()")
public void myAfter() {
System.out.println("后置通知: @After");
}
}
applicationContext.xml配置文件,指定需要扫描的包,启动基于注解的声明式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"
xmlns:context="http://www.springframework.org/schema/context"
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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.study.spring.*"/>
<aop:aspectj-autoproxy/>
</beans>
测试代码,Test.java
package com.study.spring;
import com.study.spring.dao.IUserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
String xmlPath = "applicationContext.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
IUserDao iUserDao = (IUserDao) applicationContext.getBean("userDao");
iUserDao.addUser();
iUserDao.deleteUser();
}
}
执行Test.java,测试结果如下所示:
测试异常通知,修改addUser方法为:
@Override
public void addUser() {
System.out.println("目标方法执行中:添加用户 ...");
int i = 1;
i = i/0;
}
执行Test.java,测试结果如下所示:
参考文献:
-
Spring实战:第四版 / 美(沃尔斯)著;张卫东译.——北京 :人民邮电出版社,2016.4 (2018.10 重印)
-
JavaEE 企业级应用开发教程(Spring + SpringMVC + MyBatis)/ 黑马程序员编著. ——北京 :人民邮电出版社,2017.9 (2018.2 重印)