五种通知类型

说明: (1) 【Before Advice】,【After Returning Advice】,【After Throwing Advice】,【After Advice】这四种通知,在日常开发中并不常用,只是可能在面试中会被问及;

(2)Around Advice 环绕通知 】是重点内容,这部分内容在下篇博客中详细介绍;

(3) 通知执行顺序说明:

●【前置通知】与配置顺序无关,总是先执行;

●【返回后通知】和【后置通知】的执行顺序,是由在applicationContext.xml中配置的前后顺序决定的;

●【异常通知】和【后置通知】的执行顺序,是由在applicationContext.xml中配置的前后顺序决定的;

●由于【返回后通知】和【异常通知】是互斥的(很显然,如果程序报了异常,肯定就走不到返回值的那一步了);所以,比较【返回后通知】和【异常通知】的执行前后顺序,是没有意义的;

(5) 【引介增强】了解一下,知道有这个东西就行,如果以后遇到了,再深入了解也不迟;


Advice通知是指,在什么时机去执行切面类中的切面方法;

一:五种通知类型;

(1)Before Advice: 前置通知,在目标方法执行前,执行切面方法。前面已经使用过前置通知了;

(2)After Returning Advice: 返回后通知,在目标方法返回数据后,执行切面方法;这个通知,可以接受目标方法产生的返回值;

(3)After Throwing Advice: 异常通知,目标方法抛出异常后,执行切面方法;

可以看到,【After Returning Advice代表,程序程序执行成功了,返回对象后,所产生的通知】;【After Throwing Advice代表,程序运行出错,程序内部抛出异常后,所产生的通知】,即【After Returning Advice和After Throwing Advice是互斥的,二者只能选其一】

(4)After Advice: 后置通知,目标方法执行后,执行切面方法;

【After Advice,有点像try-catch语句块中的finally;即,不管目标方法执行成功还是抛出异常,After Advice都会执行】

(5)Around Advice: 最强大的通知;这是核心的,也是最重要的通知;


Before Advice 】,【 After Returning Advice 】,【 After Throwing Advice 】,【 After Advice 】这四种通知不太重要,了解一下就行;【 Around Advice】是最重要的通知,需要重点了解;


补充:除了上述五种通知外,还有一种特殊的“通知”:引介增强;(目前来说,仅需知道有这个东西行了)

(0) Spring官方文档中,Advice通知类型只有【 Before Advice 】,【 After Returning Advice 】,【 After Throwing Advice 】,【 After Advice 】,【 Around Advice 】这五种;【引介增强】并不属于Spring文档中规定的通知;【引介增强】只是一个派生的,类似通知的一个组件;

(1) 引介增强本质是一个拦截器;Advice的五种通知,都是作用在方法上的,即上面的Advice五种通知是对方法的增强;而,引介增强是对类的增强;

(2) 引介增强,允许在运行时为目标类增加新的属性或方法;这是一种很高级的应用;在程序运行时,类已经被加载到了JVM中,然后引介增强会针对这些已经被加载进内存中的类,进行动态的调整;

(3) 引介增强的好处:可以让Java程序变得非常灵活;比如,在程序运行时,程序的运行环境参数发生变化了,那么引介增强就可以让目标类动态改变运行逻辑,去适应新的环境;

(4) 引介增强,使用比较复杂,在日常开发中,使用的也比较少;所以,知道有这个东西就可以了,如果以后碰到了再深入了解也不迟;


二:【Before Advice 前置通知】,【After Returning Advice 返回后通知】,【After Throwing Advice 异常通知】,【After Advice后置通知】的使用案例;

以下内容,基于【AOP初体验】 中的代码;

其中SpringApplication入口类代码如下:

package com.imooc.spring.aop;
 
import com.imooc.spring.aop.dao.UserDao;
import com.imooc.spring.aop.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
 
public class SpringApplication {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.createUser();
        userService.generateRandomPassword("md5", 10);
    }
}

【After Advice 后置通知】演示:

首先,在切面类中添加处理后置通知的切面方法;

然后,在applicationContext.xml中配置后置通知;

运行结果:


情况说明:

【After Advice 后置通知】有一个问题,其无法获得目标方法运行过程中,所产生的返回值或者是抛出的异常;此时,就需要【After Returning Advice 返回后通知】和【After Throwing Advice 异常通知】了;

【After Returning Advice 返回后通知】演示:(这个通知,可以接受目标方法的返回值;然后只有目标方法没有抛异常并成功执行,这个通知才会起作用)

首先,在切面类中添加处理后置通知的切面方法;

然后,在applicationContext.xml中配置返回后通知;

运行结果:


情况说明:【返回后通知】和【后置通知】的执行顺序,是由在applicationContext.xml中配置的前后顺序决定的;

然后,经过实测,发现【前置通知】与配置顺序无关,总是先执行;

【After Throwing Advice 异常通知】演示:(这个通知,可以捕获目标方法抛出的异常)

首先,在切面类中添加处理异常通知的切面方法;

然后,在applicationContext.xml中配置异常通知;

然后,去设置抛出一个异常;

运行结果;


情况说明: 由于【After Returning Advice和After Throwing Advice是互斥的,二者只能选其一】所以,对于前后顺序这儿,就比比较【After Returning Advice和After Throwing Advice是互斥的,二者只能选其一】了。 【后置通知】和【After Throwing Advice异常通知】的执行顺序,是由在applicationContext.xml中配置的前后顺序决定的;

详解环绕通知

说明:

(1)本篇博客主要内容:Around Advice 环绕通知, 这是最强大的通知,自定义通知执行时机,可决定目标方法是否运行。这是核心的,也是最重要的通知。

