SSH学习之——Spring面向方面编程AOP

原创 2012年09月13日 10:50:49

一:概述

众所周知,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接口


版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

AOP编程简介及其在Spring框架中的使用

AOP编程简介及其在Spring框架中的使用额,最近一直没来逛自己的博客发现游客蛮多的:),从前段时间开始就一直在学ssh框架(已经搞定strut2和hibernate),javaweb的框架还是蛮多...

【SSH进阶之路】Spring的AOP逐层深入——AOP的基本原理(六)

AOP(Aspect Oriented Programming),意思是面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。...

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

SSH深度历险(十) AOP原理及相关概念学习+AspectJ注解方式配置spring AOP

使用AOP,我们要注意关注横切性的功能,即抽象出独立服务,进行模块化使我们以前习惯性的纵向思维的方法再改变,注意横向思考问题的方式,我们结合现在的系统可以把判断文本框一些了的验证、日志的记录、事务的开...
  • lishehe
  • lishehe
  • 2014年07月06日 12:57
  • 3568

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

【SSH进阶之路】Spring的AOP逐层深入——采用注解完成AOP(七)

采用注解方式,实现AOP,解析五类Advice的执行顺序。

SSH框架系列:Spring AOP应用记录日志Demo

1.简介 Spring 中的AOP为Aspect Oriented Programming的缩写,面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。以下是Spring AO...

SSH AOP 学习过程中遇到的问题...

首先我保证了在没加AOP实现之前,代码是能正常运行的. 然后学习SSH使用AOP在action的方法前添加权限控制,该文就是记录出现的错误信息.. 先说明用xml注解AOP起先是使用的anno...
  • kvgnt
  • kvgnt
  • 2011年02月20日 22:05
  • 2853

几个可运行的,可以说明问题的Ajax实例

1 例子1 用AJAX实现时钟  ajax.html -->html>head>script language="javascript"> function getClock(){ var XmlHt...
  • manio
  • manio
  • 2007年04月20日 21:45
  • 1130

spring 使用注解事物管理例子

1,spring 使用注解的形式进行事物管理.主要代码如下,整个项目的代码点击这里下载. service代码如下: package com.junlenet.spring.transactio...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:SSH学习之——Spring面向方面编程AOP
举报原因:
原因补充:

(最多只允许输入30个字)