2019-08-25_Spring AOP切面编程学习笔记

Spring AOP切面编程学习笔记

1 概述

AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。

日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

1.1AOP好处

使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。

业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。

横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。

AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

2 关于AOP

2.1关于AOP核心概念

1、切面(aspect)

类是对物体特征的抽象,切面就是对横切关注点的抽象

2、切入点(pointcut)

对连接点进行拦截的定义

3、通知(advice)

所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为Before前置、After后置、after-throwing异常、最终、Around环绕通知五类。

<!--3.AOP代理配置-->
<!--proxy-target-class:true:那么基于类的代理,CGLIB-->
<!--proxy-target-class:false,或这个属性被省略,那么基于JDK接口的代理-->
<!--<aop:config proxy-target-class="false">-->
<aop:config>

    <!--定义切面1 -->
    <!--通过 order控制横切关注点的顺序,越大,优先级越高,越先执行-->
    <aop:aspect id="myAspect1" ref="myTimerDisplayHandler" order="2">

        <!--定义切入点-->
        <aop:pointcut id="showCurrentTime"
                      expression="execution(* com.kikop.myspringcglib2.service.IHelloWorld.simple*(..))"></aop:pointcut>

        <!--定义切入点:执行前通知-->
        <aop:before method="printTime" pointcut-ref="showCurrentTime"></aop:before>
        <!--定义切入点:执行后通知-->
        <aop:after method="printTime" pointcut-ref="showCurrentTime"></aop:after>


    </aop:aspect>

4、横切关注点

对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点

5、连接点(joinpoint)

被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器

6、目标对象

代理的目标对象

7、织入(weave)

将切面应用到目标对象并导致代理对象创建的过程

8、引入(introduction)

在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段。

2.2AOP pointcut切点函数

// execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)(<异常模式>))

/**

   \* 匹配 com.aop.learn.service 包下所有类的所有方法(service+一个点)

   */

  @Before("execution(* com.aop.learn.service.*(..))")

  public void beforeAspect1(){

 

  }

  /**

   \* 匹配 com.aop.learn.service 包、子孙包下所有类的所有方法(两service+个点)

   */

  @Before("execution(* com.aop.learn.service..*(..))")

  public void beforeAspect2(){

  }

2.2.1切点定义示例

package com.kikop.myspringcglib2;


import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;


/**
 * @author kikop
 * @version 1.0
 * @project Name: TechnicalAbilityToolBox
 * @file Name: MyTimerDisplayHandler
 * @desc 切点定义
 * @date 2019/8/25
 * @time 14:29
 * @by IDE: IntelliJ IDEA
 */
//等价于:xml中的<aop:config>
@Aspect
@Component
public class AspectPointCutDef {

    /**
     * 匹配所有目标类的public方法
     */
    @Before("execution(public * *(..))")
    public void beforeAspect() {

    }

    /**
     * 匹配所有以To为后缀的方法
     */
    @Before("execution(* *To(..))")
    public void beforeAspect1() {

    }

    /**
     * 匹配Waiter接口中的所有方法
     */
    @Before("execution(* com.kikop.myspringcglib2.service.Writer.*(..))")
    public void beforeAspect2() {

    }

    /**
     * 匹配Waiter接口中及其实现类的方法
     */
    @Before("execution(* com.kikop.myspringcglib2.service.Writer+.*(..))")
    public void beforeAspect3() {

    }

    /**
     * 匹配 com.kikop.myspringcglib2.service 包下所有类的所有方法
     */
    @Before("execution(* com.kikop.myspringcglib2.service.*(..))")
    public void beforeAspect4() {

    }

    /**
     * 匹配 com.kikop.myspringcglib2.service 包,子孙包下所有类的所有方法
     */
    @Before("execution(* com.kikop.myspringcglib2.service..*(..))")
    public void beforeAspect5() {

    }

    /**
     * 匹配 包名前缀为com的任何包下类名后缀为ive的方法,方法必须以Smart为前缀
     */
    @Before("execution(* com..*.*ive.Smart*(..))")
    public void beforeAspect6() {

    }

    /**
     * 匹配 save(String name,int age) 函数
     */
    @Before("execution(* save(String,int))")
    public void beforeAspect7() {

    }

    /**
     * 匹配 save(String name,*) 函数 第二个参数为任意类型
     */
    @Before("execution(* save(String,*))")
    public void beforeAspect8() {

    }

    /**
     * 匹配 save(String name,..) 函数 除第一个参数固定外,接受后面有任意个入参且入参类型不限
     */
    @Before("execution(* save(String,..))")
    public void beforeAspect9() {

    }