(2) 虽然在【MyBatis日志管理】介绍了日志的内容,但是如何把日志存放在一个日志文件中尚未接触;然后,估计日志还有很多其他内容尚不了解;所以,以后一定要较系统的了解下系统日志的内容;

(3) 本篇博客的案例还是比较重要的,可以预估在以后的开发中,案例中的问题大概率会经常遇到;

零:需求说明

场景和需求: 随着数据量的累计、用户量的增大,生产环境中系统越来越慢,如何定位到底是哪个方法执行慢?

问题分析:

●这个问题看似简单,但实际上挺复杂的;因为,一个大型系统的类和方法可能有成千上万,我们肯定不能给每一个方法都增加代码去捕捉方法的执行时间,因为这样做效率会很差;

● 合适的策略:【Spring AOP】就是一个很好的方案;我们只需要在方法执行前捕捉方法的开始时间,方法执行后捕获方法的结束时间,然后就能算出方法的执行时间;如果方法的执行时间超过了规定范围,我们就将其输出保存在日志中;

●因为这个场景下,我们要获取两个时间(运行前时间和运行后时间),可以知道AOP中的【前置通知、后置通知、返回后通知、异常通知】都不能解决这个问题;Spring提供了一个更强大的通知【Around Advice 环绕通知】,利用环绕通知,可以控制目标方法完整的运行周期;


一:准备一个工程,演示用;

为了演示,导入示例工程s01(这个工程在WorkSpace的aop目录下):

其中预置了readme.md,EmployeeDao,UserDao,EmployeeService,EmployeeDao;

(1)readme.md文档:

XML配置Sring AOP

(2)EmployeeDao类:

package com.imooc.spring.aop.dao;
 
 /**
  * 员工表Dao
  */
 public class EmployeeDao {
     public void insert(){
         System.out.println("新增员工数据");
     }
 }

说明:

(1) 这个类模拟了,操作数据中的Employee员工表; 其中添加了一个示意性的方法,向Employee员工表插入一条数据;

(3)UserDao类:

package com.imooc.spring.aop.dao;
 
 /**
  * 用户表Dao
  */
 public class UserDao {
     public void insert(){
         System.out.println("新增用户数据");
     }
 }

说明:

(1) 这个类模拟了,操作数据中User用户表; 其中添加了一个示意性的方法,向User用户表插入一条数据;

(4)EmployeeService类:

package com.imooc.spring.aop.service;
 
 import com.imooc.spring.aop.dao.EmployeeDao;
 
 /**
  * 员工服务
  */
 public class EmployeeService {
     private EmployeeDao employeeDao;
     public void entry(){
         System.out.println("执行员工入职业务逻辑");
         employeeDao.insert();
     }
 
     public EmployeeDao getEmployeeDao() {
         return employeeDao;
     }
 
     public void setEmployeeDao(EmployeeDao employeeDao) {
         this.employeeDao = employeeDao;
     }
 }

说明:

(1) EmployeeService主要是处理员工入职的逻辑,其中会调用EmployeeDao中的方法;

(2) 然后,其中的EmployeeDao属性,也生成了get和set方法;

(5)UserService类:

package com.imooc.spring.aop.service;
 
 import com.imooc.spring.aop.dao.UserDao;
 
 /**
  * 用户服务
  */
 public class UserService {
     private UserDao userDao;
 
     public void createUser(){
         System.out.println("执行员工入职业务逻辑");
         userDao.insert();
     }
 
     public String generateRandomPassword(String type , Integer length){
         System.out.println("按" + type + "方式生成"+ length  + "位随机密码");
         return "Zxcquei1";
     }
 
     public UserDao getUserDao() {
         return userDao;
     }
 
     public void setUserDao(UserDao userDao) {
         this.userDao = userDao;
     }
 }

说明:

(1) 代码分析

(2) 然后,其中的UserDao属性,也生成了get和set方法;

这个工程,目前有四个类:UserDao类,EmployeeDao类,UserService类,EmployeeService类;我们要在这四个类的每一个方法上,进行运行时间的检查,如果某个方法的运行时间超过一定时间,就认为这个方法执行太慢,需要优化;


二:正式编码前的准备

(1)pom.xml:引入所需依赖:【spring-context模块】,【aspectjweaver模块】

<?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.imooc.spring</groupId>
     <artifactId>aop</artifactId>
     <version>1.0-SNAPSHOT</version>
     <repositories>
         <repository>
             <id>aliyun</id>
             <name>aliyun</name>
             <url>https://maven.aliyun.com/repository/public</url>
         </repository>
     </repositories>
     <dependencies>
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-context</artifactId>
             <version>5.3.9</version>
         </dependency>
         <dependency>
             <groupId>org.aspectj</groupId>
             <artifactId>aspectjweaver</artifactId>
             <version>1.9.5</version>
         </dependency>
     </dependencies>
 </project>

说明:

(1) 在【AOP初体验】中,【spring-context】引入5.3.9版本会出错;但是,这儿不会报错了,有点莫名其妙;

(2)创建applicationContext.xml配置文件:引入【默认命名空间】,【context命名空间】,【aop命名空间】;配置IoC容器中的bean;

<?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: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
         http://www.springframework.org/schema/aop/spring-aop.xsd">
 
     <bean id="userDao" class="com.imooc.spring.aop.dao.UserDao"/>
     <bean id="employeeDao"
class="com.imooc.spring.aop.dao.EmployeeDao"/>
     <bean id="userService"
