五种通知类型
说明:
(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动态代理来达到【扩展目标类功能的目的】