文档章节

Spring in Action4-AOP

TSMYK
 TSMYK
发布于 2017/05/22 14:29
字数 3178
阅读 67
收藏 0

##Spring in Action4-AOP Spring in Action4的读书笔记之AOP

在我们编写程序的过程中,如果要重用通用功能的话,在 Java中最常见的技术继承(inheritance) 或委托 (delegation) ,但是, 如果在整个应用中都使用相同的基类, 继承往往会导致一个脆弱的对象体系; 而使用委托可能需要对委托对象进行复杂的调用。

切面提供了取代继承和委托的另一种可选方案, 而且在很多场景下更清晰简洁。

在使用面向切面编程时, 我们仍然需要在一个地方定义这些通用的功能, 但是可以通过声明的方式定义这个功能要以何种方式在何处应用, 而无需修改受影响的类。 横切关注点可以被模块化为特殊的类, 这些类被称为**切面(aspect) **。

这样做有两个好处:

  1. 现在每个关注点都集中于一个地方, 而不是分散到多处代码中,便于修改。
  2. 服务模块更简洁, 因为它们只包含主要核心功能的代码, 而次要关注点的代码被转移到切面中了。

####AOP术语

  1. 通知(advice) 、
  2. 切点(pointcut
  3. 连接点(join point

#####通知(advice): 切面的工作被称为通知。通知定义了切面是什么以及何时使用,它应该应用在某个方法被调用之前? 之后? 之前和之后都调用? 还是只在方法抛出异常时调用?

#####Spring切面可以应用5种类型的通知:

  1. 前置通知(Before) : 在目标方法被调用之前调用通知功能;
  2. 后置通知(After) : 在目标方法完成之后调用通知, 此时不会关心方法的输出是什么;
  3. 返回通知(After-returning) : 在目标方法成功执行之后调用通知;
  4. 异常通知(After-throwing) : 在目标方法抛出异常后调用通知;
  5. 环绕通知(Around) : 通知包裹了被通知的方法, 在被通知的方法调用之前和调用之后 执行自定义的行为。

#####连接点(Join point): 程序可能有数以千计的时机应用通知。 这些时机被称为连接点。 连接点是在应用执行过程中能够插入切面的一个点。 这个点可以是调用方法时、 抛出异常时、 甚至修改一个字段时。 切面代码可以利用这些点插入到应用的正常流程之中, 并添加新的行为。

切点(pointcut) :

切点表示了通知在哪里执行,切点有助于缩小切面所通知的连接点的范围。

切点的定义会匹配通知所要织入的一个或多个连接点。 我们通常使用明确的类和方法名称, 或是利用正则表达式定义所匹配的类和方法名称来指定这些切点

如:

@Before("execution(**chapter03.annotation_aop.Performance.perform())")

#####切面(AspectJ): 切面是通知和切点的结合。 通知和切点共同定义了切面的全部内容——它是什么, 在何时和何处完成其功能。

<br> 如下定义了一个切面:

[@Aspect](https://my.oschina.net/aspect)
public class Audience {

/**
 * 方法执行之前执行
 */
@Before("execution(**chapter03.annotation_aop.Performance.perform())")
public void silenceCellPhones(){
    System.out.println("表演之前,手机调成静音");
}

/**
 * 方法执行后执行
 */
@AfterReturning("execution(**chapter03.annotation_aop.Performance.perform())")
public void applause(){
    System.out.println("表演很精彩,鼓掌");
}

/**
 * 方法抛出异常执行
 */
@AfterThrowing("execution(**chapter03.annotation_aop.Performance.perform())")
public void demandRefund(){
    System.out.println("表演失败,要求退款");
}
}

<br> ######Spring在运行时通知对象

通过在代理类中包裹切面, Spring在运行期把切面织入到Spring管理的bean中。 代理类封装了目标类, 并拦截被通知方法的调用, 再把调用转发给真正的目标bean。

当代理拦截到方法调用时, 在调用目标bean方法之前, 会执行切面逻辑。

因为Spring基于动态代理来实现AOP功能, 所以Spring只支持方法连接点,但是方法拦截可以满足绝大部分的需求。 如果需要方法拦截之外的连接点拦截功能, 那么我们可以利用Aspect来补充Spring AOP的功能

<br> ####编写切点 比如有如下一个接口,编写一个切面通知,在接口方法调用的时候触发:

package chapter03.annotation_aop;
public interface Performance {
    void perform();
}

那么我们的切点表达式可以这样写:

@Pointcut("execution(* chapter03.annotation_aop.Performance.perform(..))")
public void perform(){

}

其中 ***** 表示该接口下任意返回值的方法, .. 表示方法的任意参数

还可以使用指示器来更加详细的进行匹配,有以下几种指示器:

AspectJ指示器描述例子说明
arg()限制连接点匹配参数为指定类型,args属于动态切入点,这种切入点开销非常大,非特殊情况最好不要使用;@Pointcut("execution(* CompactDisc.playTrack(int)) && args(number)")public void trackPlay(int number){}匹配playTrack()方法且参数为int类型
@args()匹配当前执行的方法传入的参数持有指定注解的执行@args(Anno))方法参数标有Anno注解
this()匹配当前AOP代理对象类型的执行方法this(service.IPointcutService)当前AOP对象实现了 IPointcutService接口的任何方法
target()匹配当前目标对象类型的执行方法target(service.IPointcutService)当前目标对象(非AOP对象)实现了 IPointcutService接口的任何方法
@target()匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解@target (Secure)任何目标对象持有Secure注解的类方法;必须是在目标对象上声明这个注解,在接口上声明的对它不起作用
within()匹配指定类型内的方法执行within(cn.javass..*)cn.javass包及子包下的任何方法执行
@within()匹配所有持有指定注解类型内的方法@within(Secure)任何目标对象持有Secure注解的类方法;必须是在目标对象上声明这个注解,在接口上声明的对它不起作用
@annotation匹配当前执行方法持有指定注解的方法@annotation(Secure )当前执行方法上持有注解Secure将被匹配
bean()匹配特定名称的Bean对象的执行方法bean(*Service)匹配所有以Service命名(id或name)结尾的Bean

<br><br> ####Spring使用注解实现AOP:

首先定义一个接口,通知作用在该接口中的方法,当该接口下的方法被调用的时候就会触发通知。

package chapter03.annotation_aop;
public interface Performance {
    void perform();
}

然后写一个该接口的实现类:

package chapter03.annotation_aop;
@Component(value = "performance")
public class PerformanceImpl implements Performance {
    @Override
    public void perform() {
        System.out.println("舞蹈表演中......");
    }
}

使用@Component(value = "performance")把该类注入到Spring中,bean的名字为:performance

<br> 接下来编写**切面**:

@Aspect
public class Audience {
    /**
     * 方法执行之前执行
     */
    @Before("execution(* chapter03.annotation_aop.Performance.perform())")
    public void silenceCellPhones(){
        System.out.println("表演之前,手机调成静音");
    }

    /**
     * 方法执行之前执行
     */
    @Before("execution(* chapter03.annotation_aop.Performance.perform())")
    public void takeSeats(){
        System.out.println("表演之前,坐好");
    }

    /**
     * 方法执行后执行
     */
    @AfterReturning("execution(* chapter03.annotation_aop.Performance.perform())")
    public void applause(){
        System.out.println("表演很精彩,鼓掌");
    }

    /**
     * 方法抛出异常执行
     */
    @AfterThrowing("execution(* chapter03.annotation_aop.Performance.perform())")
    public void demandRefund(){
        System.out.println("表演失败,要求退款");
    }
}
  1. @Aspect表示该类是一个切面。
  2. @Before标注的通知方法在目标方法执行之前执行。
  3. @AfterReturning标注的通知方法在目标方法成功执行后执行。
  4. @AfterThrowing标注的通知方法在目标方法出现异常的时候执行

提示:在每个通知方法中,都要写一遍切点表达式:

execution(* chapter03.annotation_aop.Performance.perform())"

可以使用 @Pointcut 来编写一个可重用的切点表达式:

@Aspect
public class Audience2 {

    //定义可重用的切点表达式
    @Pointcut("execution(* chapter03.annotation_aop.Performance.perform(..))")
    public void perform(){

    }
    /**
     * 方法执行之前执行
     */
    @Before("perform()")
    public void silenceCellPhones(){
        System.out.println("表演之前,手机调成静音");
    }

    /**
     * 方法执行之前执行
     */
    @Before("perform()")
    public void takeSeats(){
        System.out.println("表演之前,坐好");
    }

    /**
     * 方法执行后执行
     */
    @AfterReturning("perform())")
    public void applause(){
        System.out.println("表演很精彩,鼓掌");
    }

    /**
     * 方法抛出异常执行
     */
    @AfterThrowing("perform()")
    public void demandRefund(){
        System.out.println("表演失败,要求退款");
    }
}

<br> 我们定义了切点之后,它只是一个POJO,并没有特别之处,我们还要把它们注入到Spring中,

下面编写一个配置文件:

@Configuration
@EnableAspectJAutoProxy//启动 AspectJ 自动代理
@ComponentScan
public class Config {

    // 注入 bean
    @Bean
    public Audience2 audience(){
        return new Audience2();
    }
}

<br> 1. `@Configuration`:标识这是一个配置文件。 2. `@EnableAspectJAutoProxy` : 启动 AspectJ 自动代理。 3. `@ComponentScan`:启动注解扫描。

测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Config.class)
public class TestAop {

    @Autowired
    private Performance performance;
    
    @Test
    public void testAdvice(){
        performance.perform();
    }
}
  1. @RunWith(SpringJUnit4ClassRunner.class):自动创建 Spring 上下文
  2. @ContextConfiguration(classes = Config.class): 加载配置文件

结果:

表演之前,手机调成静音 表演之前,坐好 舞蹈表演中...... 表演很精彩,鼓掌

####环绕通知: 环绕通知 @Around():

这个环绕通知所达到的效果与之前的前置通知和后置通知是一样的。 但是, 现在它们 位于同一个方法中, 不像之前那样分散在四个不同的通知方法里面。

使用 **ProceedingJoinPoint**当作方法的参数

@Aspect
public class AroundAdvice {

    //定义可重用切点
    @Pointcut("execution(* chapter03.annotation_aop.Performance.perform(..))")
    public void perform(){
    }

  
    @Around("perform()")
    public void around(ProceedingJoinPoint joinPoint){
        try {

            System.out.println("表演之前,手机调成静音");
            System.out.println("表演之前,坐好");
            joinPoint.proceed();
            System.out.println("表演很精彩,鼓掌");
        } catch (Throwable throwable) {
            System.out.println("表演失败,要求退款");
            throwable.printStackTrace();
        }
    }
}

####带参数的情况:

接口:

package chapter03.annotation_aop.aop_args;
public interface CompactDisc {
    void playTrack();
    void playTrack(int i);
}

接口的实现:

@Component(value = "compactDisc")
public class CompactDiscImpl implements CompactDisc {
    @Override
    public void playTrack() {
        System.out.println("playTrack with no args........");
    }

    @Override
    public void playTrack(int i) {
        System.out.println("playTrack with args : " + i);
    }
}

编写切面:

@Aspect
public class AvdiceWithArgs {

    @Pointcut("execution(* chapter03.annotation_aop.aop_args.CompactDisc.playTrack(int)) && args(number)")
    public void trackPlay(int number){

    }

    @Before("trackPlay(num)")
    public void trackPlayBefore(int num){
        System.out.println("表演之前......" + num);
    }

    @After("trackPlay(num)")
    public void trackPlayAfter(int num){
        System.out.println("表演之后......" + num);
    }
}

args(number):: 它表明传递给 playTrack()方法的int类型参数也会传递到通知中去

@Configuration
@EnableAspectJAutoProxy//启动 AspectJ 自动代理
@ComponentScan
public class Config {

    @Bean
    public AvdiceWithArgs avdiceWithArgs(){
        return new AvdiceWithArgs();
    }
}

测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Config.class)
public class TestAop {