class="com.imooc.spring.aop.service.UserService">
         <property name="userDao" ref="userDao"/>
     </bean>
     <bean id="employeeService"
class="com.imooc.spring.aop.service.EmployeeService">
         <property name="employeeDao" ref="employeeDao"/>
     </bean>
 
 </beans>

说明:

(1) 引入了默认命名空间,context命名空间,aop命名空间;以及,设置对应命名空间的schema约束;

(2) 然后,设置了UserDao类,EmployeeDao类,UserService类,EmployeeService类的bean;


三:正式实现:使用【Spring AOP技术】中的【Around Advice 环绕通知】去实现【零:需求说明】中的需求;

(1)创建容纳切面类的包:aspect包;添加用户检查方法运行效率的切面类:MethodChecker类;

MethodChecker类:

package com.imooc.spring.aop.aspect;
 
 import org.aspectj.lang.ProceedingJoinPoint;
 
 import java.text.SimpleDateFormat;
 import java.util.Date;
 
 public class MethodChecker {
     public Object check(ProceedingJoinPoint pjp) throws Throwable {
         try {
             long startTime = new Date().getTime();//得到方法执行前的时间
             Object ret = pjp.proceed();//执行目标方法;返回值是目标方法的返回值;
             long endTime = new Date().getTime();//得到方法执行后的时间;
             long duration = endTime - startTime;//获取方法执行时间;
             if (duration = 1000) { //如果方法执行时间大于等于1000ms;
                 String className = pjp.getTarget().getClass().getName();
                 String methodName = pjp.getSignature().getName();
                 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String now = sdf.format(new Date());
System.out.println("=====" + now + ":" + className + "."
 
+ methodName + "(" + duration + "ms)=====");
  }
  return ret;
  } catch (Throwable throwable) {
  System.out.println("Exception message:" +  throwable.getMessage());
  throw throwable;
  }
  }
  }

说明:

(1) 环绕通知的切面方法,需要返回一个Object类型的对象;

(2) 【ProceedingJoinPoint】:是一个特殊的连接点;ProceedingJoinPoint是JoinPoint的升级版;JointPoint有的功能,ProceedingJoinPoint都有,而且ProceedingJoinPoint还新增了【控制目标方法是否执行】的功能;

(3)【pjp.proceed();】:proceed()方法是ProceedingJoinPoint的核心方法;这个方法的作用是执行目标方法,如果不写这条语句,那么目标方法将不会执行,即这条语句就是控制目标方法是否执行的核心所在;(其实,能感觉到这背后的支撑是IoC容器)

(4) 程序结构

(5) 可以看到【环绕通知】可以完成【前置通知、后置通知、返回后通知、异常通知】这四种通知的所有工作;如下图;这也印证了【环绕通知】是最强大的一个通知;

(2)配置applicationContext.xml:配置究竟在【哪些类的哪些方法上】应用【MethodChecker切面类】;

<?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: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
         http://www.springframework.org/schema/aop/spring-aop.xsd">
 
     <bean id="userDao" class="com.imooc.spring.aop.dao.UserDao"/>
     <bean id="employeeDao"
class="com.imooc.spring.aop.dao.EmployeeDao"/>
     <bean id="userService"
class="com.imooc.spring.aop.service.UserService">
         <property name="userDao" ref="userDao"/>
     </bean>
     <bean id="employeeService"
class="com.imooc.spring.aop.service.EmployeeService">
         <property name="employeeDao" ref="employeeDao"/>
     </bean>
 
     <bean id="methodChecker"
class="com.imooc.spring.aop.aspect.MethodChecker"/>
     <aop:config>
         <aop:pointcut id="pointcut" expression="execution(*
com.imooc..*.*(..))"/>
         <aop:aspect ref="methodChecker">
             <aop:around method="check" pointcut-ref="pointcut"/>
         </aop:aspect>
     </aop:config>
 
 </beans>

说明:

(1) 配置说明:【环绕通知】的配置,和,【前置通知、后置通知、返回后通知、异常通知】的配置,基本类似;

(3)创建SpringApplication入口类,去测试;

package com.imooc.spring.aop;
 
 import com.imooc.spring.aop.service.UserService;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringApplication {
     public static void main(String[] args) {
         ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
UserService userService = context.getBean("userService",
UserService.class);
userService.createUser();
}
 }

说明:

(1) 为了能够更好应用切面类中切面方法,让目标方法creatUser()方法所在线程休眠3秒;

(2) 这儿我只调用了creatUser()方法;(调用那么多方法干嘛,能演示环绕通知就行了)

(3) 运行效果

基于注解开发Spring AOP

说明:

(1) 本篇博客内容:利用注解配置Spring AOP;(然后,本篇博客中的IoC也采用了注解方式);

(2) 前面使用xml的方式配置Spring AOP,其配置过程还是比较麻烦的;各种bean,<aop:config都需要在applicationContext.xml中配置,开发体验比较差;为此,Spring提供了一系列注解,来简化Spring AOP的配置;

(3) 【利用注解配置Spring AOP】和【利用XML配置Spring AOP】的本质是一样的,只是外在的表现形式不一样而已;

(4) 然后,通过本博客,能够感受到【利用注解配置Spring AOP】要方便很多;

一:准备一个工程,演示用

为了演示,导入示例工程s03(这个工程在WorkSpace的aop目录下):

其中预置了readme.md,EmployeeDao,UserDao,EmployeeService,EMployeeDao;

(1)readme.md文档:

注解方式配置Sring AOP

(2)EmployeeDao类:

