一:概述

众所周知,Spring是一个轻量级的、非侵入式的、独立于各种应用服务器的开源框架。它的两大方面被人们所熟知,也应用很广。那就是IOC(控制反转)和AOP(面向方面编程)。

IOC是开发者不创建对象,但是描述创建它们的方式,对象由Spring容器根据描述来产生对象,这里特别需要指出的是Spring是依赖于接口编程的,所以描述创建对象时,改对象必须实现于对应的接口

AOP允许开发者对横切关注点或横切典型的职责分界线的行为(例如日志和事务管理)进行模块化。AOP 的核心构造是方面,它将那些影响多个类的行为封装到可重用的模块中。

二:实现

下面针对AOP来进行调试研究,Spring实现AOP有注解和非注解两种方式,这里我们采用注册时记录日志为案例来分别实现这两种方式。


首先我们创建业务,RegistService接口和RegistServiceImpl实现类

package org.cyxl.spring.aop;

public interface RegistService {
	void add(String name);
}

package org.cyxl.spring.aop;

public class RegistServiceImpl implements RegistService {

	public void add(String name) {
		System.out.println(name+" add...");
		throw new RuntimeException("runtime exception happened...");
	}

}

接下来实现非注解方式

第一步:定义日志切面类LogAspect

package org.cyxl.spring.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 日志切面类
 * 
 */
public class LogAspect {
	// 任何通知方法都可以将第一个参数定义为 org.aspectj.lang.JoinPoint类型
	public void before(JoinPoint call) {
		String clazz = call.getTarget().getClass().getName();
		// 获取目标对象上正在执行的方法名
		String methodName = call.getSignature().getName();
		System.out.println("前置通知:" + clazz + "类的" + methodName + "方法开始了...");
	}

	public void afterReturn() {
		System.out.println("后置通知:方法正常结束...");
	}

	public void after() {
		System.out.println("最终通知:不管方法有没有正常执行完成,一定会返回的...");
	}

	public void afterThrowing() {
		System.out.println("异常抛出后通知:方法执行时出现异常...");
	}

	// 用来做环绕通知的方法可以第一个参数定义为org.aspectj.lang.ProceedingJoinPoint类型
	public Object doAround(ProceedingJoinPoint call) throws Throwable {
		Object result = null;
		this.before(call);// 相当于前置通知
		try {
			result = call.proceed();
			this.afterReturn(); // 相当于后置通知
		} catch (Throwable e) {
			this.afterThrowing(); // 相当于异常抛出后通知
			throw e;
		} finally {
			this.after(); // 相当于最终通知
		}
		return result;
	}
}
第二步:添加Spring的支持,这里可以采用工具(如myeclipse)或者手动两种方式添加,我这里采用myeclipse添加Spring的支持,勾选Spring的core和aop两方面的jar库文件,添加的同时创建applicationContext.xml

第三步:添加spring配置文件applicationContext.xml对aop的支持。自动创建的配置文件没有包含aop的支持,所以我们在beans标签的属性中添加spring对aop的支持,如下

<beans
	xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
	http://www.springframework.org/schema/aop 
	http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
看看里面有aop字眼的配置就应该明白了

第四步:配置applicationContext.xml文件,首先创建业务方法对象

<bean id="registService" class="org.cyxl.spring.aop.RegistServiceImpl">
	</bean>
然后创建日志切面类对象

 <bean id="logAspectBean" class="org.cyxl.spring.aop.LogAspect"/>
最后配置aop

<aop:config>  
        <!-- 第2步:配置一个切面 -->  
        <aop:aspect id="logAspect" ref="logAspectBean">  
            <!-- 第3步:定义切入点,指定切入点表达式 -->  
            <aop:pointcut id="allMethod"   
                expression="execution(* org.cyxl.spring.aop.*.*(..))"/>   
            <!-- 第4步:应用前置通知 -->  
            <aop:before method="before" pointcut-ref="allMethod" />  
            <!-- 第4步:应用后置通知 -->  
            <aop:after-returning method="afterReturn" pointcut-ref="allMethod"/>  
            <!-- 第4步:应用最终通知 -->  
            <aop:after method="after" pointcut-ref="allMethod"/>  
            <!-- 第4步:应用抛出异常后通知 -->  
            <aop:after-throwing method="afterThrowing" pointcut-ref="allMethod"/>  
        </aop:aspect>  
    </aop:config> 

上面采用对各种通知分别配置的方式,我们也可以采用环绕通知的方式一次性配置所有的通知、

<aop:config>  
        <!-- 第2步:配置一个切面 -->  
        <aop:aspect id="logAspect" ref="logAspectBean">  
            <!-- 第3步:定义切入点,指定切入点表达式 -->  
            <aop:pointcut id="allMethod"   
                expression="execution(* org.cyxl.spring.aop.*.*(..))"/>     
            <!-- 第4步:应用环绕通知 -->  
            <aop:around method="doAround" pointcut-ref="allMethod" /> 
        </aop:aspect>  
    </aop:config> 

这里<aop:pointcut>中的expression属性起到了关键性的作用,它定义了哪些对象的哪些方法执行时会去执行日志记录

第五步:测试结果,创建测试类

package org.cyxl.spring.aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
		RegistService service=(RegistService)context.getBean("registService");
		try
		{
			service.add("cyxl");
		}
		catch(Exception e){
			
		}
		
	}

}

输出结果

前置通知:org.cyxl.spring.aop.RegistServiceImpl类的add方法开始了...
cyxl add...
最终通知:不管方法有没有正常执行完成,一定会返回的...
异常抛出后通知:方法执行时出现异常...

