Spring AOP

AOP概念

AOP又称面向切面编程,是Spring中除ioc外另一个重要的内容,使用AOP可以降低代码耦合,提高代码重用性。将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

AOP中关键性概念:

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

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

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

<tx:advice id="txAdvice"
		transaction-manager="transactionManager">
		<tx:attributes>
			<tx:method name="add*" propagation="REQUIRED" />
			<tx:method name="save*" propagation="REQUIRED" />
			<tx:method name="insert*" propagation="REQUIRED" />
			<tx:method name="edit*" propagation="REQUIRED" />
			<tx:method name="update*" propagation="REQUIRED" />
			<tx:method name="del*" propagation="REQUIRED" />
			<tx:method name="remove*" propagation="REQUIRED" />
			<!-- 只读,不开启事务 -->
			<tx:method name="load*" propagation="REQUIRED"
				read-only="true" />
			<tx:method name="list*" propagation="REQUIRED"
				read-only="true" />
			<tx:method name="select*" propagation="REQUIRED"
				read-only="true" />
			<tx:method name="query*" propagation="REQUIRED"
				read-only="true" />
			<tx:method name="do*" propagation="REQUIRED" />
		</tx:attributes>
	</tx:advice>

代理(Proxy):将通知应用到目标对象后创建的对象(代理=目标+通知),只有代理对象才有AOP功能,而AOP的代码是写在通知的方法里面的;

切入点(Pointcut):多个连接点的集合,定义了通知应该应用到那些连接点;

适配器(Advisor):适配器=通知(Advice)+切入点(Pointcut)

<!--4) 定义切入点 -->
	<aop:config>
		<!-- pointcut属性用来定义一个切入点,分成四个部分理解 [* ][*..][*Biz][.*(..)] -->
		<!-- A: 返回类型,*表示返回类型不限 -->
		<!-- B: 包名,*..表示包名不限 -->
		<!-- C: 类或接口名,*Biz表示类或接口必须以Biz结尾 -->
		<!-- D: 方法名和参数,*(..)表示方法名不限,参数类型和个数不限 -->
		<aop:advisor advice-ref="txAdvice"
			pointcut="execution(* *..*Biz.*(..))" />
	</aop:config>

注意:

目标对象只负责业务逻辑代码
通知对象负责AOP代码,这二个对象都没有AOP的功能,只有代理对象才有

下面有个案例,例如项目中有一个增加方法:

add(){
		dao.add();
	}

这只是某个接口的一个实现类,如果我要给这个方法增加日志功能,那么就会在增加方法中添加具体的业务代码:

add(){
		logDao.add(session.get("current_user"),user);
		dao.add(user);
	}

那么一旦接口实现类变多,日志实现代码就会重复写,而把这些共有的业务扩展代码交由给别人,你只需要专注于实现具体的业务逻辑实现,对很多功能都有的重复的代码抽取,再在运行的时候往业务方法上动态植入“切面类代码”,这就是面向切面(aop)

案例

接口:

package com.xiaoyang.aop;

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

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

实现类:

package com.xiaoyang.aop;

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);
	}

}

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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
	<!-- aop -->
	<!-- 目标对象 -->
	<bean class="com.xiaoyang.aop.BookBizImpl" id="bookBiz"></bean>
	<!-- 前置通知 -->
	<bean class="com.xiaoyang.aop.advice.BeforeAdvice"
		id="beforeAdvice"></bean>
	<!-- 后置通知 -->
	<bean class="com.xiaoyang.aop.advice.AfterAdvice" id="afterAdvice"></bean>
	<!-- 环绕通知 -->
	<bean class="com.xiaoyang.aop.advice.NotificationAdvice"
		id="notificationAdvice"></bean>
	<!-- 异常通知 -->
	<bean class="com.xiaoyang.aop.advice.ThrowAdvice" id="throwAdvice"></bean>
	<!-- 过滤通知 -->
	<bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" id="regexpMethodPointcutAdvisor">
	<property name="advice" ref="afterAdvice"></property>
	<property name="pattern" value=".*buy"></property>
	</bean>
	<!-- 由代理工厂来组装目标对象及通知 -->
	<bean class="org.springframework.aop.framework.ProxyFactoryBean"
		id="bookProxy">
		<property name="target" ref="bookBiz"></property>
		<property name="proxyInterfaces">
			<list>
				<value>com.xiaoyang.aop.IBookBiz</value>
			</list>
		</property>
		<property name="interceptorNames">
			<list>
				<value>beforeAdvice</value>
				<!-- <value>afterAdvice</value> -->
				<value>regexpMethodPointcutAdvisor</value>
				<value>notificationAdvice</value>
				<value>throwAdvice</value>
			</list>
		</property>

	</bean>
</beans>

前置通知

对上面两个类进行前置通知的操作,在连接点之前执行的通知就是前置通知:

package com.xiaoyang.aop.advice;

import java.lang.reflect.Method;
import java.util.Arrays;

import org.springframework.aop.MethodBeforeAdvice;
/**
 * 买书、评论加系统日志
 * @author xiaoyang
 *
 */
public class BeforeAdvice implements MethodBeforeAdvice{