package com.imooc.spring.aop.dao;
 
 import org.springframework.stereotype.Repository;
 
 /**
  * 员工表Dao
  */
 @Repository
 public class EmployeeDao {
     public void insert(){
         System.out.println("新增员工数据");
     }
 }

说明:

(1) 这个类模拟了,操作数据中的Employee员工表; 其中添加了一个示意性的方法,向Employee员工表插入一条数据;

(2) 这个项目的IoC部分,也采用了注解方式;

(3)UserDao类:

package com.imooc.spring.aop.dao;
 
 import org.springframework.stereotype.Repository;
 
 /**
  * 用户表Dao
  */
 @Repository
 public class UserDao {
     public void insert(){
         System.out.println("新增用户数据");
     }
 }

说明:

(1) 这个类模拟了,操作数据中User用户表; 其中添加了一个示意性的方法,向User用户表插入一条数据;

(2) 这个项目的IoC部分,也采用了注解方式;

(4)EmployeeService类:

package com.imooc.spring.aop.service;
 
 import com.imooc.spring.aop.dao.EmployeeDao;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
 
 /**
  * 员工服务
  */
 @Service
 public class EmployeeService {
     @Resource
     private EmployeeDao employeeDao;
     public void entry(){
         System.out.println("执行员工入职业务逻辑");
         employeeDao.insert();
     }
 
     public EmployeeDao getEmployeeDao() {
         return employeeDao;
     }
 
     public void setEmployeeDao(EmployeeDao employeeDao) {
         this.employeeDao = employeeDao;
     }
 }

说明:

(1) EmployeeService主要是处理员工入职的逻辑,其中会调用EmployeeDao中的方法;

(2) 然后,其中的EmployeeDao属性,也生成了get和set方法;

(3) 这个项目的IoC部分,也采用了注解方式;

(5)UserService类:

