1. AOP概念和术语
1.1 概念
AOP即为Aspect Oriented Programming的缩写,意为:面向切面编程。AOP是OOP(面向对象编程)的扩展和延伸,用于解决OOP开发遇到的问题。
AOP思想是最早由AOP联盟组织提出的,Spring是目前使用这种思想最好的框架。Spring的AOP有自己的实现方式。因其比较繁琐,所以Spring引入AspectJ(一个AOP框架)作为自身AOP的开发。
1.2 术语理解
为了方便理解,假设现在定义有如下类:
1 | public class UserDao { |
- Advice:增强,通知。即在方法层面上进行的增强。 比如,现在需要对save方法进行权限校验,而该权限校验的方法(checkPri)便是增强。
- Joinpoint:连接点,即可以被拦截到的点。所有可以进行增强的方法,如UserDao中的增删改查方法都可以被称为连接点。
- Pointcut:切入点,即真正被拦截到的点。在实际开发过程中,我们不一定要对所有的方法都进行增强,而是只对save方法进行增强,那么save方法则是一个切入点。
- Introduction:引介。不同于Advice是在方法层面上的增强,Introduction是在类层面上的增强。比如现在需要对UserDao类通过动态代理的方式丰富UserDao的功能,这就是引介。
- Target:被增强的对象。比如我要对UserDao进行增强,那么UserDao类称为是Target。
- Weaving:织入。指的就是将增强(Advice)应用到目标(Target)的过程。比如我现在需要将权限校验的方法的代码应用到UserDao的save方法上的过程便是织入。
- Proxy:代理。 就是一个类被AOP织入增强后产生的一个结果代理类。
- Aspect:切面。 是多个切入点和多个通知或引介的结合。
补充:Spring的底层原理 使用到了JDK动态代理和CGLIB动态代理,具体参考Java之动态代理
2. AOP开发(XML)
2.1 创建web项目,引入jar包
引入基本开发包
引入aop开发的相关jar包
分别为AOP联盟相关Jar包、Aspectj包(因为我们要使用Aspectj框架开发)、AOP包和Spring与Apspectj的整合包。引入日志打印相关包
2.2 引入Spring的配置文件
引入aop约束,配置文件applicationContext.xml的内容如下:
1
2
3
4
5
6
7
8
9
10
11
<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 -->
</beans>注意: 可在spring-framework-4.2.4.RELEASE-dist\spring-framework-4.2.4.RELEASE\docs\spring-framework-reference\html\xsd-configuration.html找到。
2.3 编写目标类并完成配置
首先定义ProductDao接口,代码如下:
1 | public interface ProductDao { |
添加实现类ProductDaoImpl,具体代码如下:
1 | public class ProductDaoImpl implements ProductDao { |
最后在applicationContext.xml进行如下配置:
1 | <!-- 配置目标对象,即被增强的对象--> |
2.4 编写测试类
1 | //Spring整合JUnit4单元测试 (SpringJUnit4ClassRunner.class) |
注意: 这里使用到了Spring与JUnit4的整合,需要在web工程导入spring-test-4.2.4.RELEASE.jar,然后使用RunWith声明。另外我们也是了ContextConfiguration注解来加载配置文件,因此不用每次都以new的方式来获取ApplicationContext对象。
测试运行结果如下:
2.5 编写一个切面类
编写切面类,具体代码如下:
1
2
3
4
5
6
7public class MyAspectXML {
//权限校验
public void checkPri() {
System.out.println("权限校验...");
}
}将切面类交由Spring管理,在applicationContext.xml进行如下配置:
1
2<!-- 将切面类交由Spring管理 -->
<bean id="myAspect" class="com.shoto.spring.xmldemo.MyAspectXML"/>
2.6 通过AOP配置来引用切面类
在applicationContext.xml进行如下配置:
1 | <!-- 通过AOP的配置完成对目标对象类ProductDaoImpl产生代理 --> |
此时再次运行测试类SpringDemo的测试方法,其测试结果如下:
也就是在save方法执行之前进行了权限校验,即对save进行了增强。
3. Spring增强类型(XML)
3.1 前置增强:在目标方法执行之前进行操作
比如上面配置的内容,即给save方法设置前置增强checkPri方法,如下所示:
另外,我们可以通过JoinPoint类来获取切入点信息:
修改切面类MyAspectXML的checkPri方法如下:
运行测试输出如下结果:
3.2 后置增强:在目标方法执行之后进行操作
下面我们给ProductDaoImpl的delete方法添加一个后置增强writeLog方法,即在删除后进行日志记录。
在applicationContext.xml进行delelet切入点的配置,具体如下:
然后配置后置通知,具体配置如下:
此时再次运行测试类SpringDemo的测试方法,其测试结果如下:
也就是在删除商品后进行日志记录操作。
另外,<aop:after-returning>标签有retruning属性,我们可以使用它来获取切入点delete方法的返回值。下面修改ProductDao的实现类ProductDaoImpl的delete方法如下:
1 |
|
同时,修改切面类MyAspect的writeLog方法如下:
1 | //日志记录 |
applicationContext.xml的配置如下:
returning的内容result即为writeLog方法的result参数。该result用于接收切入点方法delete的返回值, 这里即是”删除成功”。
运行测试的结果如下:
3.3 环绕增强:在目标方法执行前后进行操作
下面以ProductDaoImpl的update方法为切入点,演示环绕增强的使用。
在applicationContext.xml进行update切入点的配置,具体如下:
1 | <aop:pointcut expression="execution(* com.shoto.spring.xmldemo.ProductDaoImpl.update(..))" |
在增强类添加如下方法:
1 | /** |
在applicationContext.xml进行后置增强配置,具体如下:
1 | <!-- 配置环绕增强 --> |
此时再次运行测试类SpringDemo的测试方法,其测试结果如下:
3.4 异常抛出增强:在程序出现异常时进行的操作
下面以find方法为切入点来演示异常抛出增强的使用。
同样的,在applicationContext.xml进行update切入点的配置,具体如下:
1 | <aop:pointcut expression="execution(* com.shoto.spring.xmldemo.ProductDaoImpl.find(..))" |
在增强类添加如下方法:
1 | /** |
在applicationContext.xml进行异常抛出增强配置,具体如下:
1 | <!-- 配置异常抛出增强 --> |
注意: throwing属性中的ex即为afterThrowing方法的参数名
此时find方法若发生了除0异常,运行测试会输出如下结果:
3.5 最终增强:无论代码是否有异常,总会执行
在上面演示异常抛出增强的基础上来演示最终增强的使用。
在增强类添加如下方法:
1 | /** |
在applicationContext.xml进行最终增强配置,具体如下:
1 | <!-- 配置最终增强 --> |
运次测试结果如下:
4. 切入点表达式语法
基于execution的函数完成的,其结构如下所示:
[访问修饰符] 方法返回值 包名.类名.方法名(参数)
示例如下:
第一个表示访问修饰符是public,返回值是void,然后就是com.itheima.spring.CustomerDao.save方法,其中参数用..表示任意参数;
第二个省略了访问修饰符(后面几个同理),表示任意返回值,任意包下的所有以Dao结尾的类的save方法;
第三个的+号表示CustomerDao的当前类和其子类有效;
第四个表示com.itheima.spring包的所有子包的所有子类的所有方法。
5. AOP开发(注解)
5.1 创建web项目,引入Jar包,并引入配置文件
具体参考基于XML的AOP开发的内容
5.2 编写目标类并配置
1 | public class OrderDao { |
在applicationContext.xml进行如下配置:
1 | <bean id="orderDao" class="com.shoto.spring.demo.OrderDao"></bean> |
5.3 编写切面类并配置
1 | /** |
注意:需要使用@Aspect注解来声明该类为切面类。
同样,在applicationContext.xml进行如下配置:
1 | <bean id="myAspect" class="com.shoto.spring.demo.MyAspectAnno"></bean> |
5.4 使用注解对AOP对象目标类进行增强
要使用注解,必须在配置中开启注解的AOP开发
1
2<!-- 在配置文件中开启注解的AOP开发 -->
<aop:aspectj-autoproxy />然后下面以前置增强为例来在切面类上使用注解,具体代码如下:
5.5 编写测试类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//Spring整合JUnit单元测试 (SpringJUnit4ClassRunner.class)
"classpath:applicationContext.xml") //加载配置文件 (
public class SpringDemo {
"orderDao")//属性注入 (name=
private OrderDao orderDao;
//需要JUnit4,不能使用JUnit5
public void demo() {
orderDao.save();
orderDao.update();
orderDao.delete();
orderDao.find();
}
}
运行结果如下:
6. Spring增强类型(注解)
下面讲一下Spring的基于注解的AOP的各种增强使用,这里只给出增强的使用的核心内容(切面类MyAspectAnno中)。
6.1 @Before:前置增强
1 | //通过注解的方式给save配置前置增强 |
6.2 @AfterReturning:后置增强
1 | //后置通知 |
6.3 @Around:环绕增强
1 | //环绕通知 |
6.4 @AfterThrowing:异常抛出增强
1 | //异常抛出异常 |
6.5 @After:最终增强
1 | //最终增强 |