4-Spring框架 之 AOP

    1. AOP简介

AOP(Aspect Orient Programming),面向切面编程,是面向对象编程(OOP)的一种补充,面向对象编程是从静态角度考虑程序的结构,而面向切面编程是从动态角度考虑程序运行过程。

 

AOP底层,就是采用动态代理模式实现的。采用了两种代理:JDK的动态代理,与CGLIB的动态代理。

 

面向切面编程,就是将交叉 业务逻辑封装称切面,利用AOP容器的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、事务、日志等。

 

若不使用AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样,会使主业务逻辑变的混杂不清。

 

      1. AOP编程的术语
  1. 切面(Aspect)

切面泛指交叉业务逻辑。常用的切面有通知与顾问。实际就是对主业务逻辑的一种增强。

 

  1. 织入(Weaving)

织入就是指将切面代码插入到目标对象的过程。

 

  1. 连接点(JoinPoint)

通常业务接口中的方法均为连接点

 

  1. 切入点(Pointcut)

切入点指切面具体织入的方法。在StudentServiceImpl类中,若doSome()将被增强,而doOther()不被增强,则doSome()为切入点,而doOther()仅为连接点。

被标记为final的方法不可作为切入点。

 

  1. 目标对象(Target)

指将要被增强的对象,即包含主业务逻辑的类的对象。

 

  1. 通知(Advice)

通知是切面的一种实现,可以完成简单织入的功能(织入功能就是在这里完成的)。换个角度说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。

切入点定义切入的位置,通知定义切入的时间。

  1. 顾问(Advisor)

切面的另一种实现,是将通知包装为更复杂切面的装配器。

1.2 通知详解

1.2.1 前置通知MethodBeforeAdvice

定义前置通知,需要实现MethodBeforeAdvice接口,该接口中有一个方法before(),会在目标方法执行之前执行。前置通知的特点:

  1. 在目标方法执行之前执行
  2. 不改变目标方法的执行流程,前置通知代码不能阻止目标方法执行
  3. 不改变目标方法执行的结果

 

 

 

1.2.2 后置通知AfterReturningAdvice

定义后置通知,需要实现借楼AfterReturningAdvice。该接口中有一个方法afterReturning(),会在目标方法执行之后执行。后置通知的特点:

  1. 在目标方法执行之后执行
  2. 不改变目标方法执行的流程,后置通知代码不能阻止目标方法的执行
  3. 不改变目标方法执行的结果
import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;

//后置通知:可以获取到目标方法的返回值,但无法改变目标方法的结果
public class MyAfterReturningAdvice implements AfterReturningAdvice {

	//returnValue:目标方法的返回值
	@Override
	public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
		System.out.println("目标方法执行之后执行。。。。");
		if(returnValue!=null){
			returnValue = ((String)returnValue).toUpperCase();
		}
		System.out.println("returnValue = "+returnValue);
	}

}

1.2.3 环绕通知 MethodInterceptor

定义环绕通知,需要实现MethodInterceptor接口。环绕通知也叫方法拦截器,可以在目标方法执行之前之后做处理,可以改变目标方法的的返回值,也可以改变程序执行流程。

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

//环绕通知
public class MyMethodInterceptor implements MethodInterceptor {

	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		
		System.out.println("执行环绕通知:目标方法执行之前");
		
		//执行目标方法,result为目标方法的返回值
		Object result = invocation.proceed();
		System.out.println("执行环绕通知:目标方法执行之后");
		return result;
	}

}

 1.2.4 异常通知 ThrowsAdvice

package edu.sdut.service;

import org.springframework.aop.ThrowsAdvice;

//异常通知
public class MyThrowsAdvice implements ThrowsAdvice {
	
	/**
	 * 当目标方法抛出与指定类型的异常具有is-a关系的异常时,
	 * 执行当前方法
	 * @param ex
	 */
	public void afterThrowing(Exception ex){
		System.out.println("执行异常通知方法。。。");
	}
}

1.3顾问 Advisor

通知(Advice)是Spring提供的一种切面(Aspect)。但其功能过于简单:只能将切面织入到目标类的所有目标方法中,无法完成将切面织入到指定的目标方法中。

顾问(Advisor)是Spring提供的另一种切面,其可以完成更为复杂的切面织入功能。

PointcutAdvisor是顾问的一种,可以指定具体的切入点。顾问将通知进行了包装,会根据不同的通知类型,在不同的时间点,将切面织入到不同的切入点。

