Spring实战——面向切面的Spring

来源:互联网 发布:java马士兵整套视频 编辑:程序博客网 时间:2024/05/18 20:09

在我的CSDN博客的Spring分类里面,之前就已经有三篇关于面向切面编程的博客了,最近在阅读《Spring实战》这本书,不禁感叹这确实是一本好书,需要细细的阅读,今天再记述一下Spring AOP的知识,一些AOP的术语,这里就不说了,之前的博客里面都有介绍!下面开始重点:


除了添加Spring所需要的jar包之外,还需要添加aspect相关的jar包,点击下载aspect相关jar包

一、Spring对AOP的支持

Spring提供了4种类型的AOP支持:

  • 基于代理的经典Spring AOP;
  • 纯POJO切面
  • @AspectJ注解驱动切面
  • 注入式AspectJ切面(适用于Spring各版本)

前三种都是Spring AOP实现的变体,Spring AOP 构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法拦截。

二、通过切点来选择连接点

切点用于准确定位应该在什么地方应用切面的通知。

1、Spring借助AspectJ的切点表达式语言来定义Spring切面:

AspectJ指示器                                    描述

arg()                                                     限制连接点匹配参数为指定类型的执行方法

@arg()                                                 限制连接点匹配参数由指定注解标注的执行方法

execution()                                         用于匹配是连接点的执行方法

this()                                                    限制连接点匹配AOP代理的Bean引用为指定类型的类

target                                                   限制连接点匹配目标对象为指定类型的类

@target()                                            限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解

within()                                                限制连接点匹配指定的类型

@within()                                           限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义在由指定的注解所标注的类里)

@annotation                                     限制匹配带有指定注解的连接点

注意:以上只有execution指示器是实际执行匹配的,其他指示器都是用来限制匹配的。

2、编写切点:

假设有一个业务接口,取名为Performance(这个类在com.mfc.annotation.noparameter包下),接口里有一个方法perform(),现在要写一个切点表达式,这个切点表达式能设置当前的perform()方法执行时处罚通知的调用,那么这个表达式是: execution(* com.mfc.annotation.noparameter.Performance.perform(..))

对这个表达式解释一下:表达式以“*”开始,表名不关心返回值类型,然后使用了全限定类名和方法名,对于方法参数列表,使用了两个点号(..)表明其诶单要选择任意的perform()方法,无论该方法的参数是什么。

三、使用注解创建切面:

1、通知中没有参数:

      先了解一下几个注解:
        @After  通知方法在目标方法执行成功或者抛出异常后调用
        @AfterReturning 通知方法在目标方法执行成功之后调用
        @AfterThrowing 通知方法在目标方法执行抛出异常后调用
        @Around 环绕通知:通知方法将目标方法封装起来
        @Before 通知方法在目标方法执行之前调用

        @pointcut   定义可重用的切点
先看业务类接口与实现:
package com.mfc.annotation.noparameter;/** * 2017年11月18日19:25:40 * 目标接口 * */public interface Performance {public void perform();}

package com.mfc.annotation.noparameter;import org.springframework.stereotype.Component;/** * 2017年11月18日19:26:43 * 目标方法的类,继承了目标接口 * perform()方法作为切点 * */@Component("performance")public class PerformanceImpl implements Performance {@Overridepublic void perform() {System.out.println("-------开始表演!");}}

