Spring实战读书笔记 面向切面(1)

什么是面向切面

散布于应用中多处的功能称为横切关注点,这么横切关注点从概念上是与应用的业务逻辑相分离。AOP可以实现横切关注点与它们所影响的对象之间的解耦。
横切关注点可以被模块化为特殊类。这些类被称为切面。
好处:
①、每个关注点都集中在一个地方,为不是分散在多处代码中。
②、服务模块更简洁。因为只包含主要关注点(或核心功能)的代码,而次要关注点的代码被转移的切面中。

AOP术语

在这里插入图片描述
通知(Advice)
切面的工作被称为通知。通知定义了切面是什么及何时使用。
5种类型的通知:
①、前置通知(Before):在目标方法被调用之前调用通知功能。
②、后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么。
③、返回通知(After-returning):在目标方法成功执行之后调用通知。
④、异常通知(After-returning):在目标方法抛出异常后调用通知。
⑤、环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
连接点(Join point) (哪里可以织入)
连接点是在应用执行过程中能够插入切面的一个点。切面代码可以利用这些点插入到应用的正常流程中,并添加新的行为。
切点(Pointcut) (在哪里织入)
通知定义了切面的“什么”和“何时”的话,那么切点就定义了“何处”。切点的定义会匹配通知所要织入的一个或多个连接点。
切面(Aspect)
切面是通知切点的结合,通知和切点共同定义了切面的全部内容–是什么,在何时和何处。
织入(Weaving)
织入是把切面应用到目标对象并创建新的代理对象的过程。
在目标对象的生命周期有多个点可以织入:
①、编译器:切面在目标类编译时被织入。(AspectJ的织入编译器就是这种方式织入切面的)。
②、类加载器:切面在目标类加载到JVM时被织入。
③、运行期:切面在应用运行的某个时刻被织入。AOP为目标对象动态的创建一个代理对象。Spring AOP级以这种方式织入切面。
总结
通知包含了需要用于多个应用对象的横切行为,连接点是在程序执行过程中能够应用通知的所有的点。切点定义了通知被应用的具体位置。切点定义了哪些连接点会得到通知。
Spring对AOP的支持
①、基于代理的经典Spring AOP
②、纯POJO
③、@AspctJ注解驱动的切面
④、注入式ApjectJ切面。
前三种都是Spring AOP实现的变体,基于动态代理基础之上,因此Spring对AOP的支持仅限于对方法的拦截
Spring AOP支持的切点指示器。
在这里插入图片描述
在这里插入图片描述
定义切面

@Aspect
public class Audience {
    //*任意返回类型,
    //com.beanConfig.interfaceS.Performance方法
    //perform方法
    //..任意参数
//    @Before("execution(* com.beanConfig.interfaceS.Performance.perform(..))")
//    @Before("execution(* com.beanConfig.*.Performance.perform(..))")
    @Before("execution(* com.beanConfig.*.*mance.perform(..))") //*可以上省略你某个包,甚至还可以像模糊查询一样
    public void silencePhone() {
        System.out.println("Silencing cell phone");
    }
    @Before("execution(* com.beanConfig.*.*mance.perform(..))")
    public void takeSeats() {
        System.out.println("Take seats");
    }
    @AfterReturning("execution(* com.beanConfig.*.*mance.perform(..))")
    public void applaues() {
        System.out.println("CLAP CLAP CLAP");
    }
    @AfterThrowing("execution(* com.beanConfig.*.*mance.perform(..))")
    public void demandRefund() {
        System.out.println("Demanding a refund");
    }
}

我们发现以上的切点都是可重用的。那么就可是使用 @Pointcut注解定义可重用切点。

@Aspect
public class Audience {
    
    @Pointcut("execution(* com.beanConfig.*.*mance.perform(..))")
    public void performance() {
    }
    @Before("performance()")
    public void silencePhone() {
        System.out.println("Silencing cell phone");
    }
    @Before("performance()")
    public void takeSeats() {
        System.out.println("Take seats");
    }
    @AfterReturning("performance()")
    public void applaues() {
        System.out.println("CLAP CLAP CLAP");
    }
    @AfterThrowing("performance())")
    public void demandRefund() {
        System.out.println("Demanding a refund");
    }

}

