2014-03-11 Spring的学习(3)------面向切面编程(AOP)

原创
2014/03/11 11:25
阅读数 916

1. AOP概念

首先让我们从一些重要的AOP概念和术语开始。这些术语不是Spring特有的。不过AOP术语并不是特别的直观,如果Spring使用自己的术语,将会变得更加令人困惑。 

切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用基于模式)或者基于@Aspect注解的方式来实现。 

连接点(Joinpoint):在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中,一个连接点总是表示一个方法的执行。

通知(Advice):在切面的某个特定的连接点上执行的动作。其中包括了“around”、“before”和“after”等不同类型的通知(通知的类型将在后面部分进行讨论)。许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。 

切入点(Pointcut):匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。 

引入(Introduction):用来给一个类型声明额外的方法或属性(也被称为连接类型声明(inter-type declaration))。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用引入来使一个bean实现IsModified接口,以便简化缓存机制。 

目标对象(Target Object): 被一个或者多个切面所通知的对象。也被称做被通知(advised)对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxied)对象。 

AOP代理(AOP Proxy):AOP框架创建的对象,用来实现切面契约(例如通知方法执行等等)。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。 

织入(Weaving):把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。 

通知类型:

前置通知(Before advice):在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛          出一个异常)。 

后置通知(After returning advice):在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正       常返回。 

 异常通知(After throwing advice):在方法抛出异常退出时执行的通知。 

最终通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。 

环绕通知(Around Advice):包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在     方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执       行。 

      环绕通知是最常用的通知类型。和AspectJ一样,Spring提供所有类型的通知,我们推荐你使用尽可能简单的通知类型来实现需要的功能。例如,如果你只是需要一个方法的返回值来更新缓存,最好使用后置通知而不是环绕通知,尽管环绕通知也能完成同样的事情。用最合适的通知类型可以使得编程模型变得简单,并且能够避免很多潜在的错误。比如,你不需要在JoinPoint上调用用于环绕通知的proceed()方法,就不会有调用的问题。 

2. AOP代理

Spring缺省使用J2SE 动态代理(dynamic proxies)来作为AOP的代理。 这样任何接口(或者接口集)都可以被代理。

package cn.itcast.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JDKServiceProxy implements InvocationHandler {
	/**目标对象**/
	private Object targetObject;
	/**创建代理对象**/
	public Object createProxyObject(Object targetObject){
		this.targetObject = targetObject;
		return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(), 
				this.targetObject.getClass().getInterfaces(),
				this);
	}
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		// TODO Auto-generated method stub
		return method.invoke(targetObject, args);
	}
}


/**J2SE实现动态代理,目标类必须实现接口**/
package cn.itcast.service;
public interface PersonService {
	public abstract String getName();
	public abstract void save();
	public abstract void update();
}

package cn.itcast.service.impl;
import cn.itcast.service.PersonService;
public class PersonServiceImpl implements PersonService {
	/* (non-Javadoc)
	 * @see cn.itcast.service.impl.PersonService#getName()
	 */
	@Override
	public String getName(){
		return "xxx";
	}
	/* (non-Javadoc)
	 * @see cn.itcast.service.impl.PersonService#save()
	 */
	@Override
	public void save(){
		System.out.println("This is a save");
	}
	/* (non-Javadoc)
	 * @see cn.itcast.service.impl.PersonService#update()
	 */
	@Override
	public void update(){
//		throw new RuntimeException();
		System.out.println("This is a update");
	}
}

测试类:JDKAopTest.java
package junit.test;
import org.junit.Test;
import cn.itcast.proxy.JDKServiceProxy;
import cn.itcast.service.PersonService;
import cn.itcast.service.impl.PersonServiceImpl;
public class JDKAopTest {
	@Test
	public void test() {
		JDKServiceProxy proxy = new JDKServiceProxy();
		PersonService service = (PersonService) proxy.createProxyObject(new PersonServiceImpl());
	    service.save();
	}
}
//output:
This is a save
//~

Spring也可以使用CGLIB代理. 对于需要代理类而不是代理接口的时候CGLIB代理是很有必要的。如果一个业务对象并没有实现一个接口,默认就会使用CGLIB。作为面向接口编程的最佳实践,业务对象通常都会实现一个或多个接口。但也有可能会强制使用CGLIB,在这种情况(希望不常有)下,你可能需要通知一个没有在接口中声明的方法,或者需要传入一个代理对象给方法作为具体类型.需要在开发工程里导入cglib-nodep-2.1_3.jar架包。

