您的位置:首页 > 编程语言 > Java开发

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

2017-08-28 16:34 417 查看

前言:

大概在前两章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--------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: