一、AOP概念和术语
概念
AOP即为Aspect Oriented Programming的缩写,意为:面向切面编程。AOP是OOP(面向对象编程)的扩展和延伸,用于解决OOP开发遇到的问题。
AOP思想是最早由AOP联盟组织提出的,Spring是目前使用这种思想最好的框架。Spring的AOP有自己的实现方式。因其比较繁琐,所以Spring引入AspectJ(一个AOP框架)作为自身AOP的开发。
术语理解
为了方便理解,假设现在定义有如下类:
public class UserDao {
public void save(){}
public void find(){}
public void update(){}
public void delete(){}
}
- 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之动态代理
二、AOP开发(XML)
1. 创建web项目,引入jar包
- 引入基本开发包
- 引入aop开发的相关jar包
分别为AOP联盟相关Jar包、Aspectj包(因为我们要使用Aspectj框架开发)、AOP包和Spring与Apspectj的整合包。 - 引入日志打印相关包
2. 引入Spring的配置文件
- 引入aop约束,配置文件applicationContext.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: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找到。
3. 编写目标类并完成配置
首先定义ProductDao接口,代码如下:
public interface ProductDao {
public void save();
public void update();
public void delete();
public void find();
}
添加实现类ProductDaoImpl,具体代码如下:
public class ProductDaoImpl implements ProductDao {
@Override
public void save() {
System.out.println("保存商品");
}
@Override
public void update() {
System.out.println("更新商品");
}
@Override
public void delete() {
System.out.println("删除商品");
}
@Override
public void find() {
System.out.println("查询商品");
}
}
最后在applicationContext.xml进行如下配置:
<!-- 配置目标对象,即被增强的对象-->
<bean id="productDao" class="com.shoto.spring.xmldemo.ProductDaoImpl"/>
4. 编写测试类
@RunWith(SpringJUnit4ClassRunner.class) //Spring整合JUnit4单元测试
@ContextConfiguration("classpath:applicationContext.xml") //加载配置文件
public class SpringDemo {
@Resource(name="productDao")//属性注入
private ProductDao productDao;
@Test//需要JUnit4,不能使用JUnit5
public void demo() {
productDao.save();
productDao.update();
productDao.delete();
productDao.find();
}
}
注意: 这里使用到了Spring与JUnit4的整合,需要在web工程导入spring-test-4.2.4.RELEASE.jar,然后使用RunWith声明。另外我们也是了ContextConfiguration注解来加载配置文件,因此不用每次都以new的方式来获取ApplicationContext对象。
测试运行结果如下:
5. 编写一个切面类
- 编写切面类,具体代码如下:
public class MyAspectXML {
//权限校验
public void checkPri() {
System.out.println("权限校验...");
}
}
- 将切面类交由Spring管理,在applicationContext.xml进行如下配置:
<!-- 将切面类交由Spring管理 -->
<bean id="myAspect" class="com.shoto.spring.xmldemo.MyAspectXML"/>
6. 通过AOP配置来引用切面类
在applicationContext.xml进行如下配置:
<!-- 通过AOP的配置完成对目标对象类ProductDaoImpl产生代理 -->
<aop:config>
<!-- 配置切入点。
expression表达式配置哪些类的哪些方法需要进行增强 ,这里是ProductDaoImpl的save方法。
其中*表示任意返回值,..表示任意参数
此时save方法为一个pointcut,即切入点-->
<aop:pointcut expression="execution(* com.shoto.spring.xmldemo.ProductDaoImpl.save(..))"
id="pointcut1"/>
<!-- 配置切面 -->
<aop:aspect ref="myAspect">
<!-- 配置前置增强,简单说就是在切入点save之前先执行的增强即checkPri权限校验方法 -->
<aop:before method="checkPri" pointcut-ref="pointcut1"/>
</aop:aspect>
</aop:config>
此时再次运行测试类SpringDemo的测试方法,其测试结果如下:
也就是在save方法执行之前进行了权限校验,即对save进行了增强。
三、Spring增强类型(XML)
前置增强:在目标方法执行之前进行操作
比如上面配置的内容,即给save方法设置前置增强checkPri方法,如下所示:
另外,我们可以通过JoinPoint类来获取切入点信息:
修改切面类MyAspectXML的checkPri方法如下:
运行测试输出如下结果:
后置增强:在目标方法执行之后进行操作
下面我们给ProductDaoImpl的delete方法添加一个后置增强writeLog方法,即在删除后进行日志记录。
在applicationContext.xml进行delelet切入点的配置,具体如下:
然后配置后置通知,具体配置如下:
此时再次运行测试类SpringDemo的测试方法,其测试结果如下:
也就是在删除商品后进行日志记录操作。
另外,<aop:after-returning>标签有retruning属性,我们可以使用它来获取切入点delete方法的返回值。下面修改ProductDao的实现类ProductDaoImpl的delete方法如下:
@Override
public String delete() {
System.out.println("删除商品");
return "删除成功!";
}
同时,修改切面类MyAspect的writeLog方法如下:
//日志记录
public void writeLog(Object result) {
System.out.println("日志记录...");
System.out.println("delete方法的返回值为:" + result);
}
applicationContext.xml的配置如下:
returning的内容result即为writeLog方法的result参数。该result用于接收切入点方法delete的返回值, 这里即是"删除成功"。
运行测试的结果如下:
环绕增强:在目标方法执行前后进行操作
下面以ProductDaoImpl的update方法为切入点,演示环绕增强的使用。
在applicationContext.xml进行update切入点的配置,具体如下:
<aop:pointcut expression="execution(* com.shoto.spring.xmldemo.ProductDaoImpl.update(..))"
id="pointcut3"/>
在增强类添加如下方法:
/**
* 定义一个环绕的增强方法
* @throws Throwable
*/
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕输出...");
//调度ProceedingJoinPoint的proceed方法就可以调用原有的方法
Object object = joinPoint.proceed();
System.out.println("环绕输出...");
return object;
}
在applicationContext.xml进行后置增强配置,具体如下:
<!-- 配置环绕增强 -->
<aop:around method="around" pointcut-ref="pointcut3"/>
此时再次运行测试类SpringDemo的测试方法,其测试结果如下:
异常抛出增强:在程序出现异常时进行的操作
下面以find方法为切入点来演示异常抛出增强的使用。
同样的,在applicationContext.xml进行update切入点的配置,具体如下:
<aop:pointcut expression="execution(* com.shoto.spring.xmldemo.ProductDaoImpl.find(..))"
id="pointcut4"/>
在增强类添加如下方法:
/**
* 异常抛出增强,ex封装有异常信息
*/
public void afterThrowing(Throwable ex) {
System.out.println("异常抛出增强..." + ex.getMessage());
}
在applicationContext.xml进行异常抛出增强配置,具体如下:
<!-- 配置异常抛出增强 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="ex"/>
注意: throwing属性中的ex即为afterThrowing方法的参数名
此时find方法若发生了除0异常,运行测试会输出如下结果:
最终增强:无论代码是否有异常,总会执行
在上面演示异常抛出增强的基础上来演示最终增强的使用。
在增强类添加如下方法:
/**
* 定义一个最终增强方法
*/
public void after() {
System.out.println("最终通知增强...");
}
在applicationContext.xml进行最终增强配置,具体如下:
<!-- 配置最终增强 -->
<aop:after method="after" pointcut-ref="pointcut4"/>
运次测试结果如下:
四、切入点表达式语法
基于execution的函数完成的,其结构如下所示:
[访问修饰符] 方法返回值 包名.类名.方法名(参数)
示例如下:
第一个表示访问修饰符是public,返回值是void,然后就是com.itheima.spring.CustomerDao.save方法,其中参数用…表示任意参数;
第二个省略了访问修饰符(后面几个同理),表示任意返回值,任意包下的所有以Dao结尾的类的save方法;
第三个的+号表示CustomerDao的当前类和其子类有效;
第四个表示com.itheima.spring包的所有子包的所有子类的所有方法。
五、AOP开发(注解)
1. 创建web项目,引入Jar包,并引入配置文件
具体参考基于XML的AOP开发的内容
2. 编写目标类并配置
public class OrderDao {
public void save() {
System.out.println("保存订单...");
}
public void update() {
System.out.println("更新订单...");
}
public String delete() {
System.out.println("删除订单...");
return "删除成功!";
}
public void find() {
System.out.println("查询订单...");
}
}
在applicationContext.xml进行如下配置:
<bean id="orderDao" class="com.shoto.spring.demo.OrderDao"></bean>
3. 编写切面类并配置
/**
* 切面类:注解的切面类
* @author 郑松涛
*
*/
@Aspect //声明该类为切面类
public class MyAspectAnno {
public void before() {
System.out.println("前置增强===========");
}
}
注意:需要使用@Aspect注解来声明该类为切面类。
同样,在applicationContext.xml进行如下配置:
<bean id="myAspect" class="com.shoto.spring.demo.MyAspectAnno"></bean>
4. 使用注解对AOP对象目标类进行增强
- 要使用注解,必须在配置中开启注解的AOP开发
<!-- 在配置文件中开启注解的AOP开发 --> <aop:aspectj-autoproxy />
- 然后下面以前置增强为例来在切面类上使用注解,具体代码如下:
5. 编写测试类
@RunWith(SpringJUnit4ClassRunner.class) //Spring整合JUnit单元测试
@ContextConfiguration("classpath:applicationContext.xml") //加载配置文件
public class SpringDemo {
@Resource(name="orderDao")//属性注入
private OrderDao orderDao;
@Test//需要JUnit4,不能使用JUnit5
public void demo() {
orderDao.save();
orderDao.update();
orderDao.delete();
orderDao.find();
}
}
运行结果如下:
六、Spring增强类型(注解)
下面讲一下Spring的基于注解的AOP的各种增强使用,这里只给出增强的使用的核心内容(切面类MyAspectAnno中)。
@Before:前置增强
//通过注解的方式给save配置前置增强
@Before(value="execution(* com.shoto.spring.demo.OrderDao.save(..))")
public void before() {
System.out.println("前置增强===========");
}
@AfterReturning:后置增强
//后置通知
@AfterReturning(value="execution(* com.shoto.spring.demo.OrderDao.delete(..))",returning="result")
public void afterReturning(Object result) {
System.out.println("后置增强===========" + result);
//输出 后置增强===========删除成功!
}
@Around:环绕增强
//环绕通知
@Around(value="execution(* com.shoto.spring.demo.OrderDao.update(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前增强==========");
Object object = joinPoint.proceed();
System.out.println("环绕后增强==========");
return object;
}
@AfterThrowing:异常抛出增强
//异常抛出异常
@AfterThrowing(value="execution(* com.shoto.spring.demo.OrderDao.find(..))",throwing="ex")
public void afterThrowing(Throwable ex) {
System.out.println("异常抛出增强==========" + ex.getMessage());
}
@After:最终增强
//最终增强
@After(value="execution(* com.shoto.spring.demo.OrderDao.find(..))")
public void after() {
System.out.println("最终增强==========");
}