SpringAOP

AOP

一,AOP概念及作用

1,概念作用

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SDFvsXFd-1595069781332)(E:\每日讲课笔记\Spring\img\1594862540226.png)]

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-90WZbO4P-1595069781334)(E:\每日讲课笔记\Spring\img\1594862623416.png)]

  • AOP概念(不修改源代码进行功能增强)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yP0yHW21-1595069781336)(E:\每日讲课笔记\Spring\img\1594886045169.png)]

    • AOP(Aspect Oriented Proframing)面向切面编程,一种编程范式,指导开发者如何组织程序结构。

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

    ◆ OOP规定程序开发以类为主体模型,一切围绕对象进行,完成某个任务先构建模型

    ◆ AOP程序开发主要关注基于OOP开发中的共性功能,一切围绕共性功能进行,完成某个任务先

    构建可能遇到的所有共性功能(当所有功能都开发出来也就没有共性与非共性之分)

  • AOP作用

    • 伴随着AOP时代的降临,可以从各个行业的标准化、规范化开始入手,一步一步将所有共性功能逐一开

    ​ 发完毕,最终以功能组合来完成个别业务模块乃至整体业务系统的开发 。(应用场景:加日志和计算时间)

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

    • 提高代码的可重用性

      业务代码编码更简洁

      业务代码维护更高效

      业务功能扩展更便捷

二,AOP入门案例

