【Spring in Action】Spring的AOP基础知识及切面运用

前言:

大概在前两章Spring in Action的文章中写了Spring的AOP的简单运用,以及Spring依赖注入DI和Bean的装配各种不同的套路。

一、温故知新:

一种场景正好温习一下:一个接口多个实现的时候。如下:

1、接口Performance

package com.concert;

import org.aspectj.lang.annotation.Pointcut;

public interface Performance {
	void perform();
}

2、实现一:PerformanceImpl

package com.concert;

import org.springframework.stereotype.Component;

@Component
public class PerformanceImpl implements Performance {
	public PerformanceImpl() {
		
	}
	@Override
	public void perform() {
		System.out.println("----------执行perform中----------");
	}
}
3、实现二:Singer

package com.concert;

import org.springframework.stereotype.Component;

@Component
public class Singer implements Performance {
	@Override
	public void perform() {
		System.out.println("---------我是一个演唱家Singer,我正在表演perform----------");
	}
}

4、JavaConfig自动装配:SpringConfig

package com.concert;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan
public class SpringConfig {
}
5、测试用例:Test001

package com.concert;

import static org.junit.Assert.*;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=SpringConfig.class)
public class Test001 {
	@Autowired
	private Performance pe;
	@Test
	public void test() {
		pe.perform();
	}
}

6、大概会看到这样一个错误:(意思是找到了两个符合要求的匹配项,机器不知道选哪个所以抛出异常了)

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.concert.Test001': Unsatisfied dependency expressed through field 'pe': No qualifying bean of type [com.concert.Performance] is defined: expected single matching bean but found 2: performanceImpl,singer; 

7、解决方案就是:自己告诉机器指定一个实现类。SpringConfig去掉@ComponentScan注解。然后修改后的如下:

package com.concert;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
public class SpringConfig {
	@Bean
	public Performance getPerformance(){
		return new PerformanceImpl();
	}
}

8、我这里指定PerformanceImpl。好吧,这都不是重点。这期的重点是AOP切面。

二、AOP切面的使用:

1、写一个日志类:加@Aspect,加@Before("execution(* com.concert.Performance.perform(..))")等:

package com.concert;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class Log {
	@Before("execution(* com.concert.Performance.perform(..))")
	public void beforeLog() {
		System.out.println("前置日志----beforeLog");
	}
	@After("execution(* com.concert.Performance.perform(..))")
	public void afterLog() {
		System.out.println("后置日志----afterLog");
	}
}

2、光写了Log日志类,还只是一个普通的java 对象(POJO)。还需要在配置中生成Log对象,设置自动扫描@EnableAspectJAutoProxy。

package com.concert;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
public class SpringConfig {
	@Bean
	public Performance getPerformance(){
		return new PerformanceImpl();
	}
	@Bean
	public Log log(){
		return new Log(); 
	}
}

3、再次执行测试代码Test001执行结果:

前置日志----beforeLog

----------执行perform中----------

后置日志----afterLog


三、总结分析:

1.切点是什么?切点:com.concert.Performance.perform(..),凡是实现这个接口的实例调用perform方法都会执行。

2.使用切点每次都写一长串,execution(* com.xmlconcert.Performance.perform(..))简化如下:

package com.xmlconcert;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class Log {
	@Pointcut("execution(* com.xmlconcert.Performance.perform(..))")
	public void perform(){
	}
	
	@Before("perform()")
	public void beforeLog() {
		System.out.println("前置日志----beforeLog");
	}
	@After("perform()")
	public void afterLog() {
		System.out.println("后置日志----afterLog");
	}
}

3.切面是什么?Log类是切面。

4.如何设置自动代理?当前是通过JavaConfig的方式,对SpringConfig添加了@EnableAspectJAutoProxy,并在容器中生成了切面的Bean(Pojo)。当然也可以通过xml方式配置。xml配置思路同JavaConfig:

<?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.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
	<context:component-scan base-package="com.xmlconcert"></context:component-scan>
	<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
	<!-- 声明bean -->
	<bean class="com.xmlconcert.Log"></bean>
</beans>
然后删除SpringConfig,删除这个因为是通过xml配置,排除干扰。删除Singer,是因为自动扫描,一对多的话还是会报异常。就是最上面说的异常。修改Test001:
package com.xmlconcert;


import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test001 {
	@Autowired
	private Performance pe;
	@Test
	public void test() {
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("com/xmlconcert/applicationContext.xml");
		pe = ctx.getBean(Performance.class);
		pe.perform();
		ctx.close();
	}
}
由于我换了一个com.xmlconcert,之前是com.concert,所以替换掉其中的所有。包括Log的切面代码:

package com.xmlconcert;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class Log {
	
	@Before("execution(* com.xmlconcert.Performance.perform(..))")
	public void beforeLog() {
		System.out.println("前置日志----beforeLog");
	}
	@After("execution(* com.xmlconcert.Performance.perform(..))")
	public void afterLog() {
		System.out.println("后置日志----afterLog");
	}
}
执行结果:

前置日志----beforeLog

----------执行perform中----------

后置日志----afterLog

5、环绕通知:修改Log

package com.xmlconcert;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class Log {
	@Pointcut("execution(* com.xmlconcert.Performance.perform(..))")
	public void perform(){
	}
	
//	@Before("perform()")
//	public void beforeLog() {
//		System.out.println("前置日志----beforeLog");
//	}
//	@After("perform()")
//	public void afterLog() {
//		System.out.println("后置日志----afterLog");
//	}
	
	//	创建环绕通知
	@Around("perform()")
	public void watchPerformance(ProceedingJoinPoint jp){
		try {
			System.out.println("电话静音,坐下");
			jp.proceed();
		} catch (Throwable e) {
			e.printStackTrace();
			System.out.println("演出失败,退款");
		}
	}
	
}
执行结果:

电话静音,坐下

----------执行perform中----------

其中入参ProceedingJoinPoint,jp.proceed()是执行玩环绕通知后执行perform方法。如果不使用,则不执行perform。perform被拦截了。

其中System.out.println("演出失败,退款");是在抛出异常的时候执行。例如null指针异常。


6、处理通知中的参数:

Performance

package com.xmlconcert;

import org.aspectj.lang.annotation.Pointcut;

public interface Performance {
	void perform();
	void performArgs(int arg);
}

Log

package com.xmlconcert;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class Log {
	@Pointcut("execution(* com.xmlconcert.Performance.performArgs(int)) && args(num)")
	public void perform(int num){
		
	}
	
	@Before("perform(num)")
	public void beforeLog(int num) {
		System.out.println("前置日志----beforeLog"+num);
	}
	@After("perform(num)")
	public void afterLog(int num) {
		System.out.println("后置日志----afterLog"+num);
	}
	
	//	创建环绕通知
//	@Around("perform(num)")
//	public void watchPerformance(ProceedingJoinPoint jp){
//		try {
//			System.out.println("电话静音,坐下");
//			jp.proceed();
//		} catch (Throwable e) {
//			e.printStackTrace();
//			System.out.println("演出失败,退款");
//		}
//	}
	
}

Test001

package com.xmlconcert;


import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test001 {
	@Autowired
	private Performance pe;
	@Test
	public void test() {
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("com/xmlconcert/applicationContext.xml");
		pe = ctx.getBean(Performance.class);
		pe.performArgs(100001);
		ctx.close();
	}
}
执行结果:

前置日志----beforeLog100001

----------执行performArgs中----------100001

后置日志----afterLog100001


7、这个例子可能还不明显,由书中的例子改的举例如下:

Player

package demo;

public interface Player {
	void play(String song,int num);
}
CDPlayer
package demo;

import org.springframework.stereotype.Component;

@Component
public class CDPlayer implements Player {