3. 基于@AspectJ(注解)进行AOP开发

  @AspectJ使用了Java 5的注解,可以将切面声明为普通的Java类。@AspectJ样式在AspectJ 5发布的AspectJ project部分中被引入。Spring 2.0使用了和AspectJ 5一样的注解,并使用AspectJ来做切入点解析和匹配。但是,AOP在运行时仍旧是纯的Spring AOP,并不依赖于AspectJ的编译器或者织入器(weaver)。 需要在你的应用程序的classpath中引入两个AspectJ库:aspectjweaver.jaraspectjrt.jar

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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
       http://www.springframework.org/schema/aop 
       http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
        <aop:aspectj-autoproxy/>   
        <context:component-scan base-package="cn.itcast"/>
</beans>

MyInterceptor.java

package cn.itcast.interceptor;

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;
import org.springframework.stereotype.Component;


@Aspect @Component
public class MyInterceptor {
	// the pointcut expression
	@Pointcut ("execution(* cn.itcast.service.impl.PersonServiceImpl.*(..))")
	private void anyMethod(){}
	@Before ("anyMethod()")
	public void doBefore() { 
		System.out.println("前置通知");
	}
	@AfterReturning ("anyMethod()")
	public void doAfterReturning() {
		System.out.println("后置通知");
	}
	@After ("anyMethod()")
	public void doAfter(){
		System.out.println("最终通知");
	}
	@AfterThrowing ("anyMethod()")
	public void doAfterThrowing(){
		System.out.println("异常通知");
	}
	@Around ("anyMethod()")
	public Object doAround(ProceedingJoinPoint pjp) throws Throwable{
		 // start stopwatch
		System.out.println("开始方法");
	    Object retVal = pjp.proceed();
	    // stop stopwatch
	    System.out.println("退出方法");
	    return retVal;

	}

}

测试代码:SpringAopTest.java

package junit.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.itcast.service.PersonService;
public class SpringAopTest {
	@Test
	public void test(){
		ApplicationContext context= new ClassPathXmlApplicationContext("beans.xml");
		PersonService service = (PersonService) context.getBean("personServiceImpl");
		service.update();
	}
}
测试结果://output:
前置通知
开始方法
This is a update
后置通知
最终通知
退出方法
//~

开发步骤以及详细配置:请参考Spring FrameWork开发手册(例如切入点的表达式,通知参数等)

4. 基 于Schema(XML文件)进行AOP开发

如果你无法使用Java 5,或者你比较喜欢使用XML格式,Spring2.0也提供了使用新的"aop"命名空间来定义一个切面。 和使用@AspectJ风格完全一样,切入点表达式和通知类型同样得到了支持。

使用本章所介绍的aop命名空间标签,你需要引入附录 A, XML Schema-based configuration中提及的spring-aop schema。 

在Spring的配置文件中,所有的切面和通知都必须定义在<aop:config>元素内部。 (一个application context可以包含多个 <aop:config>)。 一个<aop:config>可以包含pointcut,advisor和aspect元素 (注意这三个元素必须按照这个顺序进行声明)。 

警告

<aop:config>风格的配置使得Spring auto-proxying机制的使用变得很笨重。如果你已经通过 BeanNameAutoProxyCreator或类似的东西显式使用auto-proxying,它可能会导致问题 (例如通知没有被织入)。 推荐的使用模式是仅仅使用<aop:config>风格, 或者仅仅使用AutoProxyCreator风格。

<?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:context="http://www.springframework.org/schema/context" 
       xmlns:aop="http://www.springframework.org/schema/aop"      
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
        <aop:aspectj-autoproxy/> 
        <bean id="personService" class="cn.itcast.service.impl.PersonServiceBean"></bean>
        <bean id="aspetbean" class="cn.itcast.service.MyInterceptor"/>
        <aop:config>
          <aop:aspect id="asp" ref="aspetbean">
        	<aop:pointcut id="mycut" expression="execution(* cn.itcast.service..*.*(..))"/>
        	<aop:before pointcut-ref="mycut" method="doAccessCheck"/>
        	<aop:after-returning pointcut-ref="mycut" method="doAfterReturning"/>
		<aop:after-throwing pointcut-ref="mycut" method="doAfterThrowing"/>
		<aop:after pointcut-ref="mycut" method="doAfter"/>
		<aop:around pointcut-ref="mycut" method="doBasicProfiling"/>
           </aop:aspect>
        </aop:config>