1,AOP核心概念

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i3Aq2fCQ-1595069781338)(E:\每日讲课笔记\Spring\img\1594863879567.png)]

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GTfWoC4U-1595069781340)(E:\每日讲课笔记\Spring\img\1594863914166.png)]

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0BN3b98I-1595069781341)(E:\每日讲课笔记\Spring\img\1594863946594.png)]

  • 操作的对象就是方法

  • 作用:动态的给方法添加一些功能

    //用于下面理解的目标对象,给各个方法增强功能
    @Component("accountService")
    public class AccountServiceImpl implements AccountService {
        @Autowired
        private AccountDao accountDao;
        public void save(Account account) {
            accountDao.save(account);
        }
        public void update(Account account){
            accountDao.update(account);
        }
        public void delete(Integer id) {
            accountDao.delete(id);
        }
        public Account findById(Integer id) {
            return accountDao.findById(id);
        }
        public List<Account> findAll() {
            return accountDao.findAll();
        }
    }
    
  • ⚫ Joinpoint(连接点):就是所有方法 (类中哪些方法可以被增强,这些方法称为连接点)

    ⚫ Pointcut(切入点):就是指具有共性功能的方法 (实际被真正增强的方法,称为切入点)

    ⚫ Advice(通知):就是共性功能,最终以一个方法的形式呈现 (**实际增强的逻辑部分(内容)**称为通知(增强))

    ⚫ Aspect(切面):就是共性功能与挖的位置的对应关系 (把通知应用到切入点的过程,用通知类型来指定

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

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

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

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

  • AOP开发过程
    • ⚫ 开发阶段(开发者完成)

      ◆ 正常的制作程序

      ◆ 将非共性功能开发到对应的目标对象类中,并制作成切入点方法

      ◆ 将共性功能独立开发出来,制作成通知

      ◆ 在配置文件中,声明切入点

      ◆ 在配置文件中,声明切入点通知间的关系(含通知类型),即切面

      ⚫ 运行阶段(AOP完成)

      ◆ Spring容器加载配置文件,监控所有配置的切入点方法的执行

      ◆ 当监控到切入点方法被运行,使用代理机制,动态创建目标对象代理对象,根据通知类别,在代理对象的对应位置将通知对应的功能织入,完成完整的代码逻辑并运行

2,案例制作

  • 重点:必须要创建一个接口,因为他实现功能增强会自动生成一个动态代理类,他需要和你的具体类(UserServiceImpl)一样继承同一个接口。才能测试类中类型强转为UserService所以一定要写这个接口

  • 步骤:

    • 导入坐标
    • 编写公共功能的方法(eg:编写一个方法执行打印当前时间)
    • 知道哪些方法需要增强(pointcut标签确定哪些方法需要增强)
    • 在applicationContext.xml中进行关联配置各种关系
  • 代码演示

  • 结构

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JSu50Xs8-1595069781342)(E:\每日讲课笔记\Spring\img\1594953780989.png)]

    • 坐标

      <?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.itheima</groupId>
          <artifactId>Spring_day03</artifactId>
          <version>1.0-SNAPSHOT</version>
      
          <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>
      </project>
      
    • 抽取要增强的方法在一个类中

      package com.itheima.aop;
      //制作通知类,在类中定义一个方法用于完成共性功能
      public class AOPAdvice {
          public void function(){
              System.out.println("wangling");
          }
          public void currentTime(){
              System.out.println(System.currentTimeMillis());
          }
      }
      
    • 写出具体对象类中的方法,然后再配置文件中再指定具体要增强的是这里面的哪些方法

      package com.itheima.service;
      public interface UserService {
           void save();
           void save1();
      }
      
      package com.itheima.service.impl;
      import com.itheima.service.UserService;
      
      public class UserServiceImpl implements UserService {
          //要真正增强的方法,切入点
          public void save() {
              //System.out.println("wangling");要抽取的共性功能,配置在通知类中具体的方法中
              System.out.println("林铭");
          }
          public void save1(){
              System.out.println("王林");
          }
      }
      
    • 配置关联关系,包括具体通知和切入点的关系,切面

      <?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
              https://www.springframework.org/schema/beans/spring-beans.xsd
              http://www.springframework.org/schema/aop
              https://www.springframework.org/schema/aop/spring-aop.xsd">
      
          <!--保证这俩类是spring控制的资源-->
              <bean id="userService" class="com.itheima.service.impl.UserServiceImpl"/>
              <!--配置共性功能成为spring控制的资源-->
              <bean id="Advice" class="com.itheima.aop.AOPAdvice"/>
      
          <!--开启AOP命名空间,配置AOP-->
              <aop:config>
                  <!--配置切入点(实际要增强的方法,表达式代表指定哪些方法需要增强),指定一个变量指定当前需要增强的方法-->
                  <aop:pointcut id="pt" expression="execution(* *..*(..))"/>
                  <!--<aop:pointcut id="pt" expression="execution(* *..save*(..))"/>-->
                  <!--配置切面,切入点与通知的关系,通过aspect指定pt中指定的方法到底要做什么增强,在哪个位置增强-->
                  <aop:aspect ref="Advice"><!--表示所有的通知都来自Advice中(通知中具体方法的内容就是实际要增强的内容)-->
                      <!--配置具体的切入点对应通知中的哪个操作方法(把通知中具体哪个方法通过通知类型放到切入点的具体位置,达到增强效果)-->
                     <aop:before method="function" pointcut-ref="pt"/><!--在pt指定的方法之前执行function方法-->
                      <aop:after method="currentTime" pointcut-ref="pt"/>
                  </aop:aspect>
              </aop:config>
      </beans>
      
      • 测试加结果(因为做了在方法前后都增强了东西)

        import com.itheima.service.UserService;
        import org.springframework.context.ApplicationContext;
        import org.springframework.context.support.ClassPathXmlApplicationContext;
        public class App {
            public static void main(String[] args) {
                ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
                UserService userService = (UserService) ctx.getBean("userService");
                userService.save();
                System.out.println("————————");
                userService.save1();
            }
        }
        
        wangling
        林铭
        1594953191579
        wangling
        王林
        1594953191579
        

三,AOP配置(xml)

1,AOP基本配置

  • AspectJ

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

    ⚫ AspectJ是基于java语言对Aspect的实现

  • ⚫ 名称:aop:config

    ⚫ 类型:标签

    ⚫ 归属:beans标签

    ⚫ 作用:设置AOP

    ⚫ 说明:一个beans标签中可以配置多个aop:config标签

  • ⚫ 名称:aop:aspect

    ⚫ 类型:标签

    ⚫ 归属:aop:config标签

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

    ⚫ 说明:

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

    ⚫ 基本属性:

    ◆ ref :通知所在的bean的id

  • ⚫ 名称:aop:pointcut

    ⚫ 类型:标签

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

    ⚫ 作用:设置切入点

    ⚫ 说明:

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

    ⚫ 基本属性:

    ◆ id :识别切入点的名称

    ◆ expression :切入点表达式

        <bean id="Advice" class="com.itheima.aop.AOPAdvice"/>
    <!--开启AOP命名空间,配置AOP-->
        <aop:config>
            <!--配置切入点-->
            <aop:pointcut id="pt" expression="execution(* *..*(..))"/>
            <!--配置切面,切入点与通知的关系-->
            <aop:aspect ref="Advice"><!--表示所有的通知都来自Advice中-->
                <!--配置具体的切入点对应通知中的哪个操作方法-->
               <aop:before method="function" pointcut-ref="pt"/>
            </aop:aspect>
        </aop:config>

2,切入点表达式

  • 切入点
    • 切入点描述的是某个方法(让我们快速知道是对哪个类中的哪个方法进行增强)
    • 切入点表达式是一个快速匹配方法描述的通配格式,类似于正则表达式。
  • 切入点表达式的组成
    • 切入点描述的是某个方法

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

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

    ◆ 关键字:描述表达式的匹配模式(参看关键字列表)

    ◆ 访问修饰符:方法的访问控制权限修饰符

    ◆ 类名:方法所在的类(此处也可以配置接口名称

    ◆ 异常:方法定义中指定抛出的异常

  • 切入点表达式——通配符

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

    execution(public * com.itheima.*.UserService.find*(*))
    
    • 匹配com.itheima包下的任意包下的UserService接口或者类中的find开头的带有一个任意参数的方法。

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

    execution(public User com..UserService.findById(..))
    
    • 匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法

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

    execution(* *..*Service+.*(..))
    
  • 切入点表达式——逻辑运算符

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

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

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

    ————————————————————

  • 切入点表达式——范例

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eXtMK72a-1595069781344)(E:\每日讲课笔记\Spring\img\1594869020523.png)]

3,三种切入点配置方式

<aop:config>
	<!--配置公共切入点-->
    <aop:pointcut id="pt1" expression="execution(* *(..))"/>
    
	<aop:aspect ref="myAdvice">
		<!--配置局部切入点--> 
   		 <aop:pointcut id="pt2" expression="execution(* *(..))"/>
		<!--引用公共切入点-->
   		 <aop:before method="logAdvice" pointcut-ref="pt1"/>
		<!--引用局部切入点-->
   		 <aop:before method="logAdvice" pointcut-ref="pt2"/>
		<!--直接配置切入点-->
    	<aop:before method="logAdvice" pointcut="execution(* *(..))"/>
	</aop:aspect>
</aop:config>
  • 全局切入点把切入点配置写到切面外面,局部切入点把切入点写到切面里面,第三种是直接配置切入点格式

