《Spring实战》读书笔记-第4章 面向切面的Spring(2)

<?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:aop=“http://www.springframework.org/schema/aop”

xmlns:context=“http://www.springframework.org/schema/context”

xsi:schemaLocation="http://www.springframework.org/schema/aop

http://www.springframework.org/schema/aop/spring-aop.xsd

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.xsd">

<context:component-scan base-package=“com.springinaction.perf” />

<aop:aspectj-autoproxy />

无论使用JavaConfig还是XML,Aspect自动代理都会使用@Aspect注解的bean创建一个代理,这个代理会围绕着所有该切面的切点所匹配的bean。这种情况下,将会为Concert的bean创建一个代理,Audience类中的通知方法将会在perform()调用前后执行。

我们需要记住的是,Spring的AspectJ自动代理仅仅使用@AspectJ作为创建切面的指导,切面依然是基于代理的。本质上,它依然是Spring基于代理的切面。

处理通知中的参数

目前为止,除了环绕通知,其他通知都没有参数。如果切面所通知的方法确实有参数该怎么办呢?切面能访问和使用传递给被通知方法的参数吗?

为了阐述这个问题,我们来重新看一下BlankDisc样例。假设你想记录每个磁道被播放的次数。为了记录次数,我们创建了TrackCounter类,它是通知playTrack()方法的一个切面。

package com.springinaction.disc;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.aspectj.lang.annotation.Pointcut;

import java.util.HashMap;

import java.util.Map;

@Aspect

public class TrackCounter {

private Map<Integer, Integer> trackCounts = new HashMap<>();

@Pointcut(“execution(* com.springinaction.disc.CompactDisc.playTrack(int))” +

“&& args(trackNumber)”) // 通知playTrack()方法

public void trackPlayed(int trackNumber){}

@Before(“trackPlayed(trackNumber)”) // 在播放前,为该磁道计数

public void countTrack(int trackNumber){

int currentCount = getPlayCount(trackNumber);

trackCounts.put(trackNumber, currentCount + 1);

}

public int getPlayCount(int trackNumber){

return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0;

}

}

以下为切点表达式分解

img

在切点表达式中声明参数,这个参数传入到通知方法中

其中args(trackNumber)限定符表明传递给playTrack()方法的int类型参数也会传递到通知中去。trackNumber也与切点方法签名中的参数相匹配。切点定义中的参数与切点方法中的参数名称是一样的。

下面我们启动AspectJ自动代理以及定义bean

package com.springinaction.disc;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.EnableAspectJAutoProxy;

import java.util.ArrayList;

import java.util.List;

@Configuration

@EnableAspectJAutoProxy

public class TrackCounterConfig {

@Bean

public CompactDisc sgtPeppers(){

BlankDisc cd = new BlankDisc();

cd.setTitle(“Sgt. Pepper’s Lonely Hearts Club Band”);

cd.setArtist(“The Beatles”);

List tracks = new ArrayList<>();

tracks.add(“Sgt. Pepper’s Lonely Hearts Club Band”);

tracks.add(“With a Little Help from My Friends”);

tracks.add(“Luck in the Sky with Diamonds”);

tracks.add(“Getting Better”);

tracks.add(“Fixing a Hole”);

tracks.add(“Feel My Heart”);

tracks.add(“L O V E”);

cd.setTracks(tracks);

return cd;

}

@Bean

public TrackCounter trackCounter(){

return new TrackCounter();

}

}

最后的简单测试

package com.springinaction;

import static org.junit.Assert.*;

import com.springinaction.disc.CompactDisc;

import com.springinaction.disc.TrackCounter;

import com.springinaction.disc.TrackCounterConfig;

import org.junit.Rule;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.junit.contrib.java.lang.system.StandardOutputStreamLog;

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 = TrackCounterConfig.class)

public class TrackCounterTest {

@Rule

public final StandardOutputStreamLog log = new StandardOutputStreamLog();

@Autowired

private CompactDisc cd;

@Autowired

private TrackCounter counter;

@Test

public void testTrackCounter(){

cd.playTrack(1);

cd.playTrack(2);

cd.playTrack(3);

cd.playTrack(3);

cd.playTrack(3);

cd.playTrack(3);

cd.playTrack(7);

cd.playTrack(7);

assertEquals(1,counter.getPlayCount(1));

assertEquals(1,counter.getPlayCount(2));

assertEquals(4,counter.getPlayCount(3));

assertEquals(0,counter.getPlayCount(4));

assertEquals(0,counter.getPlayCount(5));

assertEquals(0,counter.getPlayCount(6));

assertEquals(2,counter.getPlayCount(7));

}

}

通过注解引入新功能

我们除了给已有的方法添加新功能外,还可以添加一些额外的功能。

回顾一下,在Spring中,切面只是实现了它们所包装bean相同的接口代理。如果除了实现这些接口,代理也能暴露新接口。即便底层实现类并没有实现这些接口,切面所通知的bean也能实现新的接口。下图展示了它们是如何工作的。

img

使用Spring AOP,我们可以为bean引入新的方法。代理拦截调用并委托给实现该方法的其他对象

需要注意的是,当引入接口的方法被调用时,代理会把此调用委托给实现了新接口的某个其他对象。实际上,一个bean的实现被拆分到了多个类中。

为了验证能行得通,我们为所有的Performance实现引入Encoreable接口

package com.springinaction.perf;

public interface Encoreable {

void performEncore();

}

借助于AOP,我们创建一个新的切面

package com.springinaction.perf;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.DeclareParents;

@Aspect

public class EncoreableIntroducer { // 需要给Performance和其实现类额外添加方法的实现

@DeclareParents(value = “com.springinaction.perf.Performance+”,

defaultImpl = DefaultEncoreable.class)

public static Encoreable encoreable;

}

其中@DeclareParents注解,将Encoreable接口引入到Performance bean中。

@DeclareParents注解有三个部门组成:

  • value属性指定了哪种类型的bean要引入该接口。(本例中,就是Performance,加号表示Performance的所有子类型)

  • defaultImpl属性指定了为引入功能提供实现的类。

  • @DeclareParents注解所标注的静态属性指明了要引入的接口。

然后需要在配置中声明EncoreableIntroducer的bean

@Bean

public EncoreableIntroducer encoreableIntroducer(){

return new EncoreableIntroducer();

}

当调用委托给被代理的bean或被引入的实现,取决于调用的方法属性被代理的bean还是属性被引入的接口。

在Spring中,注解和自动代理提供了一种很便利的方式来创建切面。但有一个劣势:必须能够为通知类添加注解,要有源码。

如果没有源码或者不想注解到你的代码中,能可选择Spring XML配置文件中声明切面。

4.4 在XML中声明切面


如果声明切面,但不能为通知类添加注解时,需要转向XML配置了。

在Spring的aop命名空间中,提供了多个元素用来在XML中声明切面,如下表所示

| AOP配置元素 | 用途 |

| — | — |

| <aop:advisor> | 定义AOP通知器 |

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

| <aop:after-returning> | 定义AOP返回通知 |

| <aop:after-throwing> | 定义AOP异常通知 |

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

| <aop:aspect> | 定义一个切面 |

| <aop:aspectj-autoproxy> | 启用@AspectJ注解驱动的切面 |

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

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

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

| <aop:pointcut> | 定义一个切点 |

我们重新看一下Audience类,这一次我们将它所有的AspectJ注解全部移除掉:

package com.springinaction.perf;

public class Audience {

public void silenceCellPhones(){

System.out.println(“Silencing cell phones”);

}

public void takeSeats(){

System.out.println(“Taking seats”);

}

public void applause(){

System.out.println(“CLAP CLAP CLAP”);

}

public void demandRefund(){

System.out.println(“Demanding a refund”);

}

public void watchPerformance(ProceedingJoinPoint jp){

try {

System.out.println(“Silencing cell phones Again”);

System.out.println(“Taking seats Again”);

jp.proceed();

System.out.println(“CLAP CLAP CLAP Again”);

}

catch (Throwable e){

System.out.println(“Demanding a refund Again”);

}

}

}

声明前置、后置以及环绕通知

下面展示了所需要的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:aop=“http://www.springframework.org/schema/aop”

xsi:schemaLocation="http://www.springframework.org/schema/aop

http://www.springframework.org/schema/aop/spring-aop-3.2.xsd

http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd">

aop:config

<aop:aspect ref=“audience”>

<aop:pointcut id=“perf” expression=“execution(* com.springinaction.perf.Performance.perform(…))” />

<aop:before pointcut-ref=“perf” method=“silenceCellPhones” />

<aop:before pointcut-ref=“perf” method=“takeSeats” />

<aop:after-returning pointcut-ref=“perf” method=“applause” />

<aop:after-throwing pointcut-ref=“perf” method=“demandRefund”/>

<aop:around pointcut-ref=“perf” method=“watchPerformance”/>

</aop:aspect>

</aop:config>

为通知传递参数

我们使用XML来配置BlankDisc。

首先,移除掉TrackCounter上所有的@AspectJ注解。

package com.springinaction.disc;

import java.util.HashMap;