Audience 依然是一个POJO,也可以把它装配为bean。

 @Bean
    public Audience audience() {
        return new Audience();
    }

如果仅仅这样,Audience 仅仅只是spring容器中的一个bean,而不是切面。
我们需要手动开启自动代理功能。
第一种:@EnableAspectJAutoProxy 注解

@Configuration
@EnableAspectJAutoProxy //开启AspectJ自动代理
public class AspectAOPConfig {
    @Bean
    public Audience audience() {
        return new Audience();
    }
    @Bean
    public Performance show() {
        return new Show();
    }
}

第二种:xml配置

 <aop:aspectj-autoproxy/>
 <bean class="com.beanConfig.aspectAOP.Audience"></bean>

测试类:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AspectAOPConfig.class)
public class AOPTest {
    @Autowired
    private Audience audience;
    @Autowired
    private Performance performance;
    @Test
    public void play() {
        performance.perform();
    }
}

环绕通知
看代码注释吧。

@Aspect
public class AroundAudience {

    @Pointcut("execution(* *com.beanConfig.interfaceS.Performance.perform(..))")
    public void performance() {
    }
    @Around("performance()")
    //ProceedingJoinPoint 通过它调用被通知的方法。
    public void watchPerformance(ProceedingJoinPoint jp) {
        try {
            System.out.println("Silencing cell phone");
            System.out.println("Take seats");
            //当要将控制权交给被通知的方法时,调用ProceedingJoinPoint的proceed()方法。
            //如果不调用的话,阻塞被通知方法的访问。
            jp.proceed();
            System.out.println("CLAP CLAP CLAP");
        } catch (Throwable throwable) {
            System.out.println("Demanding a refund");
        }
    }

}

处理通知中的参数
为了记录每个磁道播放的次数,创建切面

/**
 * Created by 31696 on 2019/5/7.
 * 为记录每个磁道播放的次数
 */
@Aspect
public class TrackCounter {
    private Map<Integer,Integer> trackCounts = new HashMap<Integer, Integer>();
    @Pointcut("execution(* com.beanConfig.classes.bean.BlankDisc.playTracks(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;
    }
}

切点

 @Pointcut("execution(* com.beanConfig.classes.bean.BlankDisc.playTracks(int)) && args(trackNumber)")

int:表示接收int类型的参数。
args(trackNumber):限定符,表示传递给playTracks()方法的int类型参数也要传到通知中去,且参数的名称也要与切点方法签名的参数相匹配。

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;
    }
    @Override
    public void playTracks(int trackNumber) {
    }

    @Override
    public void play() {
    }
}
@Configuration
@EnableAspectJAutoProxy
public class TrackCountConfig {
    @Bean
    public CompactDisc blankDisc() {
        BlankDisc blankDisc = new BlankDisc();
        blankDisc.setTitle("sgt.Papers");
        blankDisc.setArtist("The Beatles");
        List<String> tracks = new ArrayList<String>();
        tracks.add("AAAAAA");
        tracks.add("BBBBBB");
        tracks.add("CCCCCC");
        tracks.add("DDDDDD");
        tracks.add("EEEEEE");
        tracks.add("FFFFFF");
        tracks.add("HHHHHH");
        blankDisc.setTracks(tracks);
        return blankDisc;
    }
    @Bean
    public TrackCounter trackCounter () {
        return new TrackCounter();
    }
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= TrackCountConfig.class)
public class AOPArgsTest {
    @Autowired
    private CompactDisc blankDisc;

    @Autowired
    private TrackCounter counter;

    @Test
    public void testTrackCounter() {
        blankDisc.playTracks(1);
        blankDisc.playTracks(2);
        blankDisc.playTracks(3);
        blankDisc.playTracks(3);
        blankDisc.playTracks(3);
        blankDisc.playTracks(3);
        blankDisc.playTracks(7);
        blankDisc.playTracks(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)));
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值