4,五种通知类型配置

  • 通知类型
    • ⚫ AOP的通知类型共5种

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

      应用:数据校验

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

      应用:现场清理

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

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

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

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

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

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

  • 前置通知

    • ⚫ 名称:aop:before

      ⚫ 类型:标签

      ⚫ 归属:aop:aspect标签

      ⚫ 作用:设置前置通知

      ⚫ 格式:

      <aop:aspect ref="adviceId"> 
          <aop:before method="methodName" pointcut="……"/>
      </aop:aspect>
      

      ⚫ 说明:一个aop:aspect标签中可以配置多个aop:before标签

      ⚫ 基本属性:

      ◆ method :在通知类中设置当前通知类别对应的方法

      ◆ pointcut :设置当前通知对应的切入点表达式,与pointcut-ref属性冲突

      ◆ pointcut-ref :设置当前通知对应的切入点id,与pointcut属性冲突

  • 后置通知

    • ⚫ 名称:aop:after

      ⚫ 类型:标签

      ⚫ 归属:aop:aspect标签

      ⚫ 作用:设置后置通知

      ⚫ 格式:

      <aop:aspect ref="adviceId"> 
          <aop:after method="methodName" pointcut="……"/>
      </aop:aspect>
      

      ⚫ 说明:一个aop:aspect标签中可以配置多个aop:after标签

      ⚫ 基本属性:

      ◆ method :在通知类中设置当前通知类别对应的方法

      ◆ pointcut :设置当前通知对应的切入点表达式,与pointcut-ref属性冲突

      ◆ pointcut-ref :设置当前通知对应的切入点id,与pointcut属性冲突

  • 返回后通知

    • ⚫ 名称:aop:after-returning

      ⚫ 类型:标签

      ⚫ 归属:aop:aspect标签

      ⚫ 作用:设置返回后通知

      ⚫ 格式:

      <aop:aspect ref="adviceId"> 
          <aop:after-returning method="methodName" pointcut="……"/>
      </aop:aspect>
      

      ⚫ 说明:一个aop:aspect标签中可以配置多个aop:after-returning标签

      ⚫ 基本属性:

      ◆ method :在通知类中设置当前通知类别对应的方法

      ◆ pointcut :设置当前通知对应的切入点表达式,与pointcut-ref属性冲突

      ◆ pointcut-ref :设置当前通知对应的切入点id,与pointcut属性冲突

  • 抛出异常后通知

    • ⚫ 名称:aop:after-throwing

      ⚫ 类型:标签

      ⚫ 归属:aop:aspect标签

      ⚫ 作用:设置抛出异常后通知

      ⚫ 格式:

      <aop:aspect ref="adviceId"> 
          <aop:after-throwing method="methodName" pointcut="……"/>
      </aop:aspect>
      

      ⚫ 说明:一个aop:aspect标签中可以配置多个aop:after-throwing标签

      ⚫ 基本属性:

      ◆ method :在通知类中设置当前通知类别对应的方法

      ◆ pointcut :设置当前通知对应的切入点表达式,与pointcut-ref属性冲突

      ◆ pointcut-ref :设置当前通知对应的切入点id,与pointcut属性冲突

  • 环绕通知(ProceedingJoinPoint)

    • ⚫ 名称:aop:around

      ⚫ 类型:标签

      ⚫ 归属:aop:aspect标签

      ⚫ 作用:设置环绕通知

      ⚫ 格式:

      <aop:aspect ref="adviceId">
          <aop:around method="methodName" pointcut="……"/>
      </aop:aspect>
      

      ⚫ 说明:一个aop:aspect标签中可以配置多个aop:around标签

      ⚫ 基本属性:

      ◆ method :在通知类中设置当前通知类别对应的方法

      ◆ pointcut :设置当前通知对应的切入点表达式,与pointcut-ref属性冲突

      ◆ pointcut-ref :设置当前通知对应的切入点id,与pointcut属性冲突

  • ⚫ 环绕通知是在原始方法的前后添加功能,在环绕通知中,存在对原始方法的显式调用

public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object ret = pjp.proceed();
return ret;
}

⚫ 环绕通知方法相关说明:

  • 方法须设定Object类型的返回值,否则会拦截原始方法的返回。如果原始方法返回值类型为

    void,通知方法也可以设定返回值类型为void,最终返回null

    • 方法需在第一个参数位置设定ProceedingJoinPoint对象,通过该对象调用proceed()方法,实

    ​ 现对原始方法的调用。如省略该参数,原始方法将无法执行

    • 使用proceed()方法调用原始方法时,因无法预知原始方法运行过程中是否会出现异常,强制抛

    ​ 出Throwable对象,封装原始方法中可能出现的异常信息

    • 环绕通知代码演示

      package com.itheima.aop;
      import org.aspectj.lang.ProceedingJoinPoint;
      //制作通知类,在类中定义一个方法用于完成共性功能
      public class AOPAdvice {
          //所以这个环绕通知就包括前三种通知(相当于动态代理)
          public Object around(ProceedingJoinPoint pjp)  {
              try {
                  System.out.println("前置通知");
                  Object ret = pjp.proceed();//调用原始方法
                  System.out.println("返回后通知");
              } catch (Throwable throwable) {
                  throwable.printStackTrace();
                  System.out.println("异常通知");
              }finally {
                  System.out.println("后置通知");
              }
             return null;
          }
      }
      

