spring之aop(前置通知,后置通知,环绕通知,过滤通知,异常通知)

1、AOP中关键性概念 

连接点(Joinpoint):程序执行过程中明确的点,如方法的调用,或者异常的抛出

目标(Target):被通知(被代理)的对象
注1:完成具体的业务逻辑

通知(Advice):在某个特定的连接点上执行的动作,同时Advice也是程序代码的具体实现,例如一个实现日志记录的代码(通知有些书上也称为处理)
注2:完成切面编程

代理(Proxy):将通知应用到目标对象后创建的对象(代理=目标+通知)
例子:外科医生+护士
注3:只有代理对象才有AOP功能,而AOP的代码是写在通知的方法里面的

切入点(Pointcut):多个连接点的集合,定义了通知应该应用到那些连接点
(也将Pointcut理解成一个条件 ,此条件决定了容器在什么情况下将通知和目标组合成代理返回给外部程序)
 
适配器(Advisor):适配器=通知(Advice)+切入点(Pointcut)

看图便于理解:

2.、AOP的核心点(通知、实现接口与应用场景)

通知类型    实现接口                                               应用场景

前置通知    实现org.springframework.aop.MethodBeforeAdvice接口     买书、评论前加系统日志

后置通知    实现org.springframework.aop.AfterReturningAdvice接口   买书返利

环绕通知    实现org.aopalliance.intercept.MethodInterceptor接口    类似拦截器,会包括切入点,目标类前后都会执行代码

异常通知    实现org.springframework.aop.ThrowsAdvice接口           出现异常执行系统提示,然后进行处理。价格异常为例

过滤通知(适配器)    实现org.springframework.aop.support.RegexpMethodPointcutAdvisor接口   处理买书返利的bug

一、准备工作及用到的工具类

接口类IBookBiz:

package com.lgs.aop.biz;

public interface IBookBiz {
	// 购书
	public boolean buy(String userName, String bookName, Double price);

	// 发表书评
	public void comment(String userName, String comments);
}

接口类IBookBiz的实现类BookBizImpl:

package com.lgs.aop.biz;

import com.lgs.aop.exception.PriceException;

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.lgs.aop.exception;

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"
	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">

<!-- 本文件中配置整个项目中包含的所有的JavaBean,目的在于spring项目的统一管理 -->

<!-- <bean name="userBiz1" class="com.lgs.ioc.biz.impl.UserBizImpl1"></bean> -->
<bean name="userBiz2" class="com.lgs.ioc.biz.impl.UserBizImpl1"></bean>
<bean name="userBiz1" class="com.lgs.ioc.biz.impl.UserBizImpl2"></bean>

<bean name="personAction" class="com.lgs.ioc.web.PersonAction">
	<property name="userBiz" ref="userBiz2"></property>
</bean>

<bean name="userAction1" class="com.lgs.ioc.web.UserAction1">
	<property name="userBiz" ref="userBiz1"></property>
</bean>
<bean name="userAction2" class="com.lgs.ioc.web.UserAction2">
	<property name="userBiz" ref="userBiz1"></property>
</bean>
<bean name="userAction3" class="com.lgs.ioc.web.UserAction3">
	<property name="userBiz" ref="userBiz1"></property>
</bean>

<bean name="paramAction" class="com.lgs.ioc.web.ParamAction">
<!-- 	<property name="name" value="鲁迅"></property>
	<property name="age" value="34"></property>
	<property name="hobby">
		<list>
			<value>学医</value>
			<value>白话文学</value>
			<value>作家</value>
		</list>
	</property> -->
	<constructor-arg name="name" value="武则天"></constructor-arg>
	<constructor-arg name="age" value="22"></constructor-arg>
	<constructor-arg name="hobby">
		<list>
			<value>跳舞</value>
			<value>唱歌</value>
			<value>当皇帝</value>
		</list>
	</constructor-arg>
</bean>

<!-- aop -->

<!-- 目标 -->
<bean name="bookBiz" class="com.lgs.aop.biz.BookBizImpl"></bean>
<!-- 前置通知 -->
<bean name="myBefore" class="com.lgs.aop.advice.MyMethodBeforeAdvice"></bean>
<!-- 后置通知 -->
<bean name="myAfter" class="com.lgs.aop.advice.MyAfterReturningAdvice"></bean>
<!-- 环绕通知 -->
<bean name="myFilterAdvice" class="com.lgs.aop.advice.MyMethodInterceptor"></bean>
<!-- 异常通知 -->
<bean name="myExceptionAdvice" class="com.lgs.aop.advice.MyThrowsAdvice"></bean>

<!-- 过滤通知 -->
<!-- org.springframework.aop.support.RegexpMethodPointcutAdvisor 这个类是固定的   myAfter2:过滤的是后置通知 -->
<bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" id="myAfter2">
	<!-- 过滤的哪个通知,选择过滤的是后置通知  -->
	<property name="advice" ref="myAfter"></property>
	<!-- .*buy:[.*]任意字符0~n个,以buy结尾的方法 -->
	<property name="patterns">
		<list>
			<!-- 过滤掉了buy方法 -->
			<value>.*buy</value>
		</list>
		</property>
</bean>

