Spring-AOP

Spring-AOP

1.AOP简介

1.1 AOP概念

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP:一种编程范式,隶属于软工范畴,指导开发者如何组织程序结构

AOP弥补了OOP的不足,基于OOP基础之上进行横向开发

  • OOP规定程序开发以类为主体模型,一切围绕对象进行,完成某个任务先构建模型
  • AOP程序开发主要关注基于OOP开发中的共性功能,一切围绕共性功能进行,完成某个任务先构建可能遇到的所有共性功能(当所有功能都开发出来也就是没有共性和非共性之分)

“AOP联盟”

1.2 AOP作用

伴随着AOP时代的降临,可以从各个行业的标准化、规范化开始入手,一步一步将所有共性功能逐一开发完毕,最终功能组合来完成个别业务模块乃至整体业务系统的开发

目标:将软件开发由手工制作走向半自动化/全自动化阶段,实现“插拔式组件体系结构”搭建

1.3 AOP优势

  • 提高代码的可重用性
  • 业务代码编码更简洁
  • 业务代码维护更高效
  • 业务功能扩展更便捷

2.AOP入门案例

2.1 相关概念

Joinpoint(连接点):就是方法

Pointcut(切入点):就是挖掉共性功能的方法

Advice(通知):就是共性功能,最终以一个方法形式呈现

Aspect(切面):就是共性功能与挖的位置的对应关系

Target(目标对象):就是挖掉功能的方法对应的类产生的对象,这种对象是无法直接完成最终工作的

Weaving(织入):就是挖掉功能回填的动态过程

Proxy(代理):目标对象无法直接完成工作,需要对其进行功能回填,通过创建原始对象的代理对象实现

Introduction(引入/引介):就是对原始对象无中生有的添加成员变量或成员方法

2.2 开发过程

开发阶段(开发者完成)

  • 正常的制作程序
  • 将非共性功能开发到对应的目标对象类中,并制作成切入点方法
  • 将共性功能独立开发出来,制作成通知
  • 在配置文件中,声明切入点
  • 在配置文件中,声明切入点与通知的关系(同通知类型),即切面

运行阶段(AOP完成)

  • Spring容器加载配置文件,监控所有配置的切入点方法的执行
  • 当监控到切入点方法被运行后,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置将通知对应的功能织入,完成完整的代码逻辑并运行

2.3 开发方式

  • XML方式
  • XML+注解方式
  • 注解方式

1.XML方式

maven配置导入坐标:

<dependencies>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.9.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
  </dependency>
</dependencies>

service实现类:

@Component("userService")
public class UserServiceImpl implements UserService {
    public void say() {
        //0.抽取共性功能
//        System.out.println("共性功能");
        System.out.println("user service running...");
    }
}

抽取共性功能:

//1.制作通知类,在类中定义一个方法用于完成共性功能
public class AOPAdvice {
    public void function(){
        System.out.println("共性功能");
    }
}

spring配置:

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

    <!--3.开启AOP命名空间-->
    <bean id="userService" class="com.itheima.service.impl.UserServiceImpl"/>
    <!--2.配置共性功能成为spring控制的资源-->
    <bean id="myAdvice" class="com.itheima.aop.AOPAdvice"/>

    <!--4.配置AOP-->
    <aop:config>
        <!--5.配置切入点-->
        <aop:pointcut id="pt" expression="execution(* *..*(..))"/>
        <!--6.配置切面:切入点与通知的关系-->
        <aop:aspect ref="myAdvice">
            <!--7.配置具体的切入点对应通知中的哪个操作方法-->
            <aop:before method="function" pointcut-ref="pt"/>
        </aop:aspect>
    </aop:config>
</beans>

测试类:

public class App {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) context.getBean("userService");
        userService.say();
    }
}

3.AOP配置(xml)(重点)

3.1 AOP基本配置

AspectJ

Aspect(切面)用于描述切入点与通知间的关系,是AOP编程中的一个概念

AspectJ是基于Java语言对Aspect的实现

AOP配置

名称:aop:aspect

类型:标签

归属:aop:config标签

作用:设置具体的AOP通知对应的切入点

