第十九章 Spring框架的AOP(Spring Framework3.1教程)

 Spring框架的一个关键组建是面向切面的编程(AOP)框架。面向切面的编程需要把程序的逻辑分解为不同的部分叫做关注点。这个功能跨越应用的多个点叫做横切关注点,并且这些横切关注点是从应用的业务逻辑的概念区分开来。有很多不同的公共的切面的例子如logging、auditing、declarative transactions、security、cache等。

OOP的基本单元模块是类,然而AOP的基本模块单元是方面。依赖注入帮助解耦你的应用对象并且AOP帮助你解耦他们影响的横切关注点对象。AOP像触发器在编程中,如Perl、.NET、Java和其他语言。

         Spring AOP模块提供了一个拦截器来拦截应用,例如,当一个方法执行,你可以增加额外的功能在这个方法执行前/后。

AOP术语

在我们开始SOP工作之前,让我们熟悉一下AOP的概念和术语。这些术语不特定于Spring,而是有关AOP。

术语                    

描述

方面                 

一个模块有一组APIs提供了跨越领域的需求。例如,一个日志模块叫做在日志记录上的AOP方面。一个应用可以有任意多个方面,这依赖于它的需要。

连接点

这个表示一个点在你的应用中你可以插入AOP方面。你可以说, 这是应用中使用Spring AOP框架插入行为的地方。                

通知

这是实际采取的行为,它在方法执行前/后。这实际是一个在程序执行时被Spring AOP框架调用的代码块。

切点

这是一组有一个或多个连接点通知将被执行。你能通过表达式或者模式指明切点,我们将在AOP的例子中看到。

引入

引入允许你给一个存在的类引入一个新的方法或属性。

目标对象

一个对象被一个或多个方面通知,这个对象通常是一个代理对象。通常建议称作通知对象(advised object)。

织入

织入是连接其他应用类型或者对象的方面去创建一个通知对象。可以在编译、加载或运行时完成。

通知类型

Spring 方面可以使用下面提及的五中通知工作:

通知

描述

Before(前)  

方法执行前运行通知。

after (后)

方法执行后运行通知,不管它的结果。

after-returning (返回后)

只在方法执行成功后运行通知。

after-throwing (抛出异常后)

只在方法执行抛出异常后运行通知。

around (环绕)

通知方法执行前和后运行通知。

实现自定义方面

Spring支持 @AspectJ注解风格的方法和基于schema风格方法实现定制方面。这两种方法将在下面两个子章节详细讲解。

方法

描述

基于XML模式

方面被实现通过通常的类附加基于XML的配置。

基于@AspectJ注解

@AspectJ引入了一个声明方面的风格随着普通的Java类带有Java 5注解。

Spring基于XML模式的AOP

本节为了使用aop命名空间标签,你需要导入spring-aop模式作为描述如下:

<?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-3.0.xsd 
       http://www.springframework.org/schema/aop 
       http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">

	<!-- bean definition & AOP specific configuration -->

</beans>

你的应用的类路径下需要如下的AspectJ库。这个库可以在AspectJ目录‘lib’目录下找到,否则你可以从internet上下载。

    * aspectjrt.jar

    * aspectjweaver.jar

    * aspectj.jar

声明一个方面

方面使用<aop:aspect>元素声明,并且向前引用的bean使用ref属性如下:

<aop:config>
	<aop:aspect id="myAspect" ref="aBean">
		...
	</aop:aspect>
</aop:config>

<bean id="aBean" class="...">
	...
</bean>

这里“aBean”将被配置并被依赖注入和你前面看到的章节看到的Spring bean一样。

声明切点(pointcut)

一个切点帮助在决定关注的连接点(即方法)被执行使用不同的通知。当使用基于XML模式的配置,切点将如下定义:

<aop:config>
	<aop:aspect id="myAspect" ref="aBean">

		<aop:pointcut id="businessService"
			expression="execution(* com.xyz.myapp.service.*.*(..))" />
		...
	</aop:aspect>
</aop:config>

<bean id="aBean" class="...">
	...
</bean>

接下来的示例定义了一个切点命名“businessService”它将匹配getName()方法的执行在包com.tutorialspoint中的Student类。

<aop:config>
	<aop:aspect id="myAspect" ref="aBean">

		<aop:pointcut id="businessService"
			expression="execution(* 
 com.tutorialspoint.Student.getName(..))" />
		...
	</aop:aspect>