定义切面:
package com.mfc.annotation.noparameter;import org.aspectj.lang.ProceedingJoinPoint;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;/** * 2017年11月18日19:28:16 * 切面类 * */@Aspectpublic class Audience {/* * 这里的performance()方法只是一个标识,供@Pointcut注解依附,并没有其他意义 * *///定义命名的切点@Pointcut("execution(** com.mfc.annotation.noparameter.Performance.perform(..))")public void performance(){}/* * 其实这个类中可以不用performance()存在,如果performance()不存在: * 一下的通知@Before就要写成@Before("execution(** com.mfc.annotation.Performance.perform(..))") * 如此的话,@After等注解每一个注解后面都要加上("execution(** com.mfc.annotation.Performance.perform(..))") * 代码重复真不是什么光彩的事情,所以有了performance()存在,下面的@After等注解后面就可以直接加上("performance()")了。 *  * Spring使用AspectJ注解来声明通知的方法 * @After  通知方法在目标方法执行成功或者抛出异常后调用 * @AfterReturning 通知方法在目标方法执行成功之后调用 * @AfterThrowing 通知方法在目标方法执行抛出异常后调用 * @Around 环绕通知:通知方法将目标方法封装起来 * @Before 通知方法在目标方法执行之前调用 * *//*@Before("performance()")public void beforeAdvice(){System.out.println("前置通知:表演要开始了,请将手机调制静音!");}@AfterReturning("performance()")public void successAfterAdvice(){System.out.println("成功后置通知:表演完美结束,现在开始鼓掌!");}@AfterThrowing("performance()")public void failAfterAdvice(){System.out.println("失败后置通知:表演出意外了,请大家谅解!");}*///环绕通知@Around("performance()")public void aroundAdvice(ProceedingJoinPoint jp){try {System.out.println("前置通知:表演要开始了,请将手机调制静音!");jp.proceed();System.out.println("成功后置通知:表演完美结束,现在开始鼓掌!");} catch (Throwable e) {System.out.println("失败后置通知:表演出意外了,请大家谅解!");e.printStackTrace();}}}

再看看java的配置类,这里使用了自动装配和java装配混合:
package com.mfc.annotation.noparameter;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.EnableAspectJAutoProxy;/** * 2017年11月18日19:52:35 * java配置类,这里采用了bean的自动装配 * @EnableAspectJAutoProxy注解是启用Spring的自动代理功能 * 也可以在xml配置文件中使用<aop:aspectj-autoproxy />代替@EnableAspectJAutoProxy * */@Configuration@EnableAspectJAutoProxy@ComponentScan("com.mfc.annotation")public class JavaConfig {@Beanpublic Audience audience(){return new Audience();}}

最后看测试类:
package com.mfc.annotation.noparameter;import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class JavaConfigAopTest {public static void main(String[] args) {ApplicationContext applicationContext = new AnnotationConfigApplicationContext(JavaConfig.class);Performance performance = (Performance) applicationContext.getBean("performance");performance.perform();}}

测试结果:

2、通知中有参数

业务类接口及其实现:

package com.mfc.annotation.hasparameter;/** * 2017年11月18日21:15:58 * 接口 * */public interface CompactDisc {public void play();public void playTrack(String trackNumber);}

package com.mfc.annotation.hasparameter;import java.util.List;/** * 2017年11月18日21:16:51 * 实现类 * */public class BlankDisc implements CompactDisc {private String title;private String artist;private List<String> tracts;public void setTitle(String title) {this.title = title;}public void setArtist(String artist) {this.artist = artist;}public void setTracts(List<String> tracts) {this.tracts = tracts;}@Override public void play() {System.out.println("Playing "+title+" by "+artist);for (String string : tracts) {System.out.println("-Track : "+string);}}@Overridepublic void playTrack(String trackNumber){System.out.println("播放了:"+trackNumber);}}

定义切面:

package com.mfc.annotation.hasparameter;import java.util.HashMap;import java.util.Map;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;/** * 2017年11月18日21:35:33 * 切面类 * */@Aspectpublic class TrackCounter {Map<String, Integer> trackCounts = new HashMap<String, Integer>();//通知playTrack方法/* * 这个切点表达式应该关注的是表达式中的args(trackNumber)限定符。 * 它表明传递给playTrack()方法的String类型参数也会传递到通知中去, * 参数的名称trackNumber也与切点方法签名中的参数相匹配 * */@Pointcut("execution(* com.mfc.annotation.hasparameter.CompactDisc.playTrack(String)) && args(trackNumber)")public void trackPlayer(String trackNumber){}@Before("trackPlayer(trackNumber)")public void countTrack(String trackNumber){System.out.println(trackNumber+"触发了这个前置通知");int count = getPlayCount(trackNumber);trackCounts.put(trackNumber, count+1);}public int getPlayCount(String trackNumber){//containsKey判断map集合中是否存在制定的键名return trackCounts.containsKey(trackNumber)?trackCounts.get(trackNumber):0;}}

配置java:

package com.mfc.annotation.hasparameter;import java.util.ArrayList;import java.util.List;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.EnableAspectJAutoProxy;/** * 2017年11月18日21:36:07 * java配置类 * */@Configuration@EnableAspectJAutoProxypublic class TrackCounterConfig {@Bean(name = "compactDisc")public CompactDisc sgtPepers(){BlankDisc blankDisc = new BlankDisc();blankDisc.setTitle("测试标题");blankDisc.setArtist("测试艺术家");List<String> tracks = new ArrayList<String>();tracks.add("测试数据一");tracks.add("测试数据二");tracks.add("测试数据三");tracks.add("测试数据四");return blankDisc;}@Bean(name = "trackCounter")public TrackCounter trackCounter(){return new TrackCounter();}}

测试:

package com.mfc.annotation.hasparameter;import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.AnnotationConfigApplicationContext;/** * 2017年11月18日21:40:44 * 测试类 * */public class TrackCounterTest {public static void main(String[] args) {ApplicationContext applicationContext = new AnnotationConfigApplicationContext(TrackCounterConfig.class);CompactDisc compactDisc = (CompactDisc) applicationContext.getBean("compactDisc");TrackCounter counter = (TrackCounter) applicationContext.getBean("trackCounter");compactDisc.playTrack("A");compactDisc.playTrack("A");compactDisc.playTrack("B");compactDisc.playTrack("C");compactDisc.playTrack("B");compactDisc.playTrack("B");System.out.println("A--------"+counter.getPlayCount("A"));System.out.println("B--------"+counter.getPlayCount("B"));System.out.println("C--------"+counter.getPlayCount("C"));}}

测试结果:

3、通过注解引入新功能:

一般情况,一个java类写好了就这些功能,除非修改这个java类,否则它不会有新的功能,现在Spring可以实现一个功能:写好一个java类之后,不用修改它,给它添加新的功能:

基础功能的业务类接口及其实现:

package com.mfc.annotation.newfunction;public interface BaseFunction {public void baseFunction();}

package com.mfc.annotation.newfunction;import org.springframework.stereotype.Component;@Component("baseFunction")public class BaseFunctionImpl implements BaseFunction {@Overridepublic void baseFunction() {System.out.println("这是BaseFunctionImpl的基础功能方法");}}

新添加功能的业务类及其接口:

package com.mfc.annotation.newfunction;public interface NewFunction {public void newFunction();}

package com.mfc.annotation.newfunction;import org.springframework.stereotype.Component;@Component("newFunction")public class NewFunctionImpl implements NewFunction {@Overridepublic void newFunction() {System.out.println("这是通过NewFunctionImpl给BaseFunctionImpl添加的新功能方法");}}

AOP配置:

package com.mfc.annotation.newfunction;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.DeclareParents;import org.springframework.stereotype.Component;@Component@Aspectpublic class AopConfig {/** * 2017年11月18日22:32:17 * value 指定了那种类型的Bean要引入该接口,在本例中,就是所有实现了BaseFunction的类型,加号代表BaseFunction的所有子类,而不是它本身 * defaultImpl 指定了为引入功能提供实现的类 * */@DeclareParents(value = "com.mfc.annotation.newfunction.BaseFunction+",defaultImpl = NewFunctionImpl.class)public static NewFunction function;}

java配置:

package com.mfc.annotation.newfunction;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.EnableAspectJAutoProxy;/** * 2017年11月18日22:16:07 * java配置文件 * */@Configuration@EnableAspectJAutoProxy@ComponentScan("com.mfc.annotation.newfunction")public class JavaConfig {}

测试类:

package com.mfc.annotation.newfunction;import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.AnnotationConfigApplicationContext;/** * 2017年11月18日22:35:14 * 测试类 * */public class Test {public static void main(String[] args) {ApplicationContext applicationContext = new AnnotationConfigApplicationContext(JavaConfig.class);BaseFunction baseFunction = (BaseFunction) applicationContext.getBean("baseFunction");baseFunction.baseFunction();NewFunction newFunction = (NewFunction) baseFunction;newFunction.newFunction();}}

测试结果:


四、在XML中声明切面:

在《Spring实战》中有一句话:基于注解的配置要由于基于java的配置,基于java的配置要基于XML的配置。但是,如果需要声明切面,又不能为通知类添加注解,就必须使用xml配置。

1、通知中没有参数:

Spring的AOP配置元素能够以非侵入性的方式声明切面


AOP配置元素                                 用途

<aop:advisor>                               定义AOP通知器

<aop:after>                                    定义AOP后置通知(不管被通知的方法是否执行成功)

<aop:after-returning>                  定义AOP返回通知(被通知方法执行成功后通知)

<aop:after-throwing>                   定义AOP异常通知(被通知方法执行失败后通知)

<aop:around>                               定义AOP环绕通知

<aop:aspect>                                定义一个切面

<aop:aspectj-autoproxy>            启用@Aspect注解(自动代理Aspect注解的通知类)

<aop:before>                                定义一个AOP前置通知

<aop:config>                                 顶层的AOP配置元素,大多数的<aop:*>元素必须包含在<aop:config>元素内

<aop:declare-parents>               以透明的方式为被通知的对象引入额外的接口

<aop:pointcut>                              定义一个切点


示例:

业务类接口与实现跟上面(三)中的(1)一样,Performance的接口以及它的实现类。切面类把(三)中的(1)的切面类(Audience.java)里面的注解全部删掉即可。

看XML配置文件:

<?xml version="1.0" encoding="UTF-8"?><beansxmlns="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-4.1.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd"><bean id="performanceImpl" class="com.mfc.xml.noparameter.PerformanceImpl"></bean><bean id="audience" class="com.mfc.xml.noparameter.Audience"></bean><aop:config><aop:aspect ref="audience"><aop:pointcut expression="execution(* com.mfc.xml.noparameter.Performance.perform(..))" id="performance"/><!--  <aop:before method="beforeAdvice" pointcut-ref="performance"/><aop:after-returning method="successAfterAdvice" pointcut-ref="performance"/><aop:after-throwing method="failAfterAdvice" pointcut-ref="performance"/>--><aop:around method="aroundAdvice" pointcut-ref="performance"/></aop:aspect></aop:config></beans>

测试类:

package com.mfc.xml.noparameter;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class XmlConfigTest {public static void main(String[] args) {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("noparameter.xml");Performance performance = (Performance) applicationContext.getBean("performanceImpl");performance.perform();}}

测试结果:



2、通知中有参数:

业务类接口及其实现跟上面(三)中的(2)一样,CompactDisc的接口一起它的实现类。切面类把(三)中的(2)的切面类(TrackCounter.java)里面的注解全部删掉即可。

xml的配置:

<?xml version="1.0" encoding="UTF-8"?><beansxmlns="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-4.1.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd"><bean id="trackCounter" class="com.mfc.xml.hasparameter.TrackCounter"></bean><bean id="blankDisc" class="com.mfc.xml.hasparameter.BlankDisc"><property name="title" value="测试标题"></property><property name="artist" value="测试内容"></property><property name="tracts"><list><value>测试数据一</value><value>测试数据二</value><value>测试数据三</value></list></property></bean><aop:config><aop:aspect ref="trackCounter"><aop:pointcut expression="execution(* com.mfc.xml.hasparameter.CompactDisc.playTrack(String)) and args(trackNumber)" id="performance"/><aop:before method="countTrack" pointcut-ref="performance"/></aop:aspect></aop:config></beans>

测试类:

package com.mfc.xml.hasparameter;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;/** * 2017年11月18日21:40:44 * 测试类 * */public class TrackCounterTest {public static void main(String[] args) {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("hasparameter.xml");CompactDisc compactDisc = (CompactDisc) applicationContext.getBean("blankDisc");TrackCounter counter = (TrackCounter) applicationContext.getBean("trackCounter");compactDisc.playTrack("A");compactDisc.playTrack("A");compactDisc.playTrack("B");compactDisc.playTrack("C");compactDisc.playTrack("B");compactDisc.playTrack("B");System.out.println("A--------"+counter.getPlayCount("A"));System.out.println("B--------"+counter.getPlayCount("B"));System.out.println("C--------"+counter.getPlayCount("C"));}}

测试结果:


2、通过切面引入新的功能

基础功能的业务类接口及其实现和新添加功能的业务类及其接口和(三)中(3)一样,这里可以不要java配置类JavaConfig.java了。

xml配置:

<?xml version="1.0" encoding="UTF-8"?><beansxmlns="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-4.1.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd"><bean id="baseFunctionImpl" class="com.mfc.xml.newfunction.BaseFunctionImpl"></bean><bean id="newFunctionImpl" class="com.mfc.xml.newfunction.NewFunctionImpl"></bean><aop:config><aop:aspect><aop:declare-parents types-matching="com.mfc.xml.newfunction.BaseFunction+" implement-interface="com.mfc.xml.newfunction.NewFunction"default-impl="com.mfc.xml.newfunction.NewFunctionImpl"/></aop:aspect></aop:config></beans>

测试类:

package com.mfc.xml.newfunction;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;/** * 2017年11月18日22:35:14 * 测试类 * */public class Test {public static void main(String[] args) {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("newfunction.xml");BaseFunction baseFunction = (BaseFunction) applicationContext.getBean("baseFunctionImpl");baseFunction.baseFunction();NewFunction newFunction = (NewFunction) baseFunction;newFunction.newFunction();}}

测试结果:


源码下载



阅读全文
0 0