格式:

  • <!--4.配置AOP-->
    <aop:config>
        <!--5.配置切入点-->
        <aop:pointcut id="pt" expression="execution(* *..*(..))"/>
        <!--6.配置切面:切入点与通知的关系-->
        <aop:aspect ref="myAdvice">
            <!--7.配置具体的切入点对应通知中的哪个操作方法-->
            <aop:before method="function" pointcut-ref="pt"/>
        </aop:aspect>
    </aop:config>
    

说明:

  • 一个aop:config标签中可以配置多个aop:aspect标签

基本属性:

  • ref:通知所在的bean的id

名称:aop:pointcut

类型:标签

归属:aop:config标签、aop:aspect标签

作用:设置切入点

格式:

  • <aop:config>
        <aop:pointcut id="pt" expression="execution(* *..*(..))"/>
        <aop:aspect ref="myAdvice">
            <aop:pointcut id="pt" expression="execution(* *..*(..))"/>
            <aop:before method="function" pointcut-ref="pt"/>
        </aop:aspect>
    </aop:config>
    

说明:

  • 一个aop:config标签中可以配置多个aop:pointcut标签,且该标签可以配置在aop:aspect标签内

基本属性:

  • id:识别切入点的名称
  • expression:切入点表达式

3.2 切入点

切入点描述的是某个方法

切入点表达式是一个快速匹配方法描述的通配格式,类似于正则表达式

格式:

关键字(访问修饰符 返回值 包名.类名.方法名(参数)异常名)

  • 关键字:描述表达式的匹配模式(参看关键字列表)
  • 访问修饰符:方法的访问控制权限修饰符
  • 类名:方法所在的类(此处可以配置接口名称)
  • 异常:方法定义中指定抛出的异常

范例:

  • execution(public User com.itheima.service.UserService.findById(int))

关键字:

execution:匹配执行指定方法

args:匹配带有指定参数类型的方法

通配符:

  • *:单个独立的任意符号,可以独立出现,也可以作为前缀或后缀的匹配符出

    execution(public * com.itheima.*.UserService.find* (*))
    

    匹配com.itheima包下的任意包中UserService类或接口中所有find开头的带有一个参数的方法

  • …:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写

    execution(public User com..UserService.findById (..))
    

    匹配com包下的任意包中的UserService类中或接口中所有名称为findById的方法

  • +:专用于匹配子类类型

    execution(* *..*Service+.* (..))
    

逻辑运算符:

&&:连接两个切入点表达式,表示两个切入点表达式同时成立的匹配

||:连接两个切入点表达式,表示两个切入点表达式成立任意一个的匹配

!:连接单个切入点表达式,表示该切入点表达式不成立的匹配

三种配置方法:

<aop:config>
    <!--配置公共切入点-->
	<aop:pointcut id="pt1" expression="execution(* *(..))"/>
    <aop:aspect ref="myAdvice">
        <!--配置局部变量-->
        <aop:pointcut id="pt2" expression="execution(* *..*(..))"/>
        <!--引用公共切入点-->
        <aop:before method="function" pointcut-ref="pt1"/>
        <!--引用局部切入点-->
        <aop:before method="function" pointcut-ref="pt2"/>
        <!--直接配置切入点-->
        <aop:before method="function" pointcut="execution(* *(..))"/>
    </aop:aspect>    
</aop:config>

切入点配置经验:

  • 企业开发命名规范严格遵循规范文档进行

3.3 通知类型

AOP的通知类型共5中:

  • 前置通知:原始方法执行前执行,如果通知中抛出异常,阻止原始方法运行

    应用:数据校验

  • 后置通知:原始方法执行后执行,无论原始方法中是否抛出异常,都将执行通知

    应用:现场清理

  • 返回后通知:原始方法正常执行完毕并返回结果后执行,如果原始方法中抛出异常,无法执行

    应用:返回值相关数据处理

  • 抛出异常后通知:原始方法抛出异常后执行,如果原始方法没有抛出异常,无法执行

    应用:对原始方法中出现的异常信息进行处理

  • 环绕通知:在原始方法执行前后均有对应执行,还可以阻止原始方法的执行

    应用:十分强大,可以做任何事情

public class AOPAdvice {
    public void function(){
        System.out.println("共性功能");
    }

    public void before(){
        System.out.println("before");
    }

    public void after(){
        System.out.println("after");
    }

    public void afterReturing(){
        System.out.println("afterReturing");
    }

    public void afterThrowing(){
        System.out.println("afterThrowing");
    }