package com.imooc.spring.aop.service;
 
 import com.imooc.spring.aop.dao.UserDao;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
 
 /**
  * 用户服务
  */
 @Service
 public class UserService {
     @Resource
     private UserDao userDao;
 
     public void createUser(){
         try {
             Thread.sleep(3000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println("执行员工入职业务逻辑");
         userDao.insert();
     }
 
     public String generateRandomPassword(String type , Integer length){
         System.out.println("按" + type + "方式生成"+ length  + "位随机密码");
         return "Zxcquei1";
     }
 
     public UserDao getUserDao() {
         return userDao;
     }
 
     public void setUserDao(UserDao userDao) {
         this.userDao = userDao;
     }
 }

说明:

(1) 代码分析;其中的createUser()方法所在线程睡眠了3秒钟,以模拟实际开发中方法运行慢的问题;

(2) 然后,其中的UserDao属性,也生成了get和set方法;

(3) 这个项目的IoC部分,也采用了注解方式;


二:正式编码(本文核心~~)

(1)pom.xml:引入所需依赖:【spring-context模块】,【aspectjweaver模块】

<?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.imooc.spring</groupId>
     <artifactId>aop</artifactId>
     <version>1.0-SNAPSHOT</version>
     <repositories>
         <repository>
             <id>aliyun</id>
             <name>aliyun</name>
             <url>https://maven.aliyun.com/repository/public</url>
         </repository>
     </repositories>
 
     <dependencies>
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-context</artifactId>
             <version>5.2.13.RELEASE</version>
         </dependency>
         <dependency>
             <groupId>org.aspectj</groupId>
             <artifactId>aspectjweaver</artifactId>
             <version>1.9.5</version>
         </dependency>
     </dependencies>
 </project>

**(2)创建applicationContext.xml配置文件

引入【默认命名空间】,【context命名空间】,【aop命名空间】;【IoC也采用注解方式啦, <ontext:component-scan开启组件扫描 】;【 <aop:aspectj-autoproxy/启用Spring AOP的注解模式 】;

<?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: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
             http://www.springframework.org/schema/aop/spring-aop.xsd">
         <context:component-scan base-package="com.imooc">          </context:component-scan>
         <aop:aspectj-autoproxy/>
 
     </beans>

说明:

(1) <ontext:component-scan意思是组件扫描,其作用是:在IoC容器初始化时,自动扫描【@Repository】、【@Service】、【@Controller】、【@Component】这四种【组件类型】注解,并完成实例化;

(2) <aop:aspectj-autoproxy/意思是启用Spring AOP的注解模式,当添加上这条配置后,就不用再在applicationContext.xml中进行任何额外的配置了;

(3)创建容纳切面类的包:aspect包;添加针对方法的切面类:MethodChecker类;

package com.imooc.spring.aop.aspect;
 
 import org.aspectj.lang.ProceedingJoinPoint;
 import org.aspectj.lang.annotation.Around;
 import org.aspectj.lang.annotation.Aspect;
 import org.springframework.stereotype.Component;
 
 import java.text.SimpleDateFormat;
 import java.util.Date;
 @Component
 @Aspect
 public class MethodChecker {
     @Around("execution(* com.imooc..*Service.*(..))")
     public Object check(ProceedingJoinPoint pjp) throws Throwable {
         try {
             long startTime = new Date().getTime();//得到方法执行前的时间
             Object ret = pjp.proceed();//执行目标方法;返回值是目标方法的返回值;
             long endTime = new Date().getTime();//得到方法执行后的时间;
             long duration = endTime - startTime;//获取方法执行时间;
             if (duration = 1000) { //如果方法执行时间大于等于1000ms;
                 String className = pjp.getTarget().getClass().getName();
                 String methodName = pjp.getSignature().getName();
                 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String now = sdf.format(new Date());
System.out.println("=====" + now + ":" + className + "."+ methodName + "(" + duration + "ms)=====");
  }
  return ret;
  } catch (Throwable throwable) {
  System.out.println("Exception message:" + throwable.getMessage());
  throw throwable;
  }
  }
  }

说明:

(1) 这个切面类中的切面方法的逻辑和【Around Advice环绕通知】中的MethodChecker类基本一致;

(2) 切面类和切面方法,使用的注解分析;

(4)创建SpringApplication入口类:初始化IoC容器,并测试;

package com.imooc.spring.aop;
 
 import com.imooc.spring.aop.service.UserService;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringApplication {
     public static void main(String[] args) {
         ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
UserService userService = context.getBean("userService",
UserService.class);
userService.createUser();
}
}

说明:

(1) SpringApplication类的内容很简单,没什么好说的;

(2) 运行结果;

代理模式与静态代理

说明:

(0)本篇博客重在理解代理模式的基本内容,不要和Spring AOP过分混淆;【Spring AOP】是一个高度封装的组件,只是其底层有代理模式作为支撑而已;

(1) 本篇博客内容有如下几点:

● 要知道【Spring AOP】底层是基于代理模式来实现的;

● 了解代理模式的基本内容;

● 本篇博客的代码演示的静态代理模式;但,静态代理是基础,这些代码有助于理解代理模式;

● 静态代理,在实际中使用的不多;更多的是使用动态代理;

(2) 本篇博客内容,篇幅有点多,但其核心知识很少啦~~;

(3) 【Spring AOP】底层就是基于代理模式来实现的,即【Spring AOP】可以看成是基于代理模式的封装(自然,Spring AOP中不止有代理模式,还有很多其他内容);理解代理模式的底层原理,有较大必要;所以,自本篇博客始,我们就暂时搁置【Spring AOP】这个给力的封装,去探寻其底层的代理模式;

一:Spring AOP底层实现原理简介:底层基于代理模式来实现;

了解【Spring AOP底层的实现原理】有较大必要性;

(1) Spring是基于【设计模式中的代理模式】实现功能动态扩展;

(2) 对于Spring AOP来说,其包含两种情况:

● 如果目标类实现了接口,那么【Spring AOP】底层是通过【JDK动态代理】来实现功能的扩展;

● 如果目标类没有实现了接口,那么【Spring AOP】底层是通过【CGLib这个第三方组件】来实现功能的扩展;


二:代理模式简介;

说明:

(1)实际案例阐述:

● 比如【小刘】要去其他城市,需要租房子,那么【客户】就可以通过【房产中介】来完成这个需求;

●同时,【房东】也需要把房子租出去,由于【房东】有自己的工作,其不可能整天把精力投入到出租房子这件事上,所以【房东】也可以让【房产中介】帮其对外出租房子。

●对于这种情况,【房产中介】实现了【房东】的功能;同时,在出租房子这件事上,【房产中介】肯定比【房东】更专业,可以做一些【房东】没有做的事,即可以认为【房产中介】对【房东】进行了功能扩展;

● 上面的【房产中介】就是一个典型的代理人。在程序中,这就称之为代理模式;

(2)代理模式专业阐述;

● 在上图中:【代理类】=【房产中介】;【委托类】=【房东】;【客户类】=【小刘】;

● 【代理类】和【委托类】有着同样的目的;所以,其实现了相同的接口,然后这个接口中定义了一个租房的方法,【代理类】和【委托类】都去实现租房的逻辑;

●【代理类】内部持有了【委托类】的对象,所以在【代理类】被实例化后,代理对象执行的过程中,可以对【委托类】中原始的逻辑增加额外的行为;(比如,上面中介在和小刘定好房子后,中介除了交付给房东指定的租金,中介还有向我收取一个月的代理费,这个中介向我收取中介费就是上面所谓的额外的行为)

● 【客户类】则是通过【代理类】完成所需的功能;


三:演示代理模式( 这儿通过代理模式中最简单的静态代理,来介绍

(1)创建一个项目:s04;

(2)项目初始情况;

UserService接口:

package com.imooc.spring.aop.service;
 
 public interface UserService {
     public void createUser();
 }

UserServiceImpl实现类:

package com.imooc.spring.aop.service;
 
 public class UserServiceImpl implements UserService{
     public void createUser() {
         System.out.println("userServiceImpl类执行createUser()方法;");
     }
 }

Application入口类:

package com.imooc.spring.aop;
 
 import com.imooc.spring.aop.service.UserService;
 import com.imooc.spring.aop.service.UserServiceImpl;
 
 public class Application {
     public static void main(String[] args) {
         UserService userService = new UserServiceImpl();
         userService.createUser();
     }
 }

入口类运行结果:

(3)需求来了:需要新增一个功能;

上面的过程很简单,没什么好说的;

但是,如果此时提出新的需求,将createUser()方法执行的时间打印出来;

这个需求,在前面Spring AOP中是通过增加切面类来完成的,自然Spring AOP底层就是基于代理模式来实现的;那么,如果我们不借助Spring AOP这个已经封装了代理模式的“API”,而是直接通过底层的代理模式要如何做?

通过代理模式,如果要进行功能的扩展,就必须基于UserService接口,创建代理类;同时在代理类中,持有对应的具体实现;

(4)使用代理模式:实现功能的扩展;

UserServiceProxy代理类:

package com.imooc.spring.aop.service;
 
 import java.text.SimpleDateFormat;
 import java.util.Date;
 
 public class UserServiceProxy implements UserService{
     //持有委托类对象
     private UserService userService;
     public UserServiceProxy(UserService userService) {
         //通过外部传入的【UserService接口的实现类的对象】赋值给【委托类的UserService属性】
         this.userService = userService;
     }
 
     public void createUser() {
         System.out.println("=====" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(new Date()) + "=====");
userService.createUser();
}
}

说明:

(1) 【UserServiceProxy代理类】也要实现【UserServiceImpl实现的UserService接口】;

(2) 【代理类】内部持有了【委托类】的对象;

(3) 【代理类】实现对【委托类】功能的扩展;


Application入口类:需要修改一下

package com.imooc.spring.aop;
 
 import com.imooc.spring.aop.service.UserService;
 import com.imooc.spring.aop.service.UserServiceImpl;
 import com.imooc.spring.aop.service.UserServiceProxy;
 
 public class Application {
     public static void main(String[] args) {
         UserService userService = new UserServiceProxy(new UserServiceImpl());
userService.createUser();
}
}

说明:

(1) Application类可以认为是客户类;然后,客户类是通过UserServiceProxy代理类来实现createUser()功能的;然后,UserServiceProxy代理类包含了UserServiceImpl委托类;

(2)运行结果

疑问?:代理类的有参构造的参数,为什么是接口类型,而不能是具体实现类类型?:这是因为代理模式,是可以嵌套使用的;

(5)代理模式嵌套使用案例;

案例:比如【某租户】从【房东】那儿把房子整租下来,然后【某租户】再把房间出租给其他租客;这就是所谓的二房东;代理模式中也支持“二房东”,因为委托类和代理类都实现了相同的接口;然后,代理类中传入的参数类型是接口;

UserServiceProxy1类:

package com.imooc.spring.aop.service;
 
 import java.text.SimpleDateFormat;
 import java.util.Date;
 
 public class UserServiceProxy1 implements UserService{
     //持有委托类对象
     private UserService userService;
     public UserServiceProxy1(UserService userService) {
         //通过外部传入的【UserService接口的实现类的对象】赋值给【委托类的UserService属性】
         this.userService = userService;
     }
 
     public void createUser() {
         userService.createUser();
         System.out.println("=====后置扩展功能=====");
 
     }
 }

说明:

(1) 此时,就有了两个代理类:这两个代理类的作用都是,增强UserServiceImpl委托类的功能; ● UserServiceProxy代理类在UserServiceImpl委托类的createUser()方法执行前增加功能;

● UserServiceProxy1代理类在UserServiceImpl委托类的createUser()方法执行后增加功能;

(2) 在这个具体案例中,【租客】需要面对【二房东】,所以【客户类】是面向【UserServiceProxy1】这个代理类的;


Application入口类:需要修改一下

package com.imooc.spring.aop;
 
 import com.imooc.spring.aop.service.UserService;
 import com.imooc.spring.aop.service.UserServiceImpl;
 import com.imooc.spring.aop.service.UserServiceProxy;
 import com.imooc.spring.aop.service.UserServiceProxy1;
 
 public class Application {
     public static void main(String[] args) {
         UserService userService = new UserServiceProxy1(new UserServiceProxy(new UserServiceImpl()));
userService.createUser();
}
}

说明:

(1) 这样做的基础是,UserServiceImpl委托类,UserServiceProxy代理类,UserServiceProxy1代理类,都实现了UserService接口;

(2) 上面的逻辑,其实稍微看下代码,不难理解;其就是,UserServiceProxy1,UserServiceProxy,UserServiceImpl是一层一层的调用关系;

(3) 运行结果;

(4) 可以看到,我们可以增加N个代理类,以实现对原始UserServiceImpl委托类功能的无限层次的扩展;


四:静态代理缺点分析;

(1) 上面每进行一次功能的扩展,都要自己手动去编写一个新的代理类;对于这种,需要手动创建代理类的方式,称之为静态代理;静态代理是最简单使用方式,同时静态代理也是最麻烦的使用方式;

(2.1) 通过前面Spring AOP的内容可知,一个切面需要有PointCut划定作用范围,然后切面类就会对范围内的目标类进行功能的扩展;很多情况下范围内的目标类是有成百上千个的;

(2.2) 转到代理模式这儿,Spring AOP中介绍的【被圈定的目标类】就相当于本篇博客中的【委托类】;可知,每一个委托类,都有至少一个为代理类,而这个代理类是需要自己按规则去手动编写的;如果,工程中有成百上千个具体的实现类(也就是委托类,也就是AOP中被圈定的类啦),那么就意味着也要手动编写成百上千个具体的代理类(来为业务实现类提供功能拓展职责),而这会让系统变得十分臃肿;

(3) 自动创建代理类:JDK1.2以后,由于引入了反射机制,为自动创建代理类提供了可能,这也就是下篇博客要介绍的动态代理;

JDK实现动态代理(目标类实现接口时)

说明:

(0)本篇博客重在理解代理模式的基本内容,不要和Spring AOP过分混淆;【Spring AOP】是一个高度封装的组件,只是其底层有代理模式作为支撑而已; 那么,至于其具体的封装细节,至少目前暂没必要深究。

(1) 静态代理和动态代理的底层原理是一样的;

(2) 本篇博客的ProxyInvocationHandler类,不是代理类,其可以看成是一个Utils类;(理解这一点非常重要)

反射在 Spring 中的应用举例

反射在众多框架中都有普遍的应用。比如 Spring IOC 容器帮我们实例化众多的bean,下面我们简单模拟一下 反射 在其中起到的作用。

Spring 配置文件:

<bean id="pony" class="com.xblzer.dp.proxy.springaop.Pony"></bean>

使用的时候直接这样就能拿到定义的类了:

ApplicationContext ctx = new ClassPathXmlApplicationContext("app_aop.xml");
Pony pony = (Pony) ctx.getBean("pony");

那么是怎么做到的呢?就是通过 反射 。 Spring 通过配置文件实例化对象,并将其放到容器的过程大概就是(模拟):

//伪代码
//1.解析<bean .../>元素的id属性得到该字符串值为“pony”
String idStr = "pony";
//解析<bean .../>元素的class属性得到该字符串值为“com.xblzer.dp.proxy.springaop.Pony”
String classStr = "com.xblzer.dp.proxy.springaop.Pony";
//利用反射机制,通过classStr获取Class类对象
Class<?> cls = Class.forName(classStr);
//实例化对象
Object obj = cls.newInstance();
//放到Spring容器
Map<String, Object> container = new HashMap<>();
container.put(idStr, obj);

为啥不new而是使用bean呢?

java具有一次编译,到处运行,使用java反射(编译后的class文件)可以随时随地生成bean,可以在项目到处运行,而不必主动去new

零:动态代理引入(相当于是动态代理的合理性解释);

(1)为什么需要动态代理: 在【静态代理代码演示】中介绍了静态代理;静态代理需要手写代理类,使用起来很不方便;本篇博客介绍动态代理;

(2)反射为动态代理提供了可能: 在JDK1.2以后,引入了反射机制,基于反射机制可以【根据要实现的接口,按照接口的结构,自动的去生成的相应的代理类,以完成目标方法的扩展工作】;在这个过程中,由于代理类是通过反射,自动在运行时生成的,所以称之为动态代理;

(3)动态代理最大的好处就是不用手写代理类: 静态代理需要我们手写代理类;动态代理不需要我们手写代理类,代理类是在程序运行时自动基于反射机制生成的;

(4)情况告知:对于Spring AOP来说,其底层的代理模式包含两种情况:

● 如果目标类实现了接口,那么【Spring AOP】底层是通过【JDK动态代理】来实现功能的扩展;

● 如果目标类没有实现了接口,那么【Spring AOP】底层是通过【CGLib这个第三方组件】来实现功能的扩展;

(5) 实际上动态代理就是将相同的代理扩展功能封装起来(有点像策略模式中,将一种算法封装起来),然后只要有实现类需要这种功能的扩展,就可以直接复用吧。那么,如果成百上千个实现类,每个类所需要的扩展功能都是不一样的,那么就需要为每一个类都写出其需要的扩展功能,这时候动态代理并不比静态代理灵活,这样理解是对的吗?(此段,摘MK网用户【易萧】的回答


一:演示JDK动态代理

(1)创建一个项目:s05;

(2)项目初始情况:UserService接口,UserServiceImpl实现类;

由于实现【JDK动态代理】并不依赖于其他第三方组件,所以无需引入任何依赖;

UserService接口:

package com.imooc.spring.aop.service;
 
 public interface UserService {
     public void createUser();
 }

UserServiceImpl实现类:(这个类就是所谓的【委托类,也就是AOP中所谓的【目标类】)

package com.imooc.spring.aop.service;
 
 public class UserServiceImpl implements UserService{
     public void createUser() {
         System.out.println("userServiceImpl类执行createUser()方法;");
     }
 }

(3)基于【JDK动态代理】,来实现UserServiceImpl的功能扩展:创建 ProxyInvocationHandler功能扩展类;(核心内容)

首先,要创建一个ProxyInvocationHandler类,这个类不是代理类,这个类仅仅是一个实现了InvocationHandler接口(该接口是JDK提供的反射接口,用于在JDK动态代理中对目标方法进行增强)的类。

package com.imooc.spring.aop.service;
 
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 
 public class ProxyInvocationHandler implements InvocationHandler {
     private Object target;//目标对象
     private ProxyInvocationHandler (Object target) {
         this.target = target;
     }
     /**
      *
      * @param proxy:代理类对象;
      * @param method:目标方法对象;
      * @param args:目标方法参数;
      * @return :目标方法运行后的返回值;
      * @throws Throwable:目标方法抛出的异常;
      */
     public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 打印当前系统时间;这个就是在目标方法执行前,要执行的内容;即,这个就是对目标方法的增强;
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(new Date()));
Object ret = method.invoke(target, args);//调用目标对象的目标方法;
return ret;
}
}

说明:

(1) ProxyInvocationHandler类,需要实现InvocationHandler接口;

● InvocationHandler接口是JDK提供的反射接口,用于在JDK动态代理中对目标方法进行增强;

● 从实用角度来看,【InvocationHandler接口的实现类】与【Spring AOP中的切面类的环绕通知】比较类似;

(2)【InvocationHandler接口的实现类,这儿就是ProxyInvocationHandler啦】必须要实现invoke()方法;

● invoke()方法,就是对目标方法进行增强的;

● invoke()方法内部,需要调用method对象的invoke方法,这其实就是反射的内容;(然后,前面我们知道,Spring AOP底层基于代理模式,动态代理模式底层基于反射。这儿就印证喽;)

●在【反射的核心类:Method方法类】中介绍反射类的时候,接触过Method类中的invoke方法;如有需要,可以快速参考;

(3) ProxyInvocationHandler类的作用是【扩展目标类对象的功能】;那么下图就是介绍,ProxyInvocationHandler类所谓的“扩展”到底是如何做到的;

(4) 至此,我们已经编写好了ProxyInvocationHandler类;即,我们已经写好了【一个,方法的扩展】;(自然,这个扩展是针对将来的目标类对象的目标方法的)。那么,我们如何让这个ProxyInvocationHandler类运行和生效?

(3.1)如何让这个ProxyInvocationHandler类运行和生效:策略1:直接在ProxyInvocationHandler类内编写;

package com.imooc.spring.aop.service;
 
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 
 public class ProxyInvocationHandler implements InvocationHandler {
     private Object target;//目标对象
     private ProxyInvocationHandler(Object target) {
         this.target = target;
     }
     /**
      * @param proxy:代理类对象;
      * @param method:目标方法对象;
      * @param args:目标方法参数;
      * @return :目标方法运行后的返回值;
      * @throws Throwable:目标方法抛出的异常;
      */
     public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 打印当前系统时间;这个就是在目标方法执行前,要执行的内容;即,这个就是对目标方法的增强;
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(new Date()));
Object ret = method.invoke(target, args);//调用目标对象的目标方法;
return ret;
}
public static void main(String[] args) {
         UserService userService = new UserServiceImpl();
         ProxyInvocationHandler invocationHandler = new ProxyInvocationHandler(userService);
UserService userServiceProxy = (UserService)
Proxy.newProxyInstance(userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
invocationHandler);
userServiceProxy.createUser();
}
}

