Spring实战——面向切面的Spring

在我的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 {

	@Override
	public 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
 * 切面类
 * */
@Aspect
public 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 {

	@Bean
	public 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);
		}
	}
	@Override
	public 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
 * 切面类
 * */
@Aspect
public 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
@EnableAspectJAutoProxy
public 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 {

	@Override
	public 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 {

	@Override
	public 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
@Aspect
public 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"?>
<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-4.1.xsd
	http://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"?>
<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-4.1.xsd
	http://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"?>
<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-4.1.xsd
	http://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();
	}
}

测试结果:


源码下载



评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值