前言:
大概在前两章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--------