说明:

(1) 直接在ProxyInvocationHandler类内,编写代码;此时,Object target目标类对象属性,可以是private的;

(2) 调用说明;

(3) Proxy类简述;

运行结果:

(3.2)如何让这个ProxyInvocationHandler类运行和生效:策略2:在一个新的类中,编写;

Application类:

package com.imooc.spring.aop;
 
 import com.imooc.spring.aop.service.ProxyInvocationHandler;
 import com.imooc.spring.aop.service.UserService;
 import com.imooc.spring.aop.service.UserServiceImpl;
 
 import java.lang.reflect.Proxy;
 
 public class Application {
     public static void main(String[] args) {
         UserService userService = new UserServiceImpl();
         ProxyInvocationHandler invocationHandler = new ProxyInvocationHandler(userService);
UserService userServiceProxy = (UserService)
Proxy.newProxyInstance(userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
invocationHandler);
userServiceProxy.createUser1();
}
 }

说明:

(1) 在其他类类内,编写代码;此时,Object target目标类对象属性,需要是public的;否则,会报错啦;

(2) 调用代码是一样的,没必要重复的了;

(4.1)ProxyInvocationHandler可以看成是一个Utils类:ProxyInvocationHandler不是代理类,其只是定义了一个扩展功能,而这个扩展功能【可以扩展某类对象中的,某个方法的功能】;(进一步补充和说明的,内容)

