ch03-面向切面编程(AOP)
课程录屏
ASIMOV_CHEN的个人空间-ASIMOV_CHEN个人主页-哔哩哔哩视频 (bilibili.com)
Bean注入的三种方法
构造方法:构造方法有B对象的接口
set方法:
私有属性上加atuowired
Spring的模块组成
软件编程方法的发展
面向过程编程(POP,Procedure Oriented Programming)
面向对象编程(OOP,Object Oriented Programming)
面向切面编程(AOP,Aspect Oriented Programming)
函数式编程(FP, Functional Programming)
反应式编程(RP,Reactive Programming
AOP:Aspect Oriented Programming
切入后
横切关注点(cross-cutting concern)
日志:运维关注
安全
事务
缓存:请求消耗大量时间,缓存可以极大程度减小延迟
可选
继承(inheritance)
委托(delegation)
AOP图解
AOP术语
通知(Advice):切面做什么以及何时做
切点(Pointcut):何处 ,spring只能在方法的前后切,有的别的框架支持在方法的别的地方切
切面(Aspect):Advice和Pointcut的结合
连接点(Join point):方法、字段修改、构造方法
引入(introduction):引入新的行为和状态(给一个对象动态地增加新的方法或者新的属性)
织入(Weaving):切面应用到目标对象的过程
五个通知(Advice)类型
@Before
@After :无论是正常return还是抛出异常,都算after
@AfterReturnin:正常结束
@AfterThrowing :抛出异常
@Around:环绕,几种类型的结合,通过一个around把逻辑切到其他四种
实例
不能破坏业务代码:concert类不能动,接口的获取和调用也都不能动
但是需要再perform前后插入一些逻辑:实现一个切面aspect
concert.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package concert;public class Concert implements Performance { private String myname = "taozhasoheng" ; public String getMyname () { return myname; } public void setMyname (String myname) { this .myname = myname; } @Override public void perform () { System.out.println("perform..." ); } @Override public String toString () { return "taozs" ; } }
performance.java接口
1 2 3 4 5 package concert;public interface Performance { public void perform () ; }
配置类concertConfig.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package concert;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration @EnableAspectJAutoProxy public class ConcertConfig { @Bean public Performance concert () { return new Concert (); } @Bean public Audience audience () { return new Audience (); } @Bean public EncoreableIntroducer encoreableIntroducer () { return new EncoreableIntroducer (); } }
创建上下文myAnnotationApp.java
Annotation注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package app;import concert.*;import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class MyAnnotationApp { public static void main (String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext (ConcertConfig.class); Performance concert = ctx.getBean("concert" , Performance.class); System.out.println(concert.toString() + "" ); System.out.println(concert.getClass().getName()); concert.perform(); } }
切面@Aspect :audience.java
三步走
1.定义切面
java类上加@Aspect
注解,是一个单独的类
before告诉spring这是要在方法执行之前切入的行为
pointcut是一段文本字符串:execution切点表达式
concert.Performance.perform( … ))就是包路径 + 接口 + 调用的方法
即希望在实现了performance的对象的perform的所有重载方法上切,参数不限
*表示返回值也不关心,所有返回值都可以
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package concert;import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.AfterThrowing;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;@Aspect public class Audience { @Before("execution(* concert.Performance.perform( .. ))") public void silenceCellPhones () { System.out.println("Silencing cell phones" ); } @Before("execution(* concert.Performance.perform( .. ))") public void takeSeats () { System.out.println("Taking seats" ); } @AfterReturning("execution(* concert.Performance.perform( .. ))") public void applause () { System.out.println("CLAP CLAP CLAP!!!" ); } @AfterThrowing("execution(* concert.Performance.perform( .. ))") public void demandRefund () { System.out.println("Demand a refund" ); } }
2.开启AspectJ的自动代理机制
在ConcertConfig.java加上@EnableAspectJAutoProxy
不加注解即是实例化切面,spring也不会自动创建代理对象
3.将定义的切面实例化
concertConfig.java中
在配置类中实例化切面类对应的Bean,或者使用@Component
1 2 3 4 @Bean public Audience audience () { return new Audience (); }
实现原理
切与不切通过getBean
获得的对象类型并不一样。
通过创建代理 实现
JDK本身就具备代理能力
针对该对象的调用都会转到代理对象
织入
织入时机
编译期,需要特殊的编译器
类加载期,需要类加载器的处理
运行期: Spring所采纳的方式,使用代理对象、只支持方法级别的连接点
引入接口(introduction)
引入方式
创建需要增加的接口和实现类
新建一个切面类,加@Aspect
在里面定义一个新增实现类的static
接口,加上@DeclareParents
注解
实例化切面类Bean
例子
定义切面,为对象增加新的行为 。Encoreable接口里包含要引入的新的行为的定义。新的行为引入到原有类中的时候,每个类都会实例化一个新的DefaultEncoreable类,一对一。
切面本身是单实例 的,但是实现新行为的类对应的实例和被切入类是一对一 的。
代理对象
JDK提供的代理对象
car.java
1 2 3 4 5 6 7 8 9 10 11 12 13 package proxy;public class Car implements IVehicle { public void run () { System.out.println("Car会跑" ); } @Override public String toString () { return "this is Car{}" ; } }
接口IVehicle.java
1 2 3 4 5 6 package proxy;public interface IVehicle { void run () ; }
App.java创建代理实例
1 2 3 4 5 6 7 8 9 10 11 12 13 package proxy;import java.lang.reflect.Proxy;public class App { public static void main (String[] args) { IVehicle car = new Car (); IVehicle vehicle = (IVehicle) Proxy.newProxyInstance(car.getClass().getClassLoader(), Car.class.getInterfaces(), new VehicalInvacationHandler (car)); vehicle.run(); } }
所有对car的调用都赚到VehicalInvacationHandler类中
所有对vehicle的invoke的调用都会转到这个类中的invoke
VehicalInvacationHandler.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package proxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;public class VehicalInvacationHandler implements InvocationHandler { private final IVehicle vehicle; public VehicalInvacationHandler (IVehicle vehical) { this .vehicle = vehical; } public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { System.out.println("---------before-------" ); Object invoke = method.invoke(vehicle, args); System.out.println("---------after-------" ); return invoke; } }
InvocationHandler.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package java.lang.reflect;import jdk.internal.reflect.CallerSensitive;import jdk.internal.reflect.Reflection;import java.util.Objects;public interface InvocationHandler { @CallerSensitive public static Object invokeDefault (Object proxy, Method method, Object... args) throws Throwable { Objects.requireNonNull(proxy); Objects.requireNonNull(method); return Proxy.invokeDefault(proxy, method, args, Reflection.getCallerClass()); } }
消除重复切面
audience1.java
@pointcut依附一个空类
@Before(“performance()”) 把pointcut做切点表达式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package concert;import org.aspectj.lang.annotation.*;@Aspect public class Audience1 { @Pointcut("execution(* concert.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("Demand a refund" ); } }
切点表达式
可以实现👍
指定在哪些方法上切入
获取参数
限定包路径
限定bean名称,白名单或黑名单
限定在特定注解上切入
Spring AOP
@AspectJ注解驱动的切面
@EnableAspectJAutoProxy //开启AspectJ的自动代理机制
定义切面(@Aspect)
加注解的普通POJO
定义可重用的切点
Around通知
定义参数(CD),测试
AspectJ 切点指示器(pointcut designator)
1 2 3 4 5 @Pointcut( "execution(* soundsystem.CompactDisc.playTrack( int )) " + "&& args(trackNumber) //获取参数 && within(soundsystem.*) //限定包路径 && bean(sgtPeppers) ")
1 2 3 4 5 6 7 @Around("@annotation(innerAuth)") public Object innerAround (ProceedingJoinPoint point, InnerAuth innerAuth) { ... } @InnerAuth public R register (@RequestBody SysUser sysUser) { ... }
Around
@annotation
关键字指定在特定注解上植入
Around
需要一个ProceedingJoinPoint方法参数
对ProceedingJoinPoint方法的调用实际上就是对切点的调用
1 2 3 4 5 6 @Around("@annotation(innerAuth)") 限定注解 public Object innerAround (ProceedingJoinPoint point, InnerAuth innerAuth) { ... }@InnerAuth public R<Boolean> register (@RequestBody SysUser sysUser) {...}
Around实例
audience2.java7
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package concert;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;@Aspect public class Audience2 { @Pointcut("execution(* concert.Performance.perform( .. )) ") public void performance () { } @Around("performance()") public void watchPerformance (ProceedingJoinPoint joinPoint) { try { System.out.println(".Silencing cell phones" ); System.out.println(".Taking seats" ); joinPoint.proceed(); System.out.println(".CLAP CLAP CLAP!!!" ); } catch (Throwable e) { System.out.println(".Demanding a refund" ); } } }
在之前之后都可以,需要一个参数
实现对返回值的修改
XServiceImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 13 package annotation;import org.springframework.stereotype.Service;@Service("xServiceImpl") public class XServiceImpl { @Append public String foo (String val) { return val; } }
实现切面AppendProcessor.java
around对返回值进行了处理(添加了东西) ,将逻辑添加到加了append的方法的前或者后
Append注解加在被植入的foo上,和要植入的逻辑做一个匹配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package annotation;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.springframework.stereotype.Component;@Aspect @Component public class AppendProcessor { @Around("@annotation(appendAnnotation)") public String process (ProceedingJoinPoint joinPoint, Append appendAnnotation) throws Throwable { String res = appendAnnotation.word() + " " + joinPoint.proceed() + " " + appendAnnotation.word(); return res; } }
joinPoint.proceed():原切点处理完
ProceedingJoinPoint joinPoint切点
Append.java
1 2 3 4 5 6 7 8 9 10 11 12 package annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Append { String word () default "***" ; }
配置类MyConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package annotation;import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.AnnotationConfigApplicationContext;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration @ComponentScan @EnableAspectJAutoProxy public class MyConfig { public static void main (String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext (MyConfig.class); XServiceImpl xServiceImpl = ctx.getBean("xServiceImpl" , XServiceImpl.class); System.out.println(xServiceImpl.foo("hello world" )); } }
切面统计实例
BlankDisc实现接口CompactDisc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package soundsystem;import java.util.List;public class BlankDisc implements CompactDisc { private String title; private String artist; private List<String> tracks; public void setTitle (String title) { this .title = title; } public void setArtist (String artist) { this .artist = artist; } public void setTracks (List<String> tracks) { this .tracks = tracks; } public void play () { System.out.println("Playing " + title + " by " + artist); for (String track : tracks) { System.out.println("-Track: " + track); } } public void playTrack (int num) { System.out.println("-Track: " + tracks.get(num)); } }
接口CompactDisc,用户可能经常调用playTrack,想统计每首歌调用了多少次,但是不修改playTrack,统计应该与播放解耦,使用切面实现
1 2 3 4 5 6 7 8 package soundsystem;public interface CompactDisc { void play () ; void playTrack (int num) ; }
配置类TrackCounterConfig
创建了cd
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package soundsystem;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<String> tracks = new ArrayList <>(); tracks.add("Sgt. Pepper's Lonely Hearts Club Band" ); tracks.add("With a Little Help from My Friends" ); tracks.add("Lucky in the Sky with Diamonds" ); tracks.add("Getting Better" ); tracks.add("Fixing a Hole" ); tracks.add("testtest" ); tracks.add("abcabc" ); cd.setTracks(tracks); return cd; } @Bean public TrackCounter trackCounter () { return new TrackCounter (); } }
统计类TrackCounter
获取被截获方法传入的参数值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package soundsystem;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(* soundsystem.CompactDisc.playTrack( int )) " + "&& args(trackNumber)") 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 ; } }
测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package soundsystem;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;import static org.junit.Assert.assertEquals;@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = TrackCounterConfig.class) public class TrackCounterTest { @Autowired private CompactDisc cd; @Autowired private TrackCounter counter; @Test public void testTrackCounter () { cd.playTrack(0 ); cd.playTrack(1 ); cd.playTrack(2 ); cd.playTrack(2 ); cd.playTrack(2 ); cd.playTrack(2 ); cd.playTrack(6 ); cd.playTrack(6 ); assertEquals(1 , counter.getPlayCount(0 )); assertEquals(1 , counter.getPlayCount(1 )); assertEquals(4 , counter.getPlayCount(2 )); assertEquals(0 , counter.getPlayCount(3 )); assertEquals(0 , counter.getPlayCount(4 )); assertEquals(0 , counter.getPlayCount(5 )); assertEquals(2 , counter.getPlayCount(6 )); } }