《Spring实战》读书笔记-第4章 面向切面的Spring,2024年最新刷java面试题的网站是什么

由于Sring是基于代理的,而某些切点表达式是与基于代理的AOP无关的。下表列出了Spring AOP所支持的AspectJ切点指示器。

| AspectJ指示器 | 描述 |

| :-: | :-: |

| arg() | 限制连接点匹配参数为指定类型的执行方法 |

| @args() | 限制连接点匹配参数有指定注解标注的执行方法 |

| execution() | 用于匹配是连接点的执行方法 |

| this() | 限制连接点匹配AOP代理的bean引用为指定类型的类 |

| target | 限制连接点匹配目标对象为指定类型的类 |

| @target() | 限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解 |

| within() | 限制连接点匹配指定的类型 |

| @within() | 限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义在由指定的注解所标注的类里) |

| @annotation | 限制匹配带有指定注解的连接点 |

在Spring中尝试使用AspectJ其他指示器时,将会抛出IllegalArgument-Exception异常。

注意:当我们查看如上所展示的这些Spring支持的指示器时,只有execution指示器是实际执行匹配的,而其他的指示器都是用来限制匹配的。

编写切点

为了阐述Spring中的切面,我们定义一个Performance接口:

package concert;

public interface Performance {

public void perform() ;

}

假设我们想编写Performance的perform()方法触发的通知,下面展示了切点表达式的写法

img

使用AspectJ切点表达式来选择Performance的perform()方法

现在假设我们需要配置的切点仅匹配concert包。我们可以使用within()指示器来限制匹配,如下图

img

使用within指示器限制切点范围

注意:我们使用了“&&”操作符把execution()和within()指示器连接在一起形成与(可以用and代替)关系(切点必须匹配所有的指示器)。类似地,我们可以使用“||操作符”来标识或(可以用or代替)关系,而使用“!”操作符来标识非(可以用not代替)操作。

在切点中选择bean

除了表所列的指示器外,Spring还引入了一个新的bean()指示器,它允许我们在切点表达式中使用bean的ID来标识bean。bean()使用beanID或bean名称作为参数限制切点只匹配特定的bean。

例如,考虑如下的切点:

execution(* concert.Performance.perform())

and bean(‘woodstock’)

还可以使用非操作为除了特定ID以外的其他bean应用通知:

execution(* concert.Performance.perform())

and !bean(‘woodstock’)

4.3 使用注解创建切面


我们已经定义了Performance接口,它是切面中切点的目标对象。现在,让我们使用AspectJ注解来定义切面

AspectJ提供了五个注解来定义通知,如下表所示。

| 注  解 | 通  知 |

| — | — |

| @After | 通知方法会在目标方法返回或抛出异常后调用 |

| @AfterReturning | 通知方法会在目标方法返回后调用 |

| @AfterThrowing | 通知方法会在目标方法抛出异常后调用 |

| @Around | 通知方法会将目标方法封装起来 |

| @Before | 通知方法会在目标方法调用之前执行 |

定义切面

我们将观众定义为一个切面,并将其应用到演出上就是较为明智的做法。

下面为Audience类的代码

package com.springinaction.perf;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.*;

//切面 POJO

@Aspect

public class Audience {

//定义命名的切点

@Pointcut(“execution(** com.springinaction.perf.Performance.perform(…))”)

public void performance(){

}

//定义通知

@Before(“performance()”) // 表演之前

public void silenceCellPhones(){

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

}

@Before(“performance()”) // 表演之前

public void takeSeats(){

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

}

@AfterReturning(“performance()”) // 表演之后

public void applause(){

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

}

@AfterThrowing(“performance()”) // 表演失败之后

public void demandRefund(){

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

}

@Around(“performance()”) // 环绕通知方法

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”);

}

}

}

关于环绕通知,我们首先注意到它接受ProceedingJoinPoint作为参数。这个对象是必须要有的,因为要在通知中通过它来调用被通知的方法。

需要注意的是,一般情况下,别忘记调用proceed()方法。如果不调用,那么通知实际上会阻塞对被通知方法的调用,也许这是所期望的效果。当然,也可以多次调用,比如要实现一个场景是实现重试逻辑。

除了注解和没有实际操作的performance()方法,Audience类依然是一个POJO,可以装配为Spring中的bean

@Bean

public Audience audience(){ // 声明Audience

return new Audience();

}

除了定义切面外,还需要启动自动代理,才能使这些注解解析。

如果使用JavaConfig的话,需要如下配置

package com.springinaction.perf;

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

@EnableAspectJAutoProxy //启动AspectJ自动代理

public class AppConfig {

@Bean

public Audience audience(){ // 声明Audience

return new Audience();

}

}

假如在Spring中要使用XML来装配bean的话,那么需要使用Spring aop命名空间中的<aop:aspectj-autoproxy>元素。

<?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还是属性被引入的接口。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

最后的话

无论是哪家公司,都很重视Spring框架技术,重视基础,所以千万别小看任何知识。面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。
同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,好了希望这篇文章对大家有帮助!

部分截图:
在这里插入图片描述

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img

链图片转存中…(img-Hb0QCQ3l-1712785179302)]
[外链图片转存中…(img-N9zRy0zn-1712785179303)]
[外链图片转存中…(img-IptGOgNV-1712785179303)]
[外链图片转存中…(img-7Mh9o3MB-1712785179304)]
[外链图片转存中…(img-GbNcvdMT-1712785179304)]
[外链图片转存中…(img-F1nqE4Xy-1712785179305)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-6m68zFJB-1712785179305)]

最后的话

无论是哪家公司,都很重视Spring框架技术,重视基础,所以千万别小看任何知识。面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。
同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,好了希望这篇文章对大家有帮助!

部分截图:
[外链图片转存中…(img-0q0VHypl-1712785179306)]

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-V1Cb6uhf-1712785179306)]

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值