	@Override
	public void play(String song, int num) {
		System.out.println("歌曲名:"+song+" 播放次数:"+num);
	}
}

AopLog

package demo;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class AopLog {
	//计数器
	private int numCounter = 0;
	@Pointcut("execution(* demo.Player.play(String,int))&&args(song,num)")
	public void play(String song,int num){
	}
	
	@Before("play(song,num)")
	public void before(String song,int num){
		numCounter = numCounter+num;
	}
	
	public void getNum(){
		System.out.println("CDPlayer共播放"+numCounter+"次");
	}
}
xmlplayer.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: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.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
	<context:component-scan base-package="demo"></context:component-scan>	
	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
	<bean class="demo.AopLog"></bean>
</beans>
TestDemo
package demo;

import static org.junit.Assert.*;

import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestDemo {
	@Autowired
	private Player p;
	@Autowired
	private AopLog a;
	@Test
	public void test() {
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("demo/xmlplayer.xml");
		p = ctx.getBean(Player.class);
		p.play("周杰伦的歌", 1);
		p.play("周杰伦的歌", 2);
		p.play("周杰伦的歌", 3);
		p.play("周杰伦的歌", 4);
		a = ctx.getBean(AopLog.class);
		a.getNum();
		ctx.close();
	}
}
执行结果:

歌曲名:周杰伦的歌 播放次数:1

歌曲名:周杰伦的歌 播放次数:2

歌曲名:周杰伦的歌 播放次数:3

歌曲名:周杰伦的歌 播放次数:4

播放10次


四、Spring AOP 使用接口注入为API引入新功能:

Performer

package newfunc;

public interface Performer {
	void play();
}

MisZhang

package newfunc;

import org.springframework.stereotype.Component;

@Component
public class MisZhang implements Performer {

	@Override
	public void play() {
		//张女士是表演者
		System.out.println("唱青藏高原");
	}

}
applicationContext.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: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.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
	<bean id="miszhang" class="newfunc.MisZhang"></bean>
</beans>
Test001

package newfunc;

import static org.junit.Assert.*;

import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test001 {
	private Performer p;
	@Test
	public void test() {
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("newfunc/applicationContext.xml");
		p = (Performer) ctx.getBean("miszhang");
		p.play();
		ctx.close();
	}

}

执行结果:

唱青藏高原

需求:现在想对张女士MisZhang.class类,新增一个角色Artister接口。即由原本:张女士是一个唱青藏高原的人-----》升级为:张女士是一个会唱青藏高原的女艺术家。但是不允许对MisZhang.class做任何修改。

Artister

package newfunc;

public interface Artister {
	void honor();
}

DefaultArtister

package newfunc;

import org.springframework.stereotype.Component;

@Component
public class DefaultArtister implements Artister {

	@Override
	public void honor() {
		System.out.println("--------艺术家光环普照----------");
	}

}
新增切面

EncoreableIntroducer

package newfunc;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;

@Aspect
public class EncoreableIntroducer {
	@DeclareParents(value="newfunc.MisZhang+",defaultImpl=DefaultArtister.class)
	public static Artister artister;
}

修改applicationContext.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: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.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
	<aop:aspectj-autoproxy>
	</aop:aspectj-autoproxy>
	<bean id="miszhang" class="newfunc.MisZhang"></bean>
	<bean class="newfunc.DefaultArtister"></bean>
	<bean class="newfunc.EncoreableIntroducer"></bean>
</beans>

修改Test001

package newfunc;

import static org.junit.Assert.*;

import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test001 {
	private Performer p;
	private Artister a;
	@Test
	public void test() {
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("newfunc/applicationContext.xml");
		p = (Performer) ctx.getBean("miszhang");
		p.play();
		a = (Artister) ctx.getBean("miszhang");
		a.honor();
		ctx.close();
	}

}
执行结果:

唱青藏高原

--------艺术家光环普照----------


over--------















































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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值