当然你也可以试试将故意抛出的异常去掉,这样就会得到结果如下

前置通知:org.cyxl.spring.aop.RegistServiceImpl类的add方法开始了...
cyxl add...
后置通知:方法正常结束...
最终通知:不管方法有没有正常执行完成,一定会返回的...

非注解的方式大致如此,下面采用注解的方式实现一遍

第一步:定义采用注解方式的日志切面类

package org.cyxl.spring.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Pointcut;

/**
 * 日志切面类
 */
@Aspect
// 定义切面类
public class LogAnnotationAspect {
	@SuppressWarnings("unused")
	// 定义切入点,提供一个方法,这个方法的名字就是改切入点的id
	@Pointcut("execution(* org.cyxl.spring.aop.*.*(..))")
	private void allMethod() {
	}

	// 针对指定的切入点表达式选择的切入点应用前置通知
	@Before("execution(* org.cyxl.spring.aop.*.*(..))")
	public void before(JoinPoint call) {
		String className = call.getTarget().getClass().getName();
		String methodName = call.getSignature().getName();
		System.out.println("【前置通知(Annotation)】:" + className + "类的" + methodName
				+ "方法开始了...");
	}

	// 访问命名切入点来应用后置通知
	@AfterReturning("allMethod()")
	public void afterReturn() {
		System.out.println("【后置通知(Annotation)】:方法正常结束...");
	}

	// 应用最终通知
	@After("allMethod()")
	public void after() {
		System.out.println("【最终通知(Annotation)】:不管方法有没有正常执行完成,一定会返回的...");
	}

	// 应用异常抛出后通知
	@AfterThrowing("allMethod()")
	public void afterThrowing() {
		System.out.println("【异常抛出后通知(Annotation)】:方法执行时出现异常...");
	}

	// 应用周围通知
	// @Around("allMethod()")
	public Object doAround(ProceedingJoinPoint call) throws Throwable {
		Object result = null;
		this.before(call);// 相当于前置通知
		try {
			result = call.proceed();
			this.afterReturn(); // 相当于后置通知
		} catch (Throwable e) {
			this.afterThrowing(); // 相当于异常抛出后通知
			throw e;
		} finally {
			this.after(); // 相当于最终通知
		}
		return result;
	}
}

这里和非注解方式的差不多,只是用一些annotation来赋予了每个方法的职能

第二步:配置applicationContext.xml,定义注解方式的切面类的对象

<bean id="logAspectAnnotationBean" class="org.cyxl.spring.aop.LogAnnotationAspect"/> 
第三步:配置applicationContext.xml来添加采用注解方式的aop实现

<aop:aspectj-autoproxy/>

第四步:测试结果。测试类不变,结果如下

【前置通知(Annotation)】:org.cyxl.spring.aop.RegistServiceImpl类的add方法开始了...
cyxl add...
【异常抛出后通知(Annotation)】:方法执行时出现异常...
【最终通知(Annotation)】:不管方法有没有正常执行完成,一定会返回的...

三:总结

1、采用注解的方式在配置方面简化了,但在理解方面就显得相对复杂些,必须结合具体的实现类加以理解

2、这两种方式可以同时使用,结果也是叠加的,如整体配置文件如下

<?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:p="http://www.springframework.org/schema/p"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
	http://www.springframework.org/schema/aop 
	http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

	<bean id="registService" class="org.cyxl.spring.aop.RegistServiceImpl">
	</bean>  
    <!-- 日志切面类 -->  
    <bean id="logAspectBean" class="org.cyxl.spring.aop.LogAspect"/>  
    <!-- 日志切面类Annotation实现 -->  
    <bean id="logAspectAnnotationBean" class="org.cyxl.spring.aop.LogAnnotationAspect"/> 
    <!-- 第1步: AOP的配置 -->  
    <aop:config>  
        <!-- 第2步:配置一个切面 -->  
        <aop:aspect id="logAspect" ref="logAspectBean">  
            <!-- 第3步:定义切入点,指定切入点表达式 -->  
            <aop:pointcut id="allMethod"   
                expression="execution(* org.cyxl.spring.aop.*.*(..))"/>   
            <!-- 第4步:应用前置通知 -->  
            <aop:before method="before" pointcut-ref="allMethod" />  
            <!-- 第4步:应用后置通知 -->  
            <aop:after-returning method="afterReturn" pointcut-ref="allMethod"/>  
            <!-- 第4步:应用最终通知 -->  
            <aop:after method="after" pointcut-ref="allMethod"/>  
            <!-- 第4步:应用抛出异常后通知 -->  
            <aop:after-throwing method="afterThrowing" pointcut-ref="allMethod"/>  
            <!-- 第4步:应用环绕通知 -->  
            <!--  
            <aop:around method="doAround" pointcut-ref="allMethod" /> 
             -->  
        </aop:aspect>  
    </aop:config> 
    
    <!-- 启用spring对AspectJ注解的支持 -->  
    <aop:aspectj-autoproxy/>
</beans>

运行测试,结果如下

前置通知:org.cyxl.spring.aop.RegistServiceImpl类的add方法开始了...
【前置通知(Annotation)】:org.cyxl.spring.aop.RegistServiceImpl类的add方法开始了...
cyxl add...
【异常抛出后通知(Annotation)】:方法执行时出现异常...
【最终通知(Annotation)】:不管方法有没有正常执行完成,一定会返回的...
最终通知:不管方法有没有正常执行完成,一定会返回的...
异常抛出后通知:方法执行时出现异常...

3、理解Spring中的AOP代理可以从理解JDK的动态代理开始,也就是某个类去实现 java.lang.reflect.InvocationHandler接口


Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