    public void around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before");

        //对原始方法的调用
        pjp.proceed();
        System.out.println("around after");
    }
}

xml配置:

    <aop:config>
        <aop:pointcut id="pt1" expression="execution(* *..*(..))"/>
        <aop:aspect ref="myAdvice">
<!--            <aop:before method="before" pointcut-ref="pt1"/>-->
<!--            <aop:after method="after" pointcut-ref="pt1"/>-->
<!--            <aop:after-returning method="afterReturing" pointcut-ref="pt1"/>-->
<!--            <aop:after-throwing method="afterThrowing" pointcut-ref="pt1"/>-->
            <aop:around method="around" pointcut-ref="pt1"/>

        </aop:aspect>
    </aop:config>

通知顺序以配置顺序为准

3.4 通知获取参数

设定切入点表达式为通知方法传递参数(改变通知变量名的定义顺序)

原始方法:

@Component("userService")
public class UserServiceImpl implements UserService {
    public void say(int a,int b) {
        //0.抽取共性功能
//        System.out.println("共性功能");
        System.out.println("user service running..."+a+","+b);
//        int num = 1/0;
    }
}

AOP配置:

    <aop:config>
        <aop:pointcut id="pt1" expression="execution(* *..*(..))"/>
        <aop:aspect ref="myAdvice">
<!--            <aop:before method="before" pointcut-ref="pt1"/>-->
<!--            <aop:after method="after" pointcut-ref="pt1"/>-->
<!--            <aop:after-returning method="afterReturing" pointcut-ref="pt1"/>-->
<!--            <aop:after-throwing method="afterThrowing" pointcut-ref="pt1"/>-->
<!--            <aop:around method="around" pointcut-ref="pt1"/>-->

            <aop:pointcut id="pt2" expression="execution(* *..*(int,int)) &amp;&amp; args(x,y)"/>
            <aop:before method="before1" pointcut-ref="pt2" arg-names="y,x" />
        </aop:aspect>
    </aop:config>

通知类:

package com.itheima.aop;

import org.aspectj.lang.ProceedingJoinPoint;

//1.制作通知类,在类中定义一个方法用于完成共性功能
public class AOPAdvice {
    public void function(){
        System.out.println("共性功能");
    }

    public void before(){
        System.out.println("before");
    }

    public void before1(int x,int y){
        System.out.println("before..."+x+","+y);
    }

    public void after(){
        System.out.println("after");
    }

    public void afterReturing(){
        System.out.println("afterReturing");
    }

    public void afterThrowing(){
        System.out.println("afterThrowing");
    }

    public void around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before");

        //对原始方法的调用
        pjp.proceed();
        System.out.println("around after");
    }
}

测试类:

public class App {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) context.getBean("userService");
//        userService.say();
        userService.say(666,888);
    }
}

运行结果:

共性功能
before…888,666
user service running…666,888

3.5 通知获取返回值

设定返回值变量名

原始方法:

public int update() {
    System.out.println("user service update running...");
    return 100;
}

AOP配置:

    <aop:config>
        <aop:pointcut id="pt1" expression="execution(* *..*(..))"/>
        <aop:aspect ref="myAdvice">
<!--            <aop:before method="before" pointcut-ref="pt1"/>-->
<!--            <aop:after method="after" pointcut-ref="pt1"/>-->
<!--            <aop:after-returning method="afterReturing" pointcut-ref="pt1"/>-->
<!--            <aop:after-throwing method="afterThrowing" pointcut-ref="pt1"/>-->
<!--            <aop:around method="around" pointcut-ref="pt1"/>-->

<!--            <aop:pointcut id="pt2" expression="execution(* *..*(int,int)) &amp;&amp; args(x,y)"/>-->
<!--            <aop:before method="before1" pointcut-ref="pt2" arg-names="y,x" />-->
            <aop:after-returning method="afterReturing" pointcut-ref="pt1" returning="ret"/>
            <aop:around method="around" pointcut-ref="pt1"/>
        </aop:aspect>
    </aop:config>

通知类:(after-returning和around两种方式获取返回值)

public void afterReturing(Object ret){
    System.out.println("afterReturing..."+ret);
}

public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("around before");

    //对原始方法的调用
    Object ret = pjp.proceed();

    System.out.println("around after..."+ret);
    return ret;
}

测试类:

public class App {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) context.getBean("userService");
//        userService.say();
//        userService.say(666,888);
        int ret = userService.update();
        System.out.println("app...."+ret);      
    }
}

测试结果:

around before
user service update running…
around after…100
afterReturing…100
app…100

3.6 通知获取异常

在通知类的方法中调用原始方法捕获异常

原始方法:

public void delete() {
    int a = 1/0;
}

AOP配置:

    <aop:config>
        <aop:pointcut id="pt1" expression="execution(* *..*(..))"/>
        <aop:aspect ref="myAdvice">
<!--            <aop:before method="before" pointcut-ref="pt1"/>-->
<!--            <aop:after method="after" pointcut-ref="pt1"/>-->
<!--            <aop:after-returning method="afterReturing" pointcut-ref="pt1"/>-->
<!--            <aop:after-throwing method="afterThrowing" pointcut-ref="pt1"/>-->
<!--            <aop:around method="around" pointcut-ref="pt1"/>-->

<!--            <aop:pointcut id="pt2" expression="execution(* *..*(int,int)) &amp;&amp; args(x,y)"/>-->
<!--            <aop:before method="before1" pointcut-ref="pt2" arg-names="y,x" />-->
<!--            <aop:after-returning method="afterReturing" pointcut-ref="pt1" returning="ret"/>-->
<!--            <aop:around method="around" pointcut-ref="pt1"/>-->

            <aop:around method="around" pointcut-ref="pt1"/>
            <aop:after-throwing method="afterThrowing" pointcut-ref="pt1" throwing="t"/>
        </aop:aspect>
    </aop:config>

通知类:(after-throwing和around两种方式获取异常对象)

public void afterThrowing(Throwable t){
    System.out.println("afterThrowing..."+t.getMessage());
}

public Object around(ProceedingJoinPoint pjp) {
    System.out.println("around before");

    //对原始方法的调用
    Object ret = null;
    try {
        ret = pjp.proceed();
    } catch (Throwable t) {
        System.out.println("around exception...."+t.getMessage());
    }

    System.out.println("around after..."+ret);
    return ret;
}

测试类:

public class App {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) context.getBean("userService");
//        userService.say();
//        userService.say(666,888);
//        int ret = userService.update();
//        System.out.println("app...."+ret);
        userService.delete();
    }
}

测试结果:

around before
around exception…/ by zero
around after…null

afterThrowing…/ by zero

4.AOP配置(注解)(重点)

4.1 注解配置

xml配置:

<!--开启AOP注解驱动支持-->
<aop:aspectj-autoproxy/>
<!--原始功能要配置成spring控制的资源-->
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl"/>
<!--抽取的共性功能要配置成为spring控制的资源-->
<bean id="myAdvice" class="com.itheima.aop.AOPAdvice"/>
<!--配置AOP-->
<aop:config>
    <!--配置切面-->
    <aop:aspect ref="myAdvice">
        <!--配置切入点-->
        <aop:pointcut id="pt" expression="execution(* *..*(..))"/>
        <!--通知与切入点之间的关系-->
        <aop:before method="function" pointcut-ref="pt"/>
    </aop:aspect>
</aop:config>

注解配置:

@Component
@Aspect
public class AopAdvice {
    @Pointcut("execution(* *..*(..))")
    public void pt(){}
    
    @before("pt()")
    public void function() {
        System.out.println("before");
    }
}
<!--开启AOP注解驱动支持-->
<aop:aspectj-autoproxy/>

步骤:

在xml格式基础上:

  1. 导入坐标(伴随spring-context坐标导入已经依赖导入完成)
  2. 开启AOP注解支持
  3. 配置切面@Aspect
  4. 定义专用的切入点方法,并配置切入点@Pointcut
  5. 为通知方法配置通知类型及对应切入点@Before

maven依赖配置:

<dependencies>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.9.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
  </dependency>
</dependencies>

spring配置:

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

    <!--开启AOP注解支持-->
    <aop:aspectj-autoproxy/>
    <context:component-scan base-package="com.itheima"/>
    <!--3.开启AOP命名空间-->
<!--    <bean id="userService" class="com.itheima.service.impl.UserServiceImpl"/>-->
    <!--2.配置共性功能成为spring控制的资源-->
<!--    <bean id="myAdvice" class="com.itheima.aop.AOPAdvice"/>-->

    <!--4.配置AOP-->