如图:

(4.2)ProxyInvocationHandler可以看成是一个Utils类:ProxyInvocationHandler不是代理类,其只是定义了一个扩展功能,而这个扩展功能【可以扩展某类对象中的,某个方法的功能】;(进一步补充和说明的,内容)


三:注意事项:JDK动态代理,必须要实现接口才可以;即目标类必须是一个实现了接口的类;

(1) JDK动态代理,必须要实现接口才可以;即目标类必须是一个实现了接口的类;如果目标类没有实现接口,那么反射过程必然会报错;

(2) 但是,在实际开发中,大量的类并没有实现接口;如果这样的类作为目标类,又想使用代理模式去扩展功能的话;Spring提供了另一种解决方法:利用第三方组件CGLib,实现第目标类的扩展;

CGLib组件实现功能扩展(目标类没有实现接口时)

说明:

(1) 无论【JDK动态代理】还是【CGLib】;其脉络如下:

● 【Spring AOP】通过【代理模式】这种设计理念,实现【扩展目标类目标方法】的目的;

● 而,【Spring AOP】实现【代理模式】的方式有两种,分别就是【JDK动态代理】和【CGLib】;

【Spring AOP】的使用,【JDK动态代理】及【CGLib】的使用 ,表面上存在使用上的差异;但是,不要因此迷惑; 【JDK动态代理】及【CGLib】的使用 是底层原理, 【Spring AOP】的使用 是上层的封装;底层原理是底层原理,上层封装是上层封装,在表面使用上存在差异很自然和正常,这个要拎得清。

