转载请标明出处:https://blog.csdn.net/men_ma/article/details/106847165.
本文出自 不怕报错 就怕不报错的小猿猿 的博客
spring之AOP(即面向切面编程)&&五大通知(前置通知、后置通知、异常通知、环绕通知、过滤通知)的案例
目录 结构
1.AOP(即面向切面编程)的关键性概念
首先上图,我们来理解AOP
图中的专业名词解释:
-
目标(Target):被通知(被代理)的对象 注1:完成具体的业务逻辑
-
通知(Advice):在某个特定的连接点上执行的动作,同时Advice也是程序代码的具体实现,例如一个实现日志记录的代码(通知有些书上也称为处理) 注2:完成切面编程
-
代理(Proxy):将通知应用到目标对象后创建的对象(代理=目标+通知),
例子:外科医生+护士 注3:只有代理对象才有AOP功能,而AOP的代码是写在通知的方法里面的 -
切入点(Pointcut): 多个连接点的集合,定义了通知应该应用到那些连接点。
(也将Pointcut理解成一个条件 ,此条件决定了容器在什么情况下将通知和目标组合成代理返回给外部程序) -
适配器(Advisor):适配器=通知(Advice)+切入点(Pointcut)
2. AOP的核心点(通知)
通知 | 实现接口 | 应用场景 |
---|---|---|
前置通知 | 实现org.springframework.aop.MethodBeforeAdvice接口 | 买书、评论前加系统日志 |
后置通知 | 实现org.springframework.aop.AfterReturningAdvice接口 | 买书返利(存在bug) |
环绕通知 | 实现org.aopalliance.intercept.MethodInterceptor接口 | 类似拦截器,会包括切入点,目标类前后都会执行代码。 |
异常通知 | 实现org.springframework.aop.ThrowsAdvice接口 | 出现异常执行系统提示,然后进行处理。价格异常为例 |
过滤通知(适配器) | 实现org.springframework.aop.support.RegexpMethodPointcutAdvisor接口 | 处理买书返利的bug |
各种通知的案例准备工作及用到的工具类(提前准备):
接口类IBookBiz:
package com.xiaoqing.aop.biz;
/**
* 接口类
* @author 晴sister
* 2020年8月9日22:55:27
*
*/
public interface IBookBiz {
// 购书
public boolean buy(String userName, String bookName, Double price);
// 发表书评
public void comment(String userName, String comments);
}
接口类IBookBiz的实现类BookBizImpl:
package com.xiaoqing.aop.biz;
import com.xiaoqing.aop.ex.PriceException;
/**
* IBookBiz接口的实现类
* @author 晴sister
* 2020年8月9日22:55:59
*
*/
public class BookBizImpl implements IBookBiz {
public BookBizImpl() {
super();
}
public boolean buy(String userName, String bookName, Double price) {
// 通过控制台的输出方式模拟购书
if (null == price || price <= 0) {
throw new PriceException("book price exception");
}
System.out.println(userName + " buy " + bookName + ", spend " + price);
return true;
}
public void comment(String userName, String comments) {
// 通过控制台的输出方式模拟发表书评
System.out.println(userName + " say:" + comments);
}
}
异常通知所需的运行时异常PriceException类:
package com.xiaoqing.aop.ex;
/**
* 运行时异常
* @author 晴sister
* 2020年8月9日22:50:27
*
*/
public class PriceException extends RuntimeException {
public PriceException() {
super();
}
public PriceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public PriceException(String message, Throwable cause) {
super(message, cause);
}
public PriceException(String message) {
super(message);
}
public PriceException(Throwable cause) {
super(cause);
}
}
spring-context.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"
default-autowire="byType"
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-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- **************************AOP************************************ -->
<!-- 目标对象 -->
<bean id="bookBiz" class="com.xiaoqing.aop.biz.BookBizImpl"></bean>
<!-- 通知 -->
<bean id="myBefore" class="com.xiaoqing.aop.advice.MyMethodBeforeAdvice"></bean>
<bean id="myAfter" class="com.xiaoqing.aop.advice.MyAfterReturningAdvice"></bean>
<bean id="myInterceptor" class="com.xiaoqing.aop.advice.MythodInterceptor"></bean>
<bean id="myThrowsAdvice" class="com.xiaoqing.aop.advice.MyThrowsAdvice"></bean>
<bean id="myAfter2" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="myAfter"></property>
<property name="pattern" value=".*buy"></property>
</bean>
<!-- 由代理工厂来组装目标对象及通知 -->
<bean id="bookProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 原料 -->
<!-- target:目标对象是谁 ref:引用进来,本身不变-->
<property name="target" ref="bookBiz"></property>
<!-- proxyInterfaces:代理的哪个接口 -->
<property name="proxyInterfaces">
<!-- 有时实现的接口不只一个,所以我们用列表list -->
<list>
<value>com.xiaoqing.aop.biz.IBookBiz</value>
</list>
</property>
<!--interceptorNames:需要应用到目标对象上的通知Bean的名字。(List) -->
<property name="interceptorNames">
<list>
<!--通知 -->
<value>myBefore</value>
<!-- <value>myAfter</value> -->
<value>myInterceptor</value>
<value>myThrowsAdvice</value>
<value>myAfter2</value>
</list>
</property>
</bean>
</beans>
测试AOP的main方法代码(由于每个通知的测试代码都是一样的,所以在这里博主就不每个通知的案例上都重复加上测试类AopTest.java了,在这里附上,所有的运行结果都是这个文件,为减少文章的篇幅长度):
测试类AopTest.java:
public class AopTest {
public static void main(String[] args) {
// 获取到spring-Context.xml配置文件
ApplicationContext springContext = new ClassPathXmlApplicationContext("/spring-context.xml");
// 根据bean的id得到实现类
IBookBiz bean =(IBookBiz)springContext.getBean("bookProxy");
// 输出你得到的类的全包名(相当于类对象)
System.out.println(bean.getClass());
// 用类对象去调用里面的买书方法
boolean buy = bean.buy("寻坤","圣墟" , 66d);
// 用类对象去调用里面的发表书评
bean.comment("寻坤", "真的虚了");
}
}
在前面的这些工具类或者配置文件,博主先上了,后面我们讲案例时,我会截图哪里用到哪里,这样我们自己也不会混乱
2.1 前置通知(案例(需求):买书、评论前加系统日志)
前置通知MyMethodBeforeAdvice (要实现MethodBeforeAdvice接口):
/**
* 前置通知
* @author 晴sister
* 2020年8月9日22:51:27
*
*/
public class MyMethodBeforeAdvice implements MethodBeforeAdvice{
@Override
public void before(Method method, Object[] arg1, Object target) throws Throwable {
// TODO Auto-generated method stub
// 获取到哪个类被调用了
String clazName = target.getClass().getName();
// 获取到哪个方法被调用了
String methodName = method.getName();
// 看看带了哪些参数,如果不要Arrays.toString,打印的是一串地址
String params = Arrays.toString(arg1);
System.out.println("买书、评论前加系统日志:"+clazName+","+methodName+"("+params+")");
}
}
注意spring-context.xml的修改:此上这个文档中的内容代码已附上
运行效果:
2.2 后置通知(案例(需求):买书返利(存在bug))
后置通知MyAfterReturningAdvice (要实现AfterReturningAdvice接口):
/**
* 后置通知:
* 买书返利(存在bug)
* @author 晴sister
*2020年8月5日23:14:58
*/
public class MyAfterReturningAdvice implements AfterReturningAdvice{
@Override
public void afterReturning(Object returnValue, Method method, Object[] arg1, Object target) throws Throwable {
// TODO Auto-generated method stub
String clazName = target.getClass().getName();
String methodName = method.getName();
String params = Arrays.toString(arg1);
System.out.println("买书返利的后置通知:"+clazName+","+methodName+"("+params+")"+"\t\t目标对象方法调用后的返回值"+returnValue);
}
}
注意spring-context.xml的修改:此上这个文档中的内容代码已附上
运行效果:
2.3 环绕通知:包含前置+后置通知(案例(需求):类似拦截器,会包括切入点,目标类前后都会执行代码)
环绕通知MythodInterceptor(要实现MethodInterceptor接口):
/**
* 环绕通知
* @author 晴sister
* 2020年8月9日22:51:02
*
*/
public class MythodInterceptor implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation invacation) throws Throwable {
String clazName = invacation.getThis().getClass().getName();
String methodName = invacation.getMethod().getName();
String params = Arrays.toString(invacation.getArguments());
// sessionFactory.openSession,session.beginTransation
System.out.println("环绕通知:"+clazName+","+methodName+"("+params+")");
Object returnValue = invacation.proceed();
// transation.commit(),session.close()
System.out.println("环绕通知:\t\t目标对象方法调用后的返回值"+returnValue);
return returnValue;
}
}
注意spring-context.xml的修改:此上这个文档中的内容代码已附上
运行效果(环绕通知运行结果):
2.4 异常通知:(案例(需求):出现异常执行系统提示,然后进行处理。价格异常为例)
异常通知MyThrowsAdvice(要实现ThrowsAdvice接口):
/**
* 异常通知
* @author Administrator
* 案例:张三向李四转账
* biz.transfer(user1,user2)
* UserDao.update(user1)
* UserDao.update(user2)
*
*/
public class MyThrowsAdvice implements ThrowsAdvice {
// 处理错误的代码,比如数据说明时候回滚就在这里处理
public void afterThrowing( PriceException ex ) {
System.out.println("价格输入有误,购买失败,请重新输入!!!");
}
}
注意spring-context.xml的修改:此上这个文档中的内容代码已附上
测试类AopTest.java:
public class AopTest {
public static void main(String[] args) {
// 获取到spring-Context.xml配置文件
ApplicationContext springContext = new ClassPathXmlApplicationContext("/spring-context.xml");
// 根据bean的id得到实现类
IBookBiz bean =(IBookBiz)springContext.getBean("bookProxy");
// 输出你得到的类的全包名(相当于类对象)
System.out.println(bean.getClass());
// 用类对象去调用里面的买书方法
boolean buy = bean.buy("寻坤","圣墟" , -66d);
// 用类对象去调用里面的发表书评
// bean.comment("寻坤", "真的虚了");
}
}
运行效果(异常通知运行结果):有解决方案
2.5 过滤通知:(案例(需求):处理买书返利的bug)
重现之前后置通知(myAfter)的运行结果有一bug如下:
现利用过滤通知(MyAfter2)来升级解决这个bug:
注意spring-context.xml的修改:此上这个文档中的内容代码已附上
运行效果(过滤通知运行结果):