<!--    <aop:config>-->
        <!--5.配置切入点-->
<!--        <aop:pointcut id="pt" expression="execution(* *..*(..))"/>-->
        <!--6.配置切面:切入点与通知的关系-->
<!--        <aop:aspect ref="myAdvice">-->
            <!--7.配置具体的切入点对应通知中的哪个操作方法-->
<!--            <aop:before method="function" pointcut-ref="pt"/>-->
<!--            <aop:after method="after" pointcut-ref="pt"/>-->
<!--            <aop:after-returning method="afterReturing" pointcut-ref="pt"/>-->
<!--            <aop:after-throwing method="afterThrowing" pointcut-ref="pt"/>-->
<!--            <aop:around method="around" pointcut-ref="pt"/>-->
<!--        </aop:aspect>-->
<!--    </aop:config>-->

</beans>

通知类:

//1.制作通知类,在类中定义一个方法用于完成共性功能
@Component
@Aspect
public class AOPAdvice {

    @Pointcut("execution(* *..*(..))")
    public void pt() {}

    @Before("pt()")
    public void before(){
        System.out.println("前置before");
    }

    @After("pt()")
    public void after(){
        System.out.println("后置after");
    }

    @AfterReturning("pt()")
    public void afterReturing(){
        System.out.println("返回后afterReturing...");
    }

    @AfterThrowing("pt()")
    public void afterThrowing(){
        System.out.println("抛出异常后afterThrowing...");
    }

    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕前around before");
        Object ret = pjp.proceed();
        System.out.println("环绕后around after...");
        return ret;
    }
}

service类:

@Service("userService")
public class UserServiceImpl implements UserService {
    public int say(int a,int b) {
        System.out.println("user service running..."+a+","+b);
        return 100;
    }
}

测试类:

public class App {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) context.getBean("userService");

        int ret = userService.say(666, 888);
        System.out.println("应用程序调用执行结果...."+ret);
    }
}

运行结果:

环绕前around before
前置before
user service running…666,888
环绕后around after…
后置after
返回后afterReturing…
应用程序调用执行结果…100

注解开发AOP注意事项:

  1. 切入点最终体现一个方法,无参无返回值,无实际方法体内容,但不能是抽象方法
  2. 引用切入点时必须使用方法调用名称,方法后面的()不能省略
  3. 切面类中定义的切入点只能在当前类中使用,如果想引用其他类中定义的切入点使用“类名.方法名()”引用
  4. 可以在通知类型给你注解后面添加参数,实现xml配置中的属性,例如after-returning后的returning属性

4.2 通知执行顺序(了解)

AOP使用xml配置情况下,通知执行顺序是由配置顺序决定的,正注解情况下由于不存在配置顺序概念,参照通知所配置的方法名称字符串对应的编码值顺序,可以简单理解为字母排序

  • 同一个通知类中,相同通知类型以方法名排序为准
  • 不同通知类中,以类名排序为准
  • 使用@Order注解通过变更bean加载的顺序改变通知加载的顺序

企业开发经验:

  • 通知方法由3部分组成,分别是前缀、顺序编码、功能描述
  • 前缀为固定字符串,例如:baidu、itheima等,无实际意义
  • 顺序编码为6位以内的整数,通常3位即可,不足位补0
  • 功能描述为该方法对应的实际通知功能,例如:exception、strLenCheck
  • 控制通知执行顺序使用顺序编码控制,使用时做一定空间预留
    • 003使用,006使用,预留001、002、004、005、007、008
    • 使用时从中段开始使用,方便后期做前置追加或后置追加
    • 最终顺序以运行顺序为准,以测试结果为准,不以设定规则为准

4.3 AOP注解驱动

名称:@EnableAspectAutoProxy

类型:注解

位置:Spring注解配置类定义上方

作用:设置当前类开启AOP注解驱动的支持,加载AOP注解

格式:

  • @Configurable
    @ComponentScan("com.itheima")
    @EnableAspectAutoProxy
    public class SpringConfig {
    }
    

创建SpringConfig配置类:

@Configurable
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfig {
}

创建UserServiceTest测试类:(使用junit)

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class UserServiceTest {
    @Autowired
    private UserService userService;

    @Test
    public void testSay() {
        int ret = userService.say(666, 888);
        Assert.assertEquals(100,ret);
    }
}