	@Override
	public void before(Method method, Object[] object, Object target) throws Throwable {
                String clzName=target.getClass().getName();
		        String methodName=method.getName();
		        String params=Arrays.toString(object);
		        System.out.println("前置通知,买书、评论加系统日志:"+clzName+"调用方法:"+methodName+params);
	}

}

测试一下:

package com.xiaoyang.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.xiaoyang.action.UserAction;
import com.xiaoyang.aop.IBookBiz;
public class AopTest {

	public static void main(String[] args) {
		ApplicationContext springContext = new ClassPathXmlApplicationContext("/spring-context.xml");
		IBookBiz ibookBiz = (IBookBiz) springContext.getBean("bookProxy");
		boolean buy = ibookBiz.buy("sss", "圣墟", 66d);
		ibookBiz.comment("sss", "真的虚了");
	}
}
前置通知,买书、评论加系统日志:com.xiaoyang.aop.BookBizImpl调用方法:buy[test, testName, 66.0]
test buy testName, spend 66.0

后置通知

后置通知:在连接点正常完成后执行的通知

package com.xiaoyang.aop.advice;

import java.lang.reflect.Method;
import java.util.Arrays;

import org.springframework.aop.AfterReturningAdvice;

/**
 * 后置通知
 * 
 * @author xiaoyang
 *
 */
public class AfterAdvice implements AfterReturningAdvice {

	@Override
	public void afterReturning(Object returnValue, Method method, Object[] object, Object target) throws Throwable {
		String clzName = target.getClass().getName();
		String methodName = method.getName();
		String params = Arrays.toString(object);
		System.out.println("后置通知:" + clzName + "调用方法:" + methodName + params+"\t目标对象方法调用后的返回值"+returnValue);
	}

}

后置通知:com.xiaoyang.aop.BookBizImpl调用方法:buy[test, testName, 66.0]	目标对象方法调用后的返回值true

环绕通知

实际开发中常用环绕通知代替前置和后置通知,环绕通知是两者的结合,包围一个连接点的通知,最大特点是可以修改返回值,由于它在方法前后都加入了自己的逻辑代码,因此功能异常强大。:

package com.xiaoyang.aop.advice;

import java.util.Arrays;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
 * 环绕通知
 * @author xiaoyang
 *
 */
public class NotificationAdvice implements MethodInterceptor{

	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		String clzName = invocation.getThis().getClass().getName();
		String methodName = invocation.getMethod().getName();
		String params = Arrays.toString(invocation.getArguments());
		Object returnValue = invocation.proceed();
		System.out.println("环绕通知:" + clzName + "调用方法:" + methodName + params+"\t目标对象方法调用后的返回值"+returnValue);
		return returnValue;
	}

}

环绕通知:com.xiaoyang.aop.BookBizImpl调用方法:buy[test, testName, 66.0]	目标对象方法调用后的返回值true
环绕通知:com.xiaoyang.aop.BookBizImpl调用方法:comment[test, testName环绕]	目标对象方法调用后的返回值null

异常通知

异常通知实现对异常的处理通知,这里对价格赋值为负数:

package com.xiaoyang.aop.advice;

import org.springframework.aop.ThrowsAdvice;

import com.xiaoyang.aop.PriceException;

/**
 * 异常通知
 * @author xiaoyang
 *
 */
public class ThrowAdvice implements ThrowsAdvice{

	public void afterThrowing(PriceException pe) {
		System.out.println("价格输入有误");
	}
}

package com.xiaoyang.aop;
/**
 * 异常处理类
 * @author xiaoyang
 *
 */
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);
	}
	
}

在这里插入图片描述

过滤通知

过滤通知主要通过适配器进行过滤处理(这里模仿买书返利功能处理,一般来说买书是可以返利的,但是发表书评是不能返利的,但是如果不用过滤通知处理,前置后置通知都会输出,也就是买书评论都能返利,这是bug):

<!-- 过滤通知 -->
	<bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" id="regexpMethodPointcutAdvisor">
	<property name="advice" ref="afterAdvice"></property>
	<property name="pattern" value=".*buy"></property>
	</bean>

<!-- 由代理工厂来组装目标对象及通知 -->
	<bean class="org.springframework.aop.framework.ProxyFactoryBean"
		id="bookProxy">
		<property name="target" ref="bookBiz"></property>
		<property name="proxyInterfaces">
			<list>
				<value>com.xiaoyang.aop.IBookBiz</value>
			</list>
		</property>
		<property name="interceptorNames">
			<list>
				<value>beforeAdvice</value>
				<value>regexpMethodPointcutAdvisor</value>
				<value>throwAdvice</value>
			</list>
		</property>
	</bean>
前置通知,买书、评论加系统日志:com.xiaoyang.aop.BookBizImpl调用方法:buy[test, testName, 66.0]
test buy testName, spend 66.0
后置通知:com.xiaoyang.aop.BookBizImpl调用方法:buy[test, testName, 66.0]	目标对象方法调用后的返回值true
前置通知,买书、评论加系统日志:com.xiaoyang.aop.BookBizImpl调用方法:comment[test, testName777]
test say:testName777

可以看到只有买书功能织入了后置通知,评论功能并没有后置通知,也就是返利功能。
over…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值