    @Autowired
    private CompactDisc compactDisc;

    @Test
    public void testAdviceWithArgs(){
        compactDisc.playTrack(100);
    }
}

结果:

表演之前......100 playTrack with args : 100 表演之后......100

<BR><BR> ####Spring使用XML实现AOP

定义切面:只是一个POJO。

public class Audience {

    public void silenceCellPhones(){
        System.out.println("表演之前,手机调成静音");
    }

    public void takeSeats(){
        System.out.println("表演之前,坐好");
    }

    public void applause(){
        System.out.println("表演很精彩,鼓掌");
    }

    public void demandRefund(){
        System.out.println("表演失败,要求退款");
    }
}

定义环绕通知:

public class AroundAdvice {

    public void aroundPerform(ProceedingJoinPoint joinPoint){
        try {
            System.out.println("表演之前,手机调成静音");
            System.out.println("表演之前,坐好");
            joinPoint.proceed();
            System.out.println("表演很精彩,鼓掌");
        } catch (Throwable throwable) {
            System.out.println("表演失败,要求退款");
            throwable.printStackTrace();
        }
    }
}

在 XML 文件中配置通知:

大多数的AOP配置元素必须在**<aop:config>元素的上下文内使用。这条规则有几种例外场景, 但是把bean声明为一个切面时,我们总是从<aop:config>**元素开始配置的。