    /**
     * 匹配 save(String+) 函数  String+ 表示入参类型是String的子类
     */
    @Before("execution(* save(String+))")
    public void beforeAspect10() {

    }
}

3AOP最佳实践

3.1Spring对aop支持

3.1.1Spring创建代理的规则

Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:

1、默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了

2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB。

3.1.2AOP编程步骤三个部分

1、定义普通业务组件XXXHandler

2、定义切入点PointCut,一个切入点可能横切多个业务组件

3、定义切点需要的增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作

所以进行AOP编程的关键就是定义切入点和定义切点的增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。

4代码实战

4.1maven依赖

spring-aop-5.0.0.RELEASE.jar、spring-aspects-5.0.0.RELEASE.jar(aspectjweaver-1.8.9.jar)

aopalliance-1.0.jar(好像用不到)

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.kikop</groupId>
    <artifactId>myspringdemo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <name>myspringdemo</name>
    <!-- FIXME change it to the project's website -->
    <url>http://www.example.com</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>

        <!--springframework-->
        <springframework.version>5.0.0.RELEASE</springframework.version>

    </properties>

    <dependencies>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>

        <!--2.spring对web的支持,依赖 spring-webmvc-->
        <!--此时jar包会自动下载(spring-context、spring-web、spring-webmvc)-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${springframework.version}</version>
        </dependency>

        <!--2.2.spring dao层依赖(jdbc 和 tx)-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${springframework.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${springframework.version}</version>
        </dependency>

        <!--2.4.spring aop依赖(aop 和 aspect)-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${springframework.version}</version>
        </dependency>

        <!--spring-aspects内部依赖:aspectjweaver,另外需手动添加:aopalliance-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${springframework.version}</version>
        </dependency>

        <!--2.3.spring test相关依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${springframework.version}</version>
        </dependency>

    </dependencies>

    <build>
        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
            <plugins>
                <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
                <plugin>
                    <artifactId>maven-clean-plugin</artifactId>
                    <version>3.1.0</version>
                </plugin>
                <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.0</version>
                </plugin>
                <plugin>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>2.22.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-jar-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-install-plugin</artifactId>
                    <version>2.5.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-deploy-plugin</artifactId>
                    <version>2.8.2</version>
                </plugin>
                <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
                <plugin>
                    <artifactId>maven-site-plugin</artifactId>
                    <version>3.7.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-project-info-reports-plugin</artifactId>
                    <version>3.0.0</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

4.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--1.定义业务逻辑类-->
    <bean id="helloWorldImpl1" class="com.kikop.myspringcglib2.service.impl.HelloWorldImpl1"></bean>
    <bean id="helloWorldImpl2" class="com.kikop.myspringcglib2.service.impl.HelloWorldImpl2"></bean>

    <!--2.定义动态代理类-->
    <bean id="myTimerDisplayHandler" class="com.kikop.myspringcglib2.handler.MyTimerDisplayHandler"></bean>

    <bean id="myLoggerHandler" class="com.kikop.myspringcglib2.handler.MyLoggerHandler"></bean>

    <!--3.AOP代理配置-->
    <!--proxy-target-class:true:那么基于类的代理,CGLIB-->
    <!--<aop:config proxy-target-class="true">-->
    <!--proxy-target-class:false,或这个属性被省略,那么基于JDK接口的代理-->
    <aop:config>

        <!--定义切面1 -->
        <!--通过 order控制横切关注点的顺序,越大,优先级越高,越先执行-->
        <aop:aspect id="myAspect1" ref="myTimerDisplayHandler" order="2">

            <!--定义方法XXX切入点-->
            <aop:pointcut id="showCurrentTime"
                          expression="execution(* com.kikop.myspringcglib2.service.IHelloWorld.prefixDo*(..))"></aop:pointcut>

            <!--定义切入点:执行前通知-->
            <aop:before method="printTimeBefore" pointcut-ref="showCurrentTime"></aop:before>
            <!--定义切入点:执行后通知-->
            <aop:after method="printTimeEnd" pointcut-ref="showCurrentTime"></aop:after>


        </aop:aspect>

        <!--定义切面2 -->
        <aop:aspect id="myAspect2" ref="myLoggerHandler" order="1">
            <!--定义方法YYY切入点-->
            <aop:pointcut id="showCurrentLog"
                          expression="execution(* com.kikop.myspringcglib2.service.IHelloWorld.do*(..))"></aop:pointcut>

            <!--定义切入点:执行前通知-->
            <aop:before method="loggerBefore" pointcut-ref="showCurrentLog"></aop:before>
            <!--定义切入点:执行后通知-->
            <aop:after method="loggerEnd" pointcut-ref="showCurrentLog"></aop:after>

        </aop:aspect>

    </aop:config>
</beans>

4.3业务接口

package com.kikop.myspringcglib2.service;

/**
 * @author kikop
 * @version 1.0
 * @project Name: TechnicalAbilityToolBox
 * @file Name: IHelloWorld
 * @desc 功能描述
 * @date 2019/8/25
 * @time 14:26
 * @by IDE: IntelliJ IDEA
 */
public interface IHelloWorld {
    void prefixDoWelcome() throws Exception;

    void doBusinessPrint();
}

4.4业务接口实现类

package com.kikop.myspringcglib2.service.impl;

import com.kikop.myspringcglib2.service.IHelloWorld;

import java.util.concurrent.TimeUnit;

/**
 * @author kikop
 * @version 1.0
 * @project Name: TechnicalAbilityToolBox
 * @file Name: HelloWorldImpl1
 * @desc 功能描述
 * @date 2019/8/25
 * @time 14:27
 * @by IDE: IntelliJ IDEA
 */
public class HelloWorldImpl1 implements IHelloWorld {

    @Override
    public void prefixDoWelcome() throws Exception {
        System.out.println(this.getClass().toString() + ":prefixDoWelcome");
        TimeUnit.SECONDS.sleep(5);
    }

    @Override
    public void doBusinessPrint() {
        System.out.println(this.getClass().toString() + ":doBusinessPrint");
    }
}

4.5增强逻辑Handler

package com.kikop.myspringcglib2.handler;

import java.util.Date;

/**
 * @author kikop
 * @version 1.0
 * @project Name: TechnicalAbilityToolBox
 * @file Name: MyTimerDisplayHandler
 * @desc 横切关注点:时间
 * @date 2019/8/25
 * @time 14:29
 * @by IDE: IntelliJ IDEA
 */
public class MyTimerDisplayHandler {
    public void printTimeBefore() {
        System.out.println("业务系统执行开始时间:" + new Date());
    }

    public void printTimeEnd() {
        System.out.println("业务系统执行结束时间:" + new Date());
    }
}
package com.kikop.myspringcglib2.handler;

/**
 * @author kikop
 * @version 1.0
 * @project Name: TechnicalAbilityToolBox
 * @file Name: MyTimerDisplayHandler
 * @desc 横切关注点2:日志
 * @date 2019/8/25
 * @time 14:29
 * @by IDE: IntelliJ IDEA
 */
public class MyLoggerHandler {

    public void loggerBefore() {
        System.out.println("业务方法执行,日志记录开始...");
    }

    public void loggerEnd() {
        System.out.println("业务方法执行,日志记录完成!");
    }
}

4.6代码测试

package com.kikop.myspringcglib2;

import com.kikop.myspringcglib2.service.IHelloWorld;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author kikop
 * @version 1.0
 * @project Name: TechnicalAbilityToolBox
 * @file Name: MySpringCGLIBTest
 * @desc 功能描述
 * @date 2019/8/25
 * @time 14:40
 * @by IDE: IntelliJ IDEA
 */
public class MySpringCGLIBTest {

    public static void springAOPTest() throws Exception {

        ApplicationContext ctx =
                new ClassPathXmlApplicationContext("springframework/spring-aop.xml");

        IHelloWorld helloWorld = (IHelloWorld) ctx.getBean("helloWorldImpl1");

        //显示时间
        helloWorld.prefixDoWelcome();

        //显示Log
        helloWorld.doBusinessPrint();
    }

    public static void main(String[] args) throws Exception {
        springAOPTest();
    }
}

4.7结果输出

业务系统执行开始时间:Sun Feb 28 12:38:07 CST 2021
class com.kikop.myspringcglib2.service.impl.HelloWorldImpl1:prefixDoWelcome
业务系统执行结束时间:Sun Feb 28 12:38:12 CST 2021

业务方法执行,日志记录开始...
class com.kikop.myspringcglib2.service.impl.HelloWorldImpl1:doBusinessPrint
业务方法执行,日志记录完成!

参考

1.Spring aop execution() 常见的切点函数

https://www.cnblogs.com/songshuiyang/p/7857515.html

2.[Spring AOP详解

https://www.cnblogs.com/hongwz/p/5764917.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,284评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,115评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,614评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,671评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,699评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,562评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,309评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,223评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,668评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,859评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,981评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,705评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,310评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,904评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,023评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,146评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,933评论 2 355

推荐阅读更多精彩内容