PointcutAdvisor接口有两个较为常用的实现类:

  1. NameMatchMethodPointcutAdvisor名称匹配方法切入点顾问。
  2. RegexpMethodPointcutAdvisor正则表达式匹配方法切入点顾问。

 

1.3.1名称匹配方法切入点顾问

NameMatchMethodPointcutAdvisor,即名称匹配方法切入点顾问。容器可根据配置文件中指定的方法名来设置切入点。

代码不同修改,只在配置文件中注册一个顾问,然后使用通知属性advice与切入点的方法名mapperName对其进行配置。代理中的切面,使用这个给顾问即可。

以上代码存在的两个问题

  1. 若存在多个目标对象,就需要使用多次ProxyFactoryBean来创建多个代理对象,这会使配置文件变得臃肿,不便于管理
  2. 用户真正想调用的是目标对象,而真正可以调用的却是代理对象,这不符合逻辑。

以上连个原因是由于ProxyFactoryBean功能太简单,解决办法为使用自动代理生成器。

 

 

 

1.4 自动代理生成器

前面代码中所使用的代理对象,均是由ProxyFactoryBean代理工具类生成的。而该代理工具类存在如下缺点:

  1. 一个代理对象只能代理一个Bean,即如果有两个Bean同时要织入同一个切面,这时,不仅要配置这两个Bean,即两个目标对象,同时还要配置两个代理对象。
  2. 在客户类中获取Bean时,使用的是代理类的id,而非我们定义的目标对象Bean的id。我们正真想要执行的应该是目标对象。

 

Spring提供了自动代理生成器,用于解决PeroxyFactoryBean的问题。常用的自动代理生成器有两个:

  1. 默认advisor自动代理生成器

存在的问题:

 

(2)Bean名称自动代理生成器

  1. 不能选择目标对象
  2. 不能选择切面类型,切面只能时Advisor
  3. 不能选择advisor,所以advisor均将被作为切面织入到目标方法

需要注意的是,自动代理生成器均继承自Bean后处理器BeanPostProcessor。容器中所有Bean在初始化时均会自动执行Bean后处理器中的方法,故其无需id属性。所以自动代理生成器的Bean也没有id属性,客户类直接使用目标对象Bean的id。\

1.5 AspectJ对AOP的实现

 

对于AOP这种编程思想,跟读框架都进行了实现。Spring就是其中之一,可以完成面向切面编程。然而,AspectJ也实现了AOP的功能,且其实现方法更为简捷,使用更为方便,而且还支持注解式开发,所以,Spring又将AspectJ的对于AOP的实现也引入到了自己的框架中。

 

在Spring中使用AOP开发时,一般使用AspectJ的实现方式。

1.5.1 AspectJ的通知类型

AspectJ中常用的五种通知类型:

  1. 前置通知
  2. 后置通知
  3. 环绕通知
  4. 异常通知
  5. 最终通知

其中最终通知是指,无论程序执行是否正常,该通知都会执行。类似于try..catch中的finally代码块。

 

1.5.2 AspectJ的切入点表达式

execution([modifiers-pattern] 访问权限类型

ret-type-pattern   返回值类型

[declaring-type-parrern] 全限定性类名

Name-pattern(param-pattern) 方法名参数()

[throws-pattrtn] 抛出异常类型)

 

切入点表达式要匹配的对象就是目标方法的方法名。所以,execution表达式中明显就是方法的签名。注意,表达式中加[]的部分表示可以省略部分,各部分间用空格分开。在其中可以使用一下符号:

符号

意义

*  

零至多个任意字符

..

用在方法参数中,表示任意多个参数

用在包名后,表示当前包及其子包路径

+

用在类名后,表示当前类及其子类

用在接口后,表示当前接口及其实现类

 

 

1.5.3 AspectJ的开发环境

(1)增加4个Jar包:aop,AspectJ,AspectJ和Spring整合的jar包,Aop联盟的jar包

(2)在配置文件中添加aop的约束,因为要用到aop的标签

 

基于注解实现:

/**
 * 使用AspectJ,实现切面编程
 */
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect //表示当前类为注册
public class MyAspect {
	
	/**
	 * 执行前置通知
	 * @param jp
	 */
	@Before("execution(* *..ISomeService.doFirst(..))")
	public void myBefore(JoinPoint jp){
		
		//jp表示切入点表达式
		
		System.out.println("执行前置通知方法jp = " + jp);
	}
	
	/**
	 * 执行后置通知
	 */
	@AfterReturning(value="execution(* *..ISomeService.doSecond(..))",returning="result")
	public void myAfterReturening(Object result){
		//result为返回结果
		System.out.println("执行后置通知方法 result = " + result);
	}
	