在**<aop:config>**元素内, 我们可以声明一个或多个通知器、 切面或者切点。

使用**<aop:aspect>*元素声明了一个简单的切面, ref元素引用了一个POJO bean, 该bean实现了切面的功能

使用 <aop:pointcut> 定义了可重用的切点表达式, 供后面的通知使用而不用重新定义一遍

**<aop:pointcut>**元素所定义的切点可以被同一个<aop:aspect>元素之内的所 有通知元素引用。 如果想让定义的切点能够在多个切面使用,我们可以把<aop:pointcut>元素放在<aop:config>元素的范围内

启动 AspectJ 自动代理

<aop:aspectj-autoproxy/>

配置切面的 bean

<bean id="audience" class="chapter03.annotation_xml.Audience"/>

配置环绕通知的 bean

 <aop:config>
    <!-- 引用配置的切面 bean-->
    <aop:aspect ref="audience">
        <!-- 定义可重用的切点表达式-->
        <aop:pointcut id="pc" expression="execution(* chapter03.annotation_xml.Performance.perform(..))"/>
        <!-- 前置通知-->
        <aop:before pointcut-ref="pc" method="silenceCellPhones"/>
        <aop:before pointcut-ref="pc" method="takeSeats"/>
        <!-- 后置通知 -->
        <aop:after-returning pointcut-ref="pc" method="applause"/>
        <!-- 异常通知-->
        <aop:after-throwing pointcut-ref="pc" method="demandRefund"/>
    </aop:aspect>

    <!-- 配置环绕通知 -->
    <aop:aspect ref="aroundAdvice">
        <aop:pointcut id="apc" expression="execution(* chapter03.annotation_xml.Performance.perform(..))"/>
        <aop:around pointcut-ref="apc" method="aroundPerform"/>
    </aop:aspect>

    <!--配置带参数的通知-->
    <aop:aspect ref="adviceWithArgs">
        <aop:pointcut id="bpc" expression="execution(* chapter03.annotation_xml.aop_args.CompactDisc.playTrack(int)) and args(num)"/>
        <aop:before pointcut-ref="bpc" method="trackPlayBefore"/>
        <aop:after pointcut-ref="bpc" method="trackPlayAfter"/>
    </aop:aspect>