<!-- 生成代理(目标对象+通知) -->
<!--  org.springframework.aop.framework.ProxyFactoryBean:代理工厂   proxyFactoryBean:代理对象-->
	<bean class="org.springframework.aop.framework.ProxyFactoryBean"
		id="proxyFactoryBean">
		<property name="target" ref="bookBiz"></property>
		<!-- 代理工厂生产的代理需要实现的接口列表 -->
		<property name="proxyInterfaces">
			<list>
				<value>com.lgs.aop.biz.IBookBiz</value>
			</list>
		</property>
		<!-- 代理通知列表 -->
		<property name="interceptorNames">
			<list>
				<value>myBefore</value>
				<!-- <value>myAfter</value> -->
				<value>myAfter2</value>
				<value>myFilterAdvice</value>
				<value>myExceptionAdvice</value>
			</list>
		</property>
	</bean>

</beans>

测试类AopTest.java:(几个通知案例测试类基本相同,有变动我会截图展示出来的)

package com.lgs.aop.test;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.lgs.aop.biz.IBookBiz;

public class AopTest {
	public static void main(String[] args) {
//		获取到spring-Context.xml配置文件
		ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring-context.xml");
//		根据bean的id得到实现类
//		IBookBiz bookBiz = (IBookBiz) applicationContext.getBean("bookBiz");
//		调用前置通知
		IBookBiz bookBiz = (IBookBiz) applicationContext.getBean("proxyFactoryBean");
//		买书
		bookBiz.buy("迪迦", "《光的力量》", 33d);
//		评论
		bookBiz.comment("迪迦", "我的力量源泉");
	}
}

二、前置通知

前置通知MyMethodBeforeAdvice (要实现MethodBeforeAdvice接口):

package com.lgs.aop.advice;
import java.util.Arrays;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
/**
 * 前置通知
 */
public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
/**
 * 实现接口,实现方法
 * target:目标对象
 * method:被触发目标对象的方法
 * args:目标对象的目标方法的携带的参数
 */
	public void before(Method method, Object[] args, Object target) throws Throwable {
		String targetName = target.getClass().getName();
		String methodName = method.getName();
		String params = Arrays.toString(args);
		String msg = "【系统日志】:正在调用->"+targetName+"."+methodName+",携带的参数:"+params;
		System.out.println(msg);
	}
}

使用前置通知需先进行配置,和一些改动(配置和改动如下)spring-context.xml

运行结果:

暗暗

三、后置通知

后置通知MyAfterReturningAdvice (要实现AfterReturningAdvice接口):相比于前置多了一个参数,返回值

package com.lgs.aop.advice;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.springframework.aop.AfterReturningAdvice;
/**
 * 后置通知
 */
public class MyAfterReturningAdvice implements AfterReturningAdvice {
	public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
		String targetName = target.getClass().getName();
		String methodName = method.getName();
		String params = Arrays.toString(args);
		String msg = "【返利通知:返利3元】:正在调用->" + targetName + "." + methodName + ",携带的参数:" + params + ";目标对象所调用的方法的返回值:"
				+ returnValue;
		System.out.println(msg);
	}
}

配置spring-context.xml

调用测试AopTest.java  运行结果如下

暗暗

四、环绕通知:包含前置+后置通知

环绕通知MythodInterceptor(要实现MethodInterceptor接口):

package com.lgs.aop.advice;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
 * 环绕通知 
 */
public class MyMethodInterceptor implements MethodInterceptor {
	public Object invoke(MethodInvocation invocation) throws Throwable {
		Object target = invocation.getThis();
		Method method = invocation.getMethod();
		Object[] args = invocation.getArguments();
		// a.jsp window.open(b.jsp)
		// b.jsp xxx->返回object->b.jsp window.close();window.getArguments;
		String targetName = target.getClass().getName();
		String methodName = method.getName();
		String params = Arrays.toString(args);
		String msg = "【环绕通知】:正在调用->" + targetName + "." + methodName + ",携带的参数:" + params;
		System.out.println(msg);
		//invocation可以执行被代理的目标对象的业务方法
		//过滤器    chain.dofilter:放行
		Object returnValue = invocation.proceed();
		String msg2 = "【环绕通知】:目标对象所调用的方法的返回值:" + returnValue;
		System.out.println(msg2);
		return returnValue;
	}
}

配置spring-context.xml

调用测试AopTest.java  运行结果如下

五、异常通知

假如出现异常,会直接终止程序,不会在进行数据回滚 

异常通知MyThrowsAdvice(要实现ThrowsAdvice接口):(要么同时成功,要么同时失败)

package com.lgs.aop.advice;
import org.springframework.aop.ThrowsAdvice;
import com.lgs.aop.exception.PriceException;
/**
 * 异常通知
 */
public class MyThrowsAdvice implements ThrowsAdvice {
	public void afterThrowing( PriceException ex ) {
		System.out.println("价格输入有误,购买失败,请重新输入!!!");
	}
}

配置spring-context.xml

 对测试类AopTest.java 进行修改,制造异常数据(-33d)

package com.lgs.aop.test;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.lgs.aop.biz.IBookBiz;

public class AopTest {
	public static void main(String[] args) {
//		获取到spring-Context.xml配置文件
		ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring-context.xml");
//		根据bean的id得到实现类
//		IBookBiz bookBiz = (IBookBiz) applicationContext.getBean("bookBiz");
//		调用前置通知
		IBookBiz bookBiz = (IBookBiz) applicationContext.getBean("proxyFactoryBean");
//		买书
		bookBiz.buy("迪迦", "《光的力量》", -33d);
//		评论
//		bookBiz.comment("迪迦", "我的力量源泉");
	}
}

 运行结果如下(会有提示,出现解决方案)

六、过滤通知

过滤通知:并不是每个方法都需要通知,所有需要过滤一下

配置spring-context.xml(过滤通知替代后置通知) 

运行结果:

OK!到这就结束了  希望能帮到你!!! 


版权声明:本文为m0_58525944原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。