运行结果:

环绕前around before
前置before
user service running…666,888
环绕后around after…
后置after
返回后afterReturing…

5.综合案例(重点)

5.1 案例介绍

对项目进行业务层接口执行监控,测量业务层接口的执行效率

接口:

public interface AccountService {
    void save(Account account);
    void delete(Integer id);
    void update(Account account);
    List<Account> findAll();
    Account findById(Integer id);
}

5.2 案例分析

测量接口执行效率:接口方法执行前后获取执行时间,求出执行时长

  • System.currentTimeMillis()

对项目进行监控:项目中所有接口方法,AOP思想,执行期动态织入代码

  • 环绕通知
  • proceed()方法执行前后获取系统访问时间

5.3 步骤

  1. 定义切入点(务必要绑定到接口上,而不是接口实现类上)
  2. 制作AOP环绕通知,完成测量功能
  3. 注解配置AOP
  4. 开启注解驱动支持

代码实现:

domain实体对象类:

public class Account {
    private Integer id;
    private String name;
    private Double money;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}

dao层接口:(注解方式)

public interface AccountDao {
    @Select("select * from account")
    List<Account> findAll();

    @Select("select * from account where id = #{id}")
    Account findById(Integer id);

    @Insert("inset into account values(null,#{name},#{money})")
    void inset(Account account);

    @Update("update account set name=#{name},money=#{money} where id=#{id}")
    void update(Account account);

    @Delete("delete from account where id=#{id}")
    void delete(Integer id);
}

service层接口及实现类:

public interface AccountService {
    List<Account> findAll();

    Account findById(Integer id);

    void inset(Account account);

    void update(Account account);

    void delete(Integer id);
}
@Service("accountService")
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    public List<Account> findAll() {
        return accountDao.findAll();
    }

    public Account findById(Integer id) {
        return accountDao.findById(id);
    }

    public void inset(Account account) {
        accountDao.inset(account);
    }

    public void update(Account account) {
        accountDao.update(account);
    }

    public void delete(Integer id) {
        accountDao.delete(id);
    }
}

配置类:

public class JDBCConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource getDataSource() {
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);

        return ds;
    }
}
public class MyBatisConfig {
    @Bean
    public SqlSessionFactoryBean getSqlSessionFactoryBean(@Autowired DataSource dataSource) {
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setTypeAliasesPackage("com.itheima.domain");
        ssfb.setDataSource(dataSource);
        return ssfb;
    }

    @Bean
    public MapperScannerConfigurer getMapperScannerConfigurer() {
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.itheima.dao");
        return msc;
    }
}
@Configurable
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JDBCConfig.class,MyBatisConfig.class})
@EnableAspectJAutoProxy
public class SpringConfig {
}

通知类:

@Component
@Aspect
public class RunTimeMonitorAdvice {
    //切入点,监控业务层接口
    @Pointcut("execution(* com.itheima.service.*Service.find*(..))")
    public void pt() {
    }

    @Around("pt()")
    public Object runtimeAround(ProceedingJoinPoint pjp) throws Throwable {
        Signature signature = pjp.getSignature();
        String className = signature.getDeclaringTypeName();
        String methodName = signature.getName();

        long sum = 0L;
        for (int i = 0; i <= 10000; i++) {
            long startTime = System.currentTimeMillis();
            pjp.proceed(pjp.getArgs());
            long endTime = System.currentTimeMillis();
            sum += endTime - startTime;
        }

        System.out.println(className + ":" + methodName + " 万次共运行时间: " + sum + "ms");
        return null;
    }
}

jdbc连接配置文件:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db
jdbc.username=root
jdbc.password=123456

测试类:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
    @Autowired
    private AccountService accountService;

    @Test
    public void testFindAll() {
        List<Account> list = accountService.findAll();
//        System.out.println(list);
    }

    @Test
    public void testFindById() {
        Account account = accountService.findById(2);
    }
}

测试运行结果:

com.itheima.service.AccountService:findAll 万次共运行时间: 14983ms
com.itheima.service.AccountService:findById 万次共运行时间: 5841ms

5.4 后续思考与设计