</beans>

5. AOP声明风格的选择

当你确定切面是实现一个给定需求的最佳方法时,你如何选择是使用Spring AOP还是AspectJ,以及选择 Aspect语言(代码)风格、@AspectJ声明风格或XML风格?这个决定会受到多个因素的影响,包括应用的需求、 开发工具和小组对AOP的精通程度。 

    5.1 Spring AOP还是完全用AspectJ?

做能起作用的最简单的事。Spring AOP比完全使用AspectJ更加简单, 因为它不需要引入AspectJ的编译器/织入器到你开发和构建过程中。 如果你仅仅需要在Spring bean上通知执行操作,那么Spring AOP是合适的选择。 如果你需要通知domain对象或其它没有在Spring容器中管理的任意对象,那么你需要使用AspectJ。 如果你想通知除了简单的方法执行之外的连接点(如:调用连接点、字段get或set的连接点等等), 也需要使用AspectJ。

当使用AspectJ时,你可以选择使用AspectJ语言(也称为“代码风格”)或@AspectJ注解风格。 很显然,如果你用的不是Java 5+那么结论是你只能使用代码风格。 如果切面在你的设计中扮演一个很大的角色,并且你能在Eclipse中使用AspectJ Development Tools (AJDT), 那么首选AspectJ语言 :- 因为该语言专门被设计用来编写切面,所以会更清晰、更简单。如果你没有使用 Eclipse,或者在你的应用中只有很少的切面并没有作为一个主要的角色,你或许应该考虑使用@AspectJ风格 并在你的IDE中附加一个普通的Java编辑器,并且在你的构建脚本中增加切面织入(链接)的段落。 

    5.2 Spring AOP中使用@AspectJ还是XML?

如果你选择使用Spring AOP,那么你可以选择@AspectJ或者XML风格。显然如果你不是运行 在Java 5上,XML风格是最佳选择。对于使用Java 5的项目,需要考虑多方面的折衷。

XML风格对现有的Spring用户来说更加习惯。它可以使用在任何Java级别中 (参考连接点表达式内部的命名连接点,虽然它也需要Java 5+) 并且通过纯粹的POJO来支持。当使用AOP作为工具来配置企业服务时XML会是一个很好的选择。 (一个好的例子是当你认为连接点表达式是你的配置中的一部分时,你可能想单独更改它) 对于XML风格,从你的配置中可以清晰的表明在系统中存在那些切面。

XML风格有两个缺点。第一是它不能完全将需求实现的地方封装到一个位置。 DRY原则中说系统中的每一项知识都必须具有单一、无歧义、权威的表示。 当使用XML风格时,如何实现一个需求的知识被分割到支撑类的声明中以及XML配置文件中。 当使用@AspectJ风格时就只有一个单独的模块 -切面- 信息被封装了起来。 第二是XML风格同@AspectJ风格所能表达的内容相比有更多的限制:仅仅支持"singleton"切面实例模型, 并且不能在XML中组合命名连接点的声明。例如,在@AspectJ风格中我们可以编写如下的内容:

@Pointcut(execution(* get*()))
                public void propertyAccess() {}
                @Pointcut(execution(org.xyz.Account+ *(..))
                public void operationReturningAnAccount() {}
                
                @Pointcut(propertyAccess() && operationReturningAnAccount())
            public void accountPropertyAccess() {}

在XML风格中能声明开头的两个连接点:

  <aop:pointcut id="propertyAccess"
                expression="execution(* get*())"/>
                
                <aop:pointcut id="operationReturningAnAccount"
            expression="execution(org.xyz.Account+ *(..))"/>

但是不能通过组合这些来定义accountPropertyAccess连接点

@AspectJ风格支持其它的实例模型以及更丰富的连接点组合。它具有将切面保持为一个模块单元的优点。 还有一个优点就是@AspectJ切面能被Spring AOP和AspectJ两者都理解 - 所以如果稍后你认为你需要AspectJ的能力去实现附加的需求,那么你非常容易迁移到基于AspectJ的途径。 总而言之,我们更喜欢@AspectJ风格只要你有切面去做超出简单的“配置”企业服务之外的事情。 

结束.....








展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
10 收藏
0
分享
返回顶部
顶部