(2) 自然,之所以要写(1),更多是因为目前对这些内容并不是十分熟悉,所以故意强调一些;相信随着以后的熟练和进一步理解后,这些内容会理解的更透彻;


零:CGLib引入(相当于是CGLib的合理性解释)

(1) JDK动态代理,必须要实现接口才可以;即目标类必须是一个实现了接口的类;如果目标类没有实现接口,那么反射过程必然会报错;

(2) 但是,在实际开发中,大量的类并没有实现接口;如果这样的类作为目标类,又想使用代理模式去扩展功能的话;Spring提供了另一种解决方法:利用第三方组件CGLib,实现第目标类的功能扩展;


一:CGLib技术简介

(1) CGLib(Code Generation Library);

(2) 【Spring AOP】扩展那些没有实现接口的类时,底层使用CGLib技术;

(3) 【CGLIb技术】是通过生成目标类的子类的方式,来实现功能的增强的。如下图所示:


二:CGLib代码演示

以下代码基于【基于注解开发Spring AOP】中的s03;

(1)目标类没有实现接口时:【Spring AOP】底层使用CGLib来达到【扩展目标类功能的目的】;

即,s03这个案例是【基于注解开发Spring AOP】,不过这不是重点,重点是其中的目标类UserService并没有实现接口;那么【Spring AOP】底层使用CGLib技术,通过生成目标类的子类的方式,来实现功能的增强;如下图:

(2)目标类实现接口时:【Spring AOP】底层使用JDK动态代理来达到【扩展目标类功能的目的】