import java.util.Map;

public class TrackCounter {

private Map<Integer, Integer> trackCounts = new HashMap<>();

// 在播放前,为该磁道计数

public void countTrack(int trackNumber){

int currentCount = getPlayCount(trackNumber);

trackCounts.put(trackNumber, currentCount + 1);

}

public int getPlayCount(int trackNumber){

return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0;

}

}

下面展示了在XML中将TrackCounter配置为参数化的切面

<?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:aop=“http://www.springframework.org/schema/aop”

xsi:schemaLocation="http://www.springframework.org/schema/aop

http://www.springframework.org/schema/aop/spring-aop-3.2.xsd

http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd">

Sgt. Pepper’s Lonely Hearts Club Band

With a Little Help from My Friends

Lucy in the Sky with Diamonds

Getting Better

Fixing a Hole

Feel My Heart

L O V E

aop:config

<aop:aspect ref=“trackCounter”>

<aop:pointcut id=“trackPlayed” expression="

execution(* com.springinaction.disc.CompactDisc.playTrack(int))

and args(trackNumber)" />

<aop:before pointcut-ref=“trackPlayed” method=“countTrack”/>

</aop:aspect>

</aop:config>

注意:在XML中,“&”符号会被解析为实体的开始,所用“and”关键字。

通过切面引入新的功能

使用Spring aop命名空间中的<aop:declare-parents>元素,我们可以实现相同的功能。

aop:aspect

<aop:declare-parents types-matching=“com.springinaction.perf.Performance+”

implement-interface=“com.springinaction.perf.Encoreable”

default-impl=“com.springinaction.perf.DefaultEncoreable” />

</aop:aspect>

我们还可以使用delegate-ref属性来标识

aop:aspect

<aop:declare-parents types-matching=“com.springinaction.perf.Performance+”

implement-interface=“com.springinaction.perf.Encoreable”

delegate-ref=“defaultEncoreable” />

</aop:aspect>

delegate-ref属性引用了一个Spring bean作为引入的委托。

4.5 注入AspectJ切面


AspectJ提供了Spring AOP所不能支持的许多类型的切点。例如:构造器切点就非常方便。

为了演示,我们新创建一个切面,我们以切面的方式创建一个评论员的角色,演出后提一些批评意见。

首先创建这样的一个切面

package com.springinaction.perf;

public aspect CriticAspect {

public CriticAspect(){}

pointcut performance() : execution(* perform(…));

after() returning : performance() {

System.out.println(criticismEngine.getCriticism());

}

private CriticismEngine criticismEngine;

public CriticismEngine getCriticismEngine() {

return criticismEngine;

}

public void setCriticismEngine(CriticismEngine criticismEngine) {

this.criticismEngine = criticismEngine;

}

}

然后是CriticismEngine的接口

package com.springinaction.perf;

public interface CriticismEngine {

String getCriticism();

}

CriticismEngine的实现类

package com.springinaction.perf;

public class CriticismEngineImpl implements CriticismEngine {

public CriticismEngineImpl(){}

@Override

public String getCriticism() {

int i = (int) (Math.random() * criticismPool.length);

return criticismPool[i];

}

private String[] criticismPool;

最后

整理的这些资料希望对Java开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

image

image

其实面试这一块早在第一个说的25大面试专题就全都有的。以上提及的这些全部的面试+学习的各种笔记资料,我这差不多来回搞了三个多月,收集整理真的很不容易,其中还有很多自己的一些知识总结。正是因为很麻烦,所以对以上这些学习复习资料感兴趣,

() {

return criticismEngine;

}

public void setCriticismEngine(CriticismEngine criticismEngine) {

this.criticismEngine = criticismEngine;

}

}

然后是CriticismEngine的接口

package com.springinaction.perf;

public interface CriticismEngine {

String getCriticism();

}

CriticismEngine的实现类

package com.springinaction.perf;

public class CriticismEngineImpl implements CriticismEngine {

public CriticismEngineImpl(){}

@Override

public String getCriticism() {

int i = (int) (Math.random() * criticismPool.length);

return criticismPool[i];

}

private String[] criticismPool;

最后

整理的这些资料希望对Java开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

[外链图片转存中…(img-oiZxGccL-1714505450049)]

[外链图片转存中…(img-FFDKuUiC-1714505450050)]

其实面试这一块早在第一个说的25大面试专题就全都有的。以上提及的这些全部的面试+学习的各种笔记资料,我这差不多来回搞了三个多月,收集整理真的很不容易,其中还有很多自己的一些知识总结。正是因为很麻烦,所以对以上这些学习复习资料感兴趣,

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 26
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值