</aop:config>

<bean id="aBean" class="...">
	...
</bean>

声明通知

你可以在<aop:aspect>中使用<aop:{通知类型名}>元素声明任意五中类型的通知,如下:

<aop:config>
	<aop:aspect id="myAspect" ref="aBean">
		<aop:pointcut id="businessService"
			expression="execution(* com.xyz.myapp.service.*.*(..))" />
	<!-- a before advice definition -->
		<aop:before pointcut-ref="businessService" method="doRequiredTask" />

		<!-- an after advice definition -->
		<aop:after pointcut-ref="businessService" method="doRequiredTask" />

		<!-- an after-returning advice definition -->
		<!--The doRequiredTask method must have parameter named retVal -- > <aop:after-returning 
			pointcut-ref="businessService" returning="retVal" method="doRequiredTask"/> 
			<!-- an after-throwing advice definition -->
		<!--The doRequiredTask method must have parameter named ex -->
		<aop:after-throwing pointcut-ref="businessService"
			throwing="ex" method="doRequiredTask" />

		<!-- an around advice definition -->
		<aop:around pointcut-ref="businessService" method="doRequiredTask" />
		...
	</aop:aspect>
</aop:config>

<bean id="aBean" class="...">
	...
</bean>

你可以使用"doRequiredTask"或者不同的方法对不同的通知。这些方法将被定义作为方面模块的一部分。

示例

为了理解上面基于XML模式的AOP相关涉及的概念,让我们写一个例子实现几个通知。写一个例子带有几个通知,让我们使用Eclipse IDE按着下面的步骤创建Spring应用。

步骤

描述

1

创建一个SpringExample的项目并在src目录下创建com.tutorialspoint包。所有的类都在这个包下。

2

在Add External JARs选项卡中添加Spring库如在Spring Hello World章节中所讲。

3

项目增加AOP特有的库 aspectjrt.jar、aspectjweaver.jar、aspectj.jar在项目中。

4

在com.tutorialspoint包中创建Logging、Student、MainApp类。

5

在src目录下创建配置文件Beans.xml

6

最后一步在Java类中和Bean配置文件中添加内容,并运行应用。

这是Logger的源代码。这实际上是一个简单的方面模块定义方法在不同的点的调用。

package com.tutorialspoint;

public class Logging {

    /**
     * This is the method which I would like to execute before a selected method
     * execution.
     */
    public void beforeAdvice() {
        System.out.println("Going to setup student profile.");
    }

    /**
     * This is the method which I would like to execute after a selected method
     * execution.
     */
    public void afterAdvice() {
        System.out.println("Student profile has been setup.");
    }

    /**
     * This is the method which I would like to execute when any method returns.
     */
    public void afterReturningAdvice(Object retVal) {
        System.out.println("Returning:" + retVal.toString());
    }

    /**
     * This is the method which I would like to execute if there is an exception
     * raised.
     */
    public void AfterThrowingAdvice(IllegalArgumentException ex) {
        System.out.println("There has been an exception: " + ex.toString());
    }
}

如下是Student类的源代码:

package com.tutorialspoint;

public class Student {
    private Integer age;
    private String name;
    public void setAge(Integer age) {
        this.age = age;
    }
    public Integer getAge() {
        System.out.println("Age : " + age);
        return age;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        System.out.println("Name : " + name);
        return name;
    }
    public void printThrowException() {
        System.out.println("Exception raised");
        throw new IllegalArgumentException();
    }
}

如下是MainApp的源代码:

package com.tutorialspoint;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        Student student = (Student) context.getBean("student");

        student.getName();
        student.getAge();

        student.printThrowException();
    }
}

如下是配置文件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-3.0.xsd 
     http://www.springframework.org/schema/aop 
     http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">

	<aop:config>
		<aop:aspect id="log" ref="logging">
			<aop:pointcut id="selectAll"
				expression="execution(* com.tutorialspoint.*.*(..))" />
			<aop:before pointcut-ref="selectAll" method="beforeAdvice" />
			<aop:after pointcut-ref="selectAll" method="afterAdvice" />
			<aop:after-returning pointcut-ref="selectAll"
				returning="retVal" method="afterReturningAdvice" />
			<aop:after-throwing pointcut-ref="selectAll"
				throwing="ex" method="AfterThrowingAdvice" />
		</aop:aspect>
	</aop:config>

	<!-- Definition for student bean -->
	<bean id="student" class="com.tutorialspoint.Student">
		<property name="name" value="Zara" />
		<property name="age" value="11" />
	</bean>

	<!-- Definition for logging aspect -->
	<bean id="logging" class="com.tutorialspoint.Logging" />

</beans>

一旦你完成了源代码和配置文件的创建,运行这个应用。如果应用一切正常,将会打印如下消息:

Going to setup student profile.
Name : Zara
Student profile has been setup.
Returning:Zara
Going to setup student profile.
Age : 11
Student profile has been setup.
Returning:11
Going to setup student profile.
Exception raised
Exception in thread "main" java.lang.IllegalArgumentException
Student profile has been setup.
There has been an exception: java.lang.IllegalArgumentException
…………other exception content

让我解释一下上面定义<aop:pointcut>选择包com.tutorialspoint包的所有方法。让我们假设,你想执行你的通知在一个特定的方法前或后,你可以在切点的定义中定义一个切点去缩小你执行的通知范围通过使用特定的类和方法名替代星号(*)。如下是修改的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-3.0.xsd 
      http://www.springframework.org/schema/aop 
      http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
	<aop:config>
		<aop:aspect id="log" ref="logging">
			<aop:pointcut id="selectAll" expression="execution(* com.tutorialspoint.Student.getName(..))"/>
			<aop:before pointcut-ref="selectAll" method="beforeAdvice"/>
			<aop:after pointcut-ref="selectAll" method="afterAdvice"/>
		</aop:aspect>
	</aop:config>
	<!-- Definition for student bean -->
	<bean id="student" class="com.tutorialspoint.Student">
		<property name="name" value="Zara"/>
		<property name="age" value="11"/>
	</bean>
	<!-- Definition for logging aspect -->
	<bean id="logging" class="com.tutorialspoint.Logging"/>
</beans>

如果你用修改过的配置文件执行这个应用,将会打印如下消息:

Going to setup student profile.
Name : Zara
Student profile has been setup.
Age : 11
Exception raised
Exception in thread "main" java.lang.IllegalArgumentException
…………other exception content

Spring基于@AspectJ的AOP

@AspectJ指明了和Java 5类似的Java类注解的声明方面的风格。@AspectJ被支持通过在你的基于XML模式的配置文件中包含如下元素。

<aop:aspectj-autoproxy/>

 你的应用的类路径下需要如下的AspectJ库。这个库可以在AspectJ目录‘lib’目录下找到,否则你可以从internet上下载。

    *aspectjrt.jar

    *aspectjweaver.jar

    *aspectj.jar

声明一个方面

方面类和其它的有方法和域的其他通常的类一样,除非他们被@Aspect注解,如下:

package org.xyz; 
 import org.aspectj.lang.annotation.Aspect; 
 @Aspect 
 public class AspectModule { 
  }

这和如下的XML的配置一样:

<bean id="myAspect" class="org.xyz.AspectModule">
	<!-- configure properties of aspect here as normal -->
</bean>

声明一个切点

   切点决定连接点在感兴趣的地方执行不同的通知。当使用基于@AspectJ的配置,切点的声明需要两部分。

    * 一个切点表达式决定哪个方法的执行是我们感兴趣的。

    * 一个切点签名包含一个名字和任意数量的参数。方法的主体是不相关的实际上应该是空的。

接下来的例子定义了一个切点命名“businessService”匹配包com.xyz.myapp.service下的任何方法的执行:

import org.aspectj.lang.annotation.Pointcut; 
@Pointcut("execution(* com.xyz.myapp.service.*.*(..))") // 
expression 
private void businessService () {}  // signature

接下来的例子定义了一个切点命名“getname”将匹配com.tutorialspoint包下的类Student的getName()方法的执行。

import org.aspectj.lang.annotation.Pointcut; 
@Pointcut("execution(* com.tutorialspoint.Student.getName(..))") 
private void getname () {} 

声明通知

你可以使用注解@{通知类型名}注解声明任意的5种通知。这个假设你已经定义了一个切点签名方法businessService():