测量真实性:

  • 开发测量是隔离性反复执行某个操作,是理想情况,上线测量差异过大
  • 上线测量服务器性能略低于单机开发测量
  • 上线测量基于缓存的性能查询要优于数据库查询测量
  • 上线测量接口的性能与最终对外提供的服务性能差异过大
  • 当外部条件发生变化(硬件),需要进行回归测试,例如数据库迁移

测量结果展示:

  • 测量结果无需每一个都展示,需要设定检测阈值
  • 阈值设定要根据业务进行区分,一个复杂的查询与简单的查询差异化很大
  • 阈值设定需要做独立的配置文件或通过图形工具配置(工具级别的开发)
  • 配合图形界面展示测量结果

6.AOP底层原理(重点)

6.1 静态代理

装饰者模式(Decorator Pattern):在不惊动原始设计的基础上,为其添加功能

service层接口及实现类:

public interface UserService {
    void save();
}
public class UserServiceImpl implements UserService {
    public void save() {
        System.out.println("水泥墙");
    }
}

装饰类:

public class UserServiceImplDecorator implements UserService {

    private UserService userService;

    public UserServiceImplDecorator(UserService userService) {
        this.userService = userService;
    }

    public void save() {
        userService.save();
        //增强功能:刮大白

        System.out.println("刮大白");
    }
}
public class UserServiceImplDecorator2 implements UserService {

    private UserService userService;

    public UserServiceImplDecorator2(UserService userService) {
        this.userService = userService;
    }

    public void save() {
        userService.save();
        //增强功能:刮大白

        System.out.println("贴壁纸");
    }
}

测试类:

public class App {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
//        userService.save();
        UserService userService1 = new UserServiceImplDecorator(userService);
//        userService1.save();
        UserService userService2 = new UserServiceImplDecorator2(userService1);
        userService2.save();
    }
}

运行结果:

水泥墙
刮大白
贴壁纸

6.2 动态代理 Proxy

JDKProxy动态代理是针对对象做动态代理,要求原始对象具有接口实现,并对接口方法进行增强

动态代理类:

public class UserServiceJDKProxy {
    public static UserService createUserServiceJDKProxy(final UserService userService) {
        ClassLoader cl = userService.getClass().getClassLoader();
        Class[] classes = userService.getClass().getInterfaces();
        InvocationHandler ih = new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object ret = method.invoke(userService, args);
                System.out.println("刮大白2");
                System.out.println("贴壁纸2");
                return ret;
            }
        };

        UserService service = (UserService) Proxy.newProxyInstance(cl,classes,ih);
        return service;
    }
}

测试类:

public class App {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        UserService userService1 = UserServiceJDKProxy.createUserServiceJDKProxy(userService);
        userService1.save();
    }
}

运行结果:

水泥墙
刮大白2
贴壁纸2

6.3 动态代理 CGLIB

CGLIB(Code Generation Libarary),code生成类库

CGLIB动态代理不限定是否具有接口,可以对任意操作进行增强

CGLIB动态代理无需要原始被代理对象,动态创建新的代理对象

动态代理类:

public class UserServiceCglibProxy {

    public static UserService createUserServiceCglibProxy(Class cls) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(cls);
        enhancer.setCallback(new MethodInterceptor() {
            public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//                System.out.println(method.getName());
                Object ret = methodProxy.invokeSuper(o, args);
                if (method.getName().equals("save")) {
                    System.out.println("刮大白3");
                    System.out.println("贴墙纸3");
                }
                return ret;
            }
        });
        return (UserService) enhancer.create();
    }
}

测试类:

public class App {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        UserService userService1 = UserServiceCglibProxy.createUserServiceCglibProxy(UserServiceImpl.class);
//        System.out.println(userService1);
        userService1.save();
    }
}

运行结果:

水泥墙
刮大白3
贴墙纸3

代理模式选择:

Spring可以通过配置的形式控制使用的代理形式,默认使用jdkproxy,通过配置可以修改为使用cglib

  • XML配置

    <!--XML配置AOP-->
    <aop:config proxy-target-class="false"/>
    
  • XML注解支持

    <!--注解配置AOP-->
    <aop:aspectj-autoproxy proxy-target-class="false"/>
    
  • 注解驱动

    //注解驱动
    @EnableAspectJAutoProxy(proxyTargetClass = true)
    

6.4 织入形式

image-20210417171513007

编译期织入

类加载期织入

运行期织入

相关文章:Spring基础Spring进阶Spring高级

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值