- 配置文件

  ```xml
  <?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
          https://www.springframework.org/schema/beans/spring-beans.xsd
          http://www.springframework.org/schema/aop
          https://www.springframework.org/schema/aop/spring-aop.xsd">
  
      <!--保证这俩类是spring控制的资源-->
          <bean id="userService" class="com.itheima.service.impl.UserServiceImpl"/>
          <!--配置共性功能成为spring控制的资源-->
          <bean id="Advice" class="com.itheima.aop.AOPAdvice"/>
  
      <!--开启AOP命名空间,配置AOP-->
          <aop:config>
              <!--配置切入点(实际要增强的方法,表达式代表指定哪些方法需要增强),指定一个变量指定当前需要增强的方法-->
              <aop:pointcut id="pt" expression="execution(* *..*(..))"/>
              <!--<aop:pointcut id="pt" expression="execution(* *..save*(..))"/>-->
              <!--配置切面,切入点与通知的关系,通过aspect指定pt中指定的方法到底要做什么增强,在哪个位置增强-->
              <aop:aspect ref="Advice"><!--表示所有的通知都来自Advice中(通知中具体方法的内容就是实际要增强的内容)-->
                  <!--配置具体的切入点对应通知中的哪个操作方法(把通知中具体哪个方法通过通知类型放到切入点的具体位置,达到增强效果)-->
                  <aop:around method="around" pointcut-ref="pt"/><!--在指定方法环绕执行around方法-->
              </aop:aspect>
          </aop:config>
  </beans>
  • 接口和实现类的代码和上面一样,

5,通知顺序

  • 当同一个切入点配置了多个通知时,通知会存在运行的先后顺序,该顺序以通知配置的顺序为准

6,通知中获取参数

  • 设定通知方法第一个参数为JoinPoint,通过该对象调用getArgs()方法,获取原始方法运行的参数数组
public void before(JoinPoint jp) throws Throwable {
 	Object[] args = jp.getArgs();
}
  • 所有的通知均可以获取参数

7,通知中获取返回值

  • 应用场景

    执行方法之前记录日志
    记录是哪一个类
    记录方法的名称
    记录方法调用的具体参数
    记录返回值
    记录异常
    
  • 方法签名(signature):包括,方法名+方法返回值+方法的参数,全限定类名等

  • 拿到原始方法的信息(通过获得的签名对象)

  • JoinPoint poj必须放在第一个参数位置

  • 通知类

  •  public void currentTime(JoinPoint poj){
            //获取签名,然后通过签名获取方法名
            Signature signature=poj.getSignature();
            String name = signature.getName();
            System.out.println(name+":start"+System.currentTimeMillis());
        }
    

    获取返回值数据的两种(结束后返回,和环绕)

  • ⚫ 1,环绕通知类,的方法中调用原始方法获取返回值

    ⚫ 原始方法

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

    ⚫ AOP配置

    <aop:aspect ref="myAdvice"> <aop:pointcut id="pt2" expression="execution(* *(..)) "/>
    <aop:around method="around" pointcut-ref="pt2" />
    </aop:aspect>
    

    ⚫ 通知类

     public Object around(ProceedingJoinPoint pjp)  {
            Object ret=null;
            try {
                System.out.println("前置通知");
                ret = pjp.proceed(pjp.getArgs());//调用原始方法,这里不确定有没有返回值,所以传了参数
                System.out.println("返回后通知");
            } catch (Throwable throwable) {
                throwable.printStackTrace();
                System.out.println("异常通知");
            }finally {
                System.out.println("后置通知");
            }
           return ret;
        }
    

    2,返回后通知,设定返回值变量名

    ⚫ 原始方法

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

    ⚫ AOP配置

    <aop:aspect ref="myAdvice"> <aop:pointcut id="pt3" expression="execution(* *(..)) "/>
    <aop:after-returning method="afterReturning" pointcut-ref="pt3" returning="ret"/>
    </aop:aspect>
    

    ⚫ 通知类

    blic void afterReturning(Object ret) {
    System.out.println(ret);
    }
    

    ⚫ 适用于返回后通知(after-returning)

8,通知中获取异常对象

四,AOP配置(注解)

1,注解配置AOP

  • 需要配这几个点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MJI2KGtl-1595069781345)(E:\每日讲课笔记\Spring\img\1594972584043.png)]

  • 类上方配@Aspect,设置当前类为切面类

  • 步骤(参照上图)
    1,类上方配@Aspect,设置当前类为切面类
    2,方法定义上方@Pointcut,使用当前方法名作为切入点引用名称
    3,方法定义上方。标注切入点和切面关系@Before@After@AfterReturning@AfteThrowing@Around
    4,主配置类(springConfig)中,开启包扫描注解和AOP注解驱动支持注解(@Configuration
    @ComponentScan("com.itheima"):@EnableAspectJAutoProxy)
    
  • @Configuration
    @ComponentScan("com.itheima")
    @EnableAspectJAutoProxy
    public class SpringConfig {
    }//创建一个config包,然后在这里面创造这个SpringConfig主配置类,里面开启包扫描和AOP注解驱动支持
    
  • 注解开发AOP注意事项

    1. 切入点最终体现为一个方法,无参无返回值,无实际方法体内容,但不能是抽象方法

    2. 引用切入点时必须使用方法调用名称,方法后面的()不能省略

    3. 切面类中定义的切入点只能在当前类中使用,如果想引用其他类中定义的切入点使用“类名.方法名()”引用

    4. 可以在通知类型注解后添加参数,实现XML配置中的属性,例如after-returning后的returning属性

    @AfterReturning(value="pt()",returning = "ret")
    public void afterReturning(Object ret) {
    System.out.println("before");
    }
    

2,注解AOP通知执行顺序控制

3,AOP注解驱动

五,综合案例

  • 案例分析

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

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

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

    ◆ System.currentTimeMillis( )

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

    ◆ 环绕通知 (因为要算原始方法执行前后的时间,所以用环绕)

    proceed()方法执行前后获取系统时间

  • 代码实现

  • 项目结构

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3sHE2XZa-1595069781347)(E:\每日讲课笔记\Spring\img\1594984574576.png)]

  • 接口

    package com.itheima.service;
    import com.itheima.domain.Account;
    import java.util.List;
    public interface AccountService {
        void save(Account account);
        void delete(Integer id);
       void update(Account account);
        List<Account> findAll();
        Account findById(Integer id);
    }
    
  • 实现类

    package com.itheima.service.impl;
    import com.itheima.dao.AccountDao;
    import com.itheima.domain.Account;
    import com.itheima.service.AccountService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import java.util.List;
    @Service("accountService")
    public class AccountServiceImpl implements AccountService {
    
        @Autowired
        private AccountDao accountDao;
    
        public void save(Account account) {
            accountDao.save(account);
        }
        public void update(Account account){
            accountDao.update(account);
        }
        public void delete(Integer id) {
            accountDao.delete(id);
        }
        public Account findById(Integer id) {
            return accountDao.findById(id);
        }
        public List<Account> findAll() {
            return accountDao.findAll();
        }
    }
    
  • 环绕通知类

    package com.itheima.aop;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.Signature;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    //制作AOP环绕通知类
    @Component
    @Aspect
    public class RunTimeMonitorAdvice {
        //切入点,监控业务层接口
        @Pointcut("execution(* com.itheima.service.AccountService.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;
    
            //线上写不能这样写一万次,因为数据库一级缓存,findById和findAll测试的时间差距会越来越小,所以不能写一万次
            for (int i = 0; i < 10000; i++) {
                //获取操作前系统时间beginTime
                long startTime = System.currentTimeMillis();
                //原始操作调用
                pjp.proceed(pjp.getArgs());
                //获取操作后系统时间endTime
                long endTime = System.currentTimeMillis();
                sum += endTime-startTime;
            }
            //打印信息
            System.out.println(className+":"+methodName+"   (万次)run:"+sum+"ms");
            return null;
        }
    }
    
    
  • 主配置类

    package com.itheima.config;
    import org.springframework.context.annotation.*;
    
    @Configuration
    @ComponentScan("com.itheima")//开启包扫描
    @PropertySource("classpath:jdbc.properties")
    @Import({JDBCConfig.class,MyBatisConfig.class})
    @EnableAspectJAutoProxy//开启AOP注解驱动支持
    public class SpringConfig {
    }
    
  • 测试和结果

    package com.itheima.service;
    import com.itheima.config.SpringConfig;
    import com.itheima.domain.Account;
    import org.junit.Assert;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    import java.util.List;
    
    //设定spring专用的类加载器
    @RunWith(SpringJUnit4ClassRunner.class)
    //设定加载的spring上下文对应的配置
    @ContextConfiguration(classes = SpringConfig.class)
    public class UserServiceTest {
    
        @Autowired
        private AccountService accountService;
    
        @Test
        public void testFindById(){
            Account ac = accountService.findById(2);
    //        System.out.println(ac);
        }
    
        @Test
        public void testFindAll(){
            List<Account> list = accountService.findAll();
    //        System.out.println(list);
        }
    
    }
    
    
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jwtWkaHY-1595069781348)(E:\每日讲课笔记\Spring\img\1594976910120.png)]

  • 其他是sping_mybatis整合的类,参照上一个文件

六,AOP底层原理

1,装饰模式(静态代理)

2,动态代理

  • 1,jdk动态代理实现(Spirng可以通过配置或注解的形式控制使用的代理形式,默认使用jdkproxy,)
  • 使用jdk动态代理,使用Proxy类里面的方法创建代理对象,比如创建一个接口,有一个这个接口的实现类,现在对接口进行代理,对其方法做增强,那么他会自动创建一个代理类实现这个接口,然后在这个类做你要做的动态代理,然后强转为接口类型。

  • newProxyInstance方法

    • 方法有三个参数分别是:
      • 类加载器,
      • 增强方法所在的类,这个类实现的接口,支持多个接口
      • 实现这个接口InvocationHandler,创建代理对象,写增强的方法。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xv9ksyIg-1595069781350)(E:\每日讲课笔记\Spring\img\1594988450890.png)]

  • 代码实现

  • 准备接口和实现类

    package com.itheima.service.impl;
    import com.itheima.service.UserService;
    public class UserServiceImpl implements UserService {
        //要真正增强的方法,切入点
        public void save() {
            //System.out.println("wangling");要抽取的共性功能,配置在通知类中具体的方法中
            System.out.println("林铭");
        }
        public void save1(){
            System.out.println("王林");
        }
    }
    
    package com.itheima.service;
    public interface UserService {
         void save();
         void save1();
    }
    
  • 准备一个包实现jdk动态代理

    package base.proxy;
    import com.itheima.service.UserService;
    import org.springframework.transaction.PlatformTransactionManager;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    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;
        }
    
    }
    
  • 测试

    package base.proxy;
    import com.itheima.service.UserService;
    import com.itheima.service.impl.UserServiceImpl;
    public class App {
        public static void main(String[] args) {
            UserService userService  = new UserServiceImpl();
            UserService userService1 = UserServiceJDKProxy.createUserServiceJDKProxy(userService);
            userService1.save();
        }
    }
    
  • 2,动态代理——CGLIB实现

    ⚫ CGLIB(Code Generation Library),Code生成类库

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

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rpgkOMsw-1595069781351)(E:\每日讲课笔记\Spring\img\1594984780039.png)]

  • 代码实现

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;
  }

}


- 测试

```java
package base.proxy;
import com.itheima.service.UserService;
import com.itheima.service.impl.UserServiceImpl;
public class App {
    public static void main(String[] args) {
        UserService userService  = new UserServiceImpl();
        UserService userService1 = UserServiceJDKProxy.createUserServiceJDKProxy(userService);
        userService1.save();
    }
}
  • 2,动态代理——CGLIB实现

    ⚫ CGLIB(Code Generation Library),Code生成类库

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

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

    [外链图片转存中…(img-rpgkOMsw-1595069781351)]

  • 代码实现

3,织入形式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值