@Before("businessService()") 
    public void doBeforeTask (){ 
      ... 
    } 

    @After("businessService()") 
    public void doAfterTask (){ 
      ... 
    } 

    @AfterReturning(pointcut = "businessService()", returning="retVal") 
    public void doAfterReturnningTask (Object retVal){ 
       // you can intercept retVal here. 
       ... 
    } 

    @AfterThrowing(pointcut = "businessService()", throwing="ex") 
    public void doAfterThrowingTask (Exception ex){ 
       // you can intercept thrown exception here. 
       ... 
    } 

    @Around("businessService()") 
    public void doAroundTask (){ 
      ... 
    } 

你可以定义切点内嵌在任何通知中。如下是定义一个内嵌的切点在before通知中。

  @Before("execution(* com.xyz.myapp.service.*.*(..))") 
    public doBeforeTask (){ 
      ... 
    }

示例

为了理解上面关于基于@AspectJ注解的概念,让我们写一个实现了几个通知的示例。这个示例带有几个通知,让我们使用Eclipe IDE按如下的步骤创建Spring应用。

 步骤  

 描述

 1

创建一个SpringExample的项目并在src目录下创建com.tutorialspoint包。所有的类都在这个包下。

 2

在Add External JARs选项卡中添加Spring库如在Spring Hello World章节中所讲。

 3

项目增加AOP特有的库 aspectjrt.jar、aspectjweaver.jar、aspectj.jar在项目中。

 4

在com.tutorialspoint包中创建Logging、Student、MainApp类。

 5

在src目录下创建配置文件Beans.xml

 6

最后一步在Java类中和Bean配置文件中添加内容,并运行应用。

如下是Logging类的源代码。这实际是一个方面模块的样例这定义了一个方法被不同的点调用。

package com.tutorialspoint;
import org.aspectj.lang.annotation.*;
@Aspect
public class Logging {
    /**
     * Following is the definition for a pointcut to select all the methods
     * available. So advice will be called for all the methods.
     */
    @Pointcut("execution(* com.tutorialspoint.*.*(..))")
    private void selectAll() {
    }
    /**
     * This is the method which I would like to execute before a selected method
     * execution.
     */
    @Before("selectAll()")
    public void beforeAdvice() {
        System.out.println("Going to setup student profile.");
    }
    /**
     * This is the method which I would like to execute after a selected method
     * execution.
     */
    @After("selectAll()")
    public void afterAdvice() {
        System.out.println("Student profile has been setup.");
    }
  /**
     * This is the method which I would like to execute when any method returns.
     */
    @AfterReturning(pointcut = "selectAll()", returning = "retVal")
    public void afterReturningAdvice(Object retVal) {
        System.out.println("Returning:" + retVal.toString());
    }
  
    /**
     * This is the method which I would like to execute if there is an exception
     * raised by any method.
     */
    @AfterThrowing(pointcut = "selectAll()", throwing = "ex")
    public void AfterThrowingAdvice(IllegalArgumentException ex) {
        System.out.println("There has been an exception: " + ex.toString());
    }
}

如下是Student类的源代码:

package com.tutorialspoint;
public class Student {
    private Integer age;
    private String name;
    public void setAge(Integer age) {
        this.age = age;
    }
    public Integer getAge() {
        System.out.println("Age : " + age);
        return age;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        System.out.println("Name : " + name);
        return name;
    }

    public void printThrowException() {
        System.out.println("Exception raised");
        throw new IllegalArgumentException();
    }
}

如下是MainApp的源代码:

package com.tutorialspoint;

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

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");

        Student student = (Student) context.getBean("student");

        student.getName();
        student.getAge();

        student.printThrowException();
    }
}

如下是配置文件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-3.0.xsd 
http://www.springframework.org/schema/aop 
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">

	<aop:aspectj-autoproxy/>

	<!-- Definition for student bean -->
	<bean id="student" class="com.tutorialspoint.Student">
		<property name="name" value="Zara"/>
		<property name="age" value="11"/>
	</bean>

	<!-- Definition for logging aspect -->
	<bean id="logging" class="com.tutorialspoint.Logging"/>

</beans>

一旦你完成了源代码和配置文件的创建,运行这个应用。如果应用一切正常,将会打印如下消息:


Going to setup student profile.
Name : Zara
Student profile has been setup.
Returning:Zara
Going to setup student profile.
Age : 11
Student profile has been setup.
Returning:11
Going to setup student profile.
Exception raised
Student profile has been setup.
There has been an exception: java.lang.IllegalArgumentException
…………other exception content

























  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值