	/**
	 * 执行环绕通知
	 * @throws Throwable 
	 */
	@Around("execution(* *..ISomeService.doSecond(..))")
	public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
		System.out.println("执行环绕通知方法,目标方法执行之前");
		//执行目标方法
		Object result = pjp.proceed();
		System.out.println("执行环绕通知方法,目标方法执行之后");
		//因为该通知可以返回值,所以可在在这里对结果及进行修改
		if(result!=null){
			result = ((String)result).toUpperCase();
		}
		return result;
	}
	
	/**
	 * 执行异常通知
	 */
	@AfterThrowing("execution(* *..ISomeService.doThird(..))")
	public void myAfterThrowing(){
		System.out.println("执行异常通知");
	}
	
	/**
	 * 执行自定义异常,并捕获信息
	 * @param ex
	 */
	@AfterThrowing(value="execution(* *..ISomeService.doThird(..))",throwing="ex")
	public void myAfterThrowing(Exception ex){
		System.out.println("执行异常通知方法 ex = " + ex.getMessage());
	}
	
	/**
	 * 执行最终通知
	 */
	@After("doThirdPointcut()")
	public void myAfter(){
		System.out.println("执行最终通知");
	}
	
	/**
	 * 定义了一个切入点
	 * 叫doThirdPointcut()
	 */
	@Pointcut("execution(* *..ISomeService.doThird(..))")
	public void doThirdPointcut(){}
	
}
<?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 -->
        
   <!-- 注册切面 -->
   <bean id="myAspect" class="edu.sdut.service.MyAspect"/>
   <bean id="someService" class="edu.sdut.service.SomeServiceIMPL"></bean>
   
   <!-- 注册aspectj的自动代理 -->
   <aop:aspectj-autoproxy/>  
</beans>

基于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 -->
        
   <!-- 注册切面 -->
   <bean id="myAspect" class="edu.sdut.service.MyAspect"/>
   <bean id="someService" class="edu.sdut.service.SomeServiceIMPL"></bean>
   
   <!-- AOP配置 -->
   <aop:config>
   	<aop:pointcut expression="execution(* *..ISomeService.doFirst(..))" id="doFirstPointCut"/>
   	<aop:pointcut expression="execution(* *..ISomeService.doSecond(..))" id="doSecondPointCut"/>
   	<aop:aspect ref="myAspect">
   		<aop:before method="myBefore" pointcut="execution(* *..ISomeService.doFirst(..))"/>
   		<aop:after-returning method="myAfterReturening" pointcut-ref="doFirstPointCut" returning="result"/>
   		<aop:around method="myAround" pointcut-ref="doSecondPointCut"/>
   		<aop:after-throwing method="myAfterThrowing(java.lang.Exception)" pointcut-ref="doSecondPointCut" throwing="ex"/>
   		<aop:after method="myAfter" pointcut-ref="doSecondPointCut"/>
   	</aop:aspect>
   </aop:config>
</beans>
/**
 * 使用AspectJ,实现切面编程
 */
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

public class MyAspect {
	
	/**
	 * 执行前置通知
	 * @param jp
	 */
	public void myBefore(JoinPoint jp){
		
		//jp表示切入点表达式
		
		System.out.println("执行前置通知方法jp = " + jp);
	}
	
	/**
	 * 执行后置通知
	 */
	public void myAfterReturening(Object result){
		//result为返回结果
		System.out.println("执行后置通知方法 result = " + result);
	}
	
	/**
	 * 执行环绕通知
	 * @throws Throwable 
	 */
	public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
		System.out.println("执行环绕通知方法,目标方法执行之前");
		//执行目标方法
		Object result = pjp.proceed();
		System.out.println("执行环绕通知方法,目标方法执行之后");
		//因为该通知可以返回值,所以可在在这里对结果及进行修改
		if(result!=null){
			result = ((String)result).toUpperCase();
		}
		return result;
	}
	
	/**
	 * 执行异常通知
	 */
	public void myAfterThrowing(){
		System.out.println("执行异常通知");
	}
	
	/**
	 * 执行自定义异常,并捕获信息
	 * @param ex
	 */
	public void myAfterThrowing(Exception ex){
		System.out.println("执行异常通知方法 ex = " + ex.getMessage());
	}
	
	/**
	 * 执行最终通知
	 */

	public void myAfter(){
		System.out.println("执行最终通知");
	}
}

猜你喜欢

转载自blog.csdn.net/txgANG/article/details/81158499