</aop:config>

测试:

首先加载配置文件:

@Configuration
@ImportResource("classpath:applicationContext.xml")
public class Config {
}

编写测试类:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Config.class)
public class TestAop {

    @Autowired
    private Performance performance;

    @Test
    public void test(){
        performance.perform();
    }
}

结果:

表演之前,手机调成静音 表演之前,坐好 表演之前,手机调成静音 表演之前,坐好 舞蹈表演中...... 表演很精彩,鼓掌 表演很精彩,鼓掌

因为配置了两个通知,所以出现上述效果。

结束!!!!

引用:

详细参考:

http://sishuok.com/forum/posts/list/281.html

© 著作权归作者所有

上一篇: Java 反射
TSMYK
粉丝 96
博文 81
码字总数 197813
作品 0
成都
程序员
私信 提问
Spring之 Aspect Oriented Programming with Spring

1. Concepts Aspect-Oriented Programming (AOP) complements OOP by providing another way of thinking about program structure. While OO decomposes applications into a hierarchy of ......

leodaxin
2018/07/29
0
0
主键不是id的实体使用insertUseGeneratedKeys方法问题

@Liuzh_533 你好,想跟你请教个问题: 看了你在 org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.builder.BuilderException: Error invoking SqlProvide......

rexxx
2017/05/27
158
1
Spring中的AOP(二)——AOP基本概念和Spring对AOP的支持

AOP的基本概念 AOP从运行的角度考虑程序的流程,提取业务处理过程的切面。AOP面向的是程序运行中的各个步骤,希望以更好的方式来组合业务逻辑的各个步骤。AOP框架并不与特定的代码耦合,AOP...

摆渡者
2014/03/17
0
3
Spring 2.0 的AOP介绍及其通知类型

Spring 2.0的AOP 在Spring 2.0中最激动人心的增强之一是关于Spring AOP,它变得更加便于使用而且更加强大,主要是通过复杂而成熟的AspectJ语言的支持功能来实现,而同时保留纯的基于代理的J...

小星星程序员
2014/08/15
0
0
最最简单的spring及AOP实例

一、简单的spring实现(annotation方式) bean类 测试类: 运行结果: (xml方式) bean类 xml配置文件applicationContext.xml(放在包com.hello下) 测试类: 二、注解方式实现aop(需要导入...

wangxuwei
2017/10/24
0
0

没有更多内容

加载失败,请刷新页面

加载更多

基础工具类

package com.atguigu.util;import java.sql.Connection;import java.sql.SQLException;import java.util.Properties;import javax.sql.DataSource;import com.alibaba.druid......

architect刘源源
今天
43
0
P30 Pro劲敌!DxO官宣新机:排行榜又要变

5月26日晚间,DxOMark官方推特预告,将在5月27日公布一款新机型的DxOMark评分,猜猜是哪款? 网友猜想的机型有:红米K20、谷歌Pixel 3a、索尼Xperia 1、诺基亚9 PureView等。 DxOMark即将公布...

linux-tao
昨天
15
0
Ubuntu18.04.2窗口过小不能自适应(二次转载)

解决Ubuntu在虚拟机窗口不能自适应 2018年09月06日 16:20:08 起不了名儿 阅读数 855 此博文转载:https://blog.csdn.net/nuddlle/article/details/77994080(原地址) 试了很多办法这个好用 ...

tahiti_aa
昨天
2
0
死磕 java同步系列之CountDownLatch源码解析

问题 (1)CountDownLatch是什么? (2)CountDownLatch具有哪些特性? (3)CountDownLatch通常运用在什么场景中? (4)CountDownLatch的初始次数是否可以调整? 简介 CountDownLatch,可以...

彤哥读源码
昨天
6
0
Nginx提供下载apk服务

有时候我们可能需要提供文件或者其他apk下载链接,通过 nginx 配置可以很简单地实现。 server {    listen 80;    server_name download.xxx.com;    root app;    locati...

Jack088
昨天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部