3 - Spring AOP
1-Spring AOP简介
1-1-什么是AOP
AOP的全称是Aspect-Oriented Programming,即面向切面编程(也称面向方面编程)。它是面向对象编程(OOP)的一种补充,目前已成为一种比较成熟的编程方式。
AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方。
类与切面的关系
1-2-AOP术语
Proxy(代理):将通知应用到目标对象之后,被动态创建的对象。
Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程。
2-动态代理
2-1-JDK动态代理
JDK动态代理是通过java.lang.reflect.Proxy 类来实现的,我们可以调用Proxy类的newProxyInstance()方法来创建代理对象。对于使用业务接口的类,Spring默认会使用JDK动态代理来实现AOP。
2-2-案例演示
2-2-1-创建web项目
2-2-2-创建包、接口、实现类
package com.itheima.jdk;
public interface UserDao {
void addUser();
void deleteUser();
}
package com.itheima.jdk;
public class UserDaoImpl implements UserDao{
@Override
public void addUser() {
System.out.println("添加用户");
}
@Override
public void deleteUser() {
System.out.println("删除用户");
}
}
2-2-3-创建切面类
package com.itheima.aspect;
public class MyAspect {
public void check_permissions() {
System.out.println("模拟检查权限。。。");
}
public void log() {
System.out.println("模拟检查日志。。");
}
}
2-2-4-创建处理调用的类
public class JdkInvocationHandler implements InvocationHandler{
//声明目标类接口
private UserDao userDao;
public Object createProxy(UserDao userDao) {
this.userDao=userDao;
ClassLoader classLoader = JdkInvocationHandler.class.getClassLoader();
Class[] clazz = userDao.getClass().getInterfaces();
return Proxy.newProxyInstance(classLoader, clazz, this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MyAspect myAspect = new MyAspect();
myAspect.check_permissions();
Object object = method.invoke(userDao, null);
myAspect.log();
return object;
}
}
2-2-5-创建测试类
public class JdkTest {
public static void main(String[] args) {
JdkInvocationHandler handler = new JdkInvocationHandler();
//创建目标对象
UserDao userDao = new UserDaoImpl();
//创建代理对象
UserDao proxyUserDao = (UserDao) handler.createProxy(userDao);
proxyUserDao.addUser();
proxyUserDao.deleteUser();
}
}
2-2-6-分析一下JDK生成动态的代理类
可以参考下面的链接
https://blog.csdn.net/jupiter_888/article/details/103852693
https://www.cnblogs.com/lonecloud/p/9158668.html
https://www.jb51.net/article/135597.htm
修改测试类,设置保存Java动态代理生成的类文件
public class JdkTest {
public static void main(String[] args) throws Exception {
// 设置保存Java动态代理生成的类文件
saveGeneratedJdkProxyFiles();
JdkInvocationHandler handler = new JdkInvocationHandler();
//创建目标对象
UserDao userDao = new UserDaoImpl();
//创建代理对象
UserDao proxyUserDao = (UserDao) handler.createProxy(userDao);
proxyUserDao.addUser();
proxyUserDao.deleteUser();
}
/**
* 设置保存Java动态代理生成的类文件。
*
* @throws Exception
*/
public static void saveGeneratedJdkProxyFiles() throws Exception {
Field field = System.class.getDeclaredField("props");
field.setAccessible(true);
Properties props = (Properties) field.get(null);
props.put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
}
}
反编译代理类,其位置在你项目源代码下,如下图
核心代码
public final class $Proxy0 extends Proxy
implements UserDao
{
private static Method m1;
private static Method m2;
private static Method m0;
private static Method m3;
private static Method m4;
public $Proxy0(InvocationHandler invocationhandler)
{
super(invocationhandler);
}
public final void addUser()
{
try
{
super.h.invoke(this, m3, null);
return;
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
Class.forName("java.lang.Object")
});
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m3 = Class.forName("com.itheima.jdk.UserDao").getMethod("addUser", new Class[0]);
m4 = Class.forName("com.itheima.jdk.UserDao").getMethod("deleteUser", new Class[0]);
}
2-2-7-测试类运行过程图
2-3-CGLIB代理
CGLIB(Code Generation Library)是一个高性能开源的代码生成包,它采用非常底层的字节码技术,对指定的目标类生成一个子类,并对子类进行增强。
2-4-案例演示
2-4-1-添加spring核心包、创建包、目标类
package com.itheima.cglib;
public class UserDao {
public void addUser() {
System.out.println("添加用户");
}
public void deleteUser() {
System.out.println("删除用户");
}
}
2-4-2-创建方法拦截器
//创建方法拦截器
public class CglibMethodInterceptor implements MethodInterceptor {
//创建代理对象
public Object createProxy(Object target) {
//创建字节码增强器
Enhancer enhancer = new Enhancer();
//确定需要增强的类,作为增强器的父类
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
//返回创建的代理类
return enhancer.create();
}
/**
* proxy 代理对象
* method 拦截的方法
* args 拦截方法参数数组
* methodProxy 方法的代理对象,用于执行父类的方法
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
MyAspect myAspect = new MyAspect();
myAspect.check_permissions();
Object object = methodProxy.invokeSuper(proxy, args);
myAspect.log();
return object;
}
}
2-4-3-创建测试类
public class CglibTest {
public static void main(String[] args) {
CglibMethodInterceptor interceptor = new CglibMethodInterceptor();
UserDao userDao = new UserDao();
UserDao userDao1 = (UserDao) interceptor.createProxy(userDao);
userDao1.addUser();
userDao1.deleteUser();
}
}
3-基于代理类的AOP实现
3-1- Spring的通知类型
org.springframework.aop.MethodBeforeAdvice(前置通知)
在目标方法执行前实施增强,可以应用于权限管理等功能。
org.springframework.aop.AfterReturningAdvice(后置通知)
在目标方法执行后实施增强,可以应用于关闭流、上传文件、删除
临时文件等功能。
org.aopalliance.intercept.MethodInterceptor(环绕通知)
在目标方法执行前后实施增强,可以应用于日志、事务管理等功能。
org.springframework.aop.ThrowsAdvice(异常抛出通知)
在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能。
org.springframework.aop.IntroductionInterceptor(引介通知)
在目标类中添加一些新的方法和属性,可以应用于修改老版本程序。
3-2- ProxyFactoryBean
ProxyFactoryBean是FactoryBean接口的实现类,FactoryBean负责实例化一个Bean,而ProxyFactoryBean负责为其他Bean创建代理实例。在Spring中,使用ProxyFactoryBean是创建AOP代理的基本方式。
ProxyFactoryBean类中的常用可配置属性如下:
3-3-环绕通知案例
3-3-1-添加aop相关的jar
3-3-2-创建包、切面类
package com.itheima.factorybean;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyAspect implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
check_permissions();
Object object = mi.proceed();
log();
return object;
}
public void check_permissions() {
System.out.println("模拟检查权限。。。");
}
public void log() {
System.out.println("模拟检查日志。。");
}
}
3-3-3-创建配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
<!-- 1 目标类 -->
<bean id="userDao" class="com.itheima.jdk.UserDaoImpl" />
<!-- 2 切面类 -->
<bean id="myAspect" class="com.itheima.factorybean.MyAspect" />
<!-- 3 使用Spring代理工厂定义一个名称为userDaoProxy的代理对象 -->
<bean id="userDaoProxy"
class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 3.1 指定代理实现的接口-->
<property name="proxyInterfaces"
value="com.itheima.jdk.UserDao" />
<!-- 3.2 指定目标对象 -->
<property name="target" ref="userDao" />
<!-- 3.3 指定切面,织入环绕通知 -->
<property name="interceptorNames" value="myAspect" />
<!-- 3.4 指定代理方式,true:使用cglib,false(默认):使用jdk动态代理 -->
<property name="proxyTargetClass" value="true" />
</bean>
</beans>
3-3-4-创建测试类
// 测试类
public class ProxyFactoryBeanTest {
public static void main(String args[]) {
String xmlPath = "com/itheima/factorybean/applicationContext.xml";
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext(xmlPath);
// 从Spring容器获得内容
UserDao userDao =
(UserDao) applicationContext.getBean("userDaoProxy");
// 执行方法
userDao.addUser();
userDao.deleteUser();
}
}
4-AspectJ开发
AspectJ是一个基于Java语言的AOP框架,它提供了强大的AOP功能。
Spring 2.0以后,Spring AOP引入了对AspectJ的支持,并允许直接使用AspectJ进行编程,而Spring自身的AOP API也尽量与AspectJ保持一致。
新版本的Spring框架,也建议使用AspectJ来开发AOP。
使用AspectJ实现AOP有两种方式:一种是基于XML的声明式AspectJ,另一种是基于注解的声明式AspectJ。
4-1-基于XML的声明式AspectJ
基于XML的声明式AspectJ是指通过XML文件来定义切面、切入点及通知,所有的切面、切入点和通知都必须定义在<aop:config>元素内。
4-1-1-<aop:config>元素及其子元素如下:
4-1-2-XML文件中常用元素的配置方式如下:
<bean id="myAspect" class="com.itheima.aspectj.xml.MyAspect" />
<aop:config>
<aop:aspect id="aspect" ref="myAspect">
<aop:pointcut expression="execution(* com.itheima.jdk.*.*(..))“ id="myPointCut" />
<aop:before method="myBefore" pointcut-ref="myPointCut" />
<aop:after-returning method="myAfterReturning“ pointcut-ref="myPointCut" returning="returnVal" />
<aop:around method="myAround" pointcut-ref="myPointCut" />
<aop:after-throwing method="myAfterThrowing“ pointcut-ref="myPointCut" throwing="e" />
<aop:after method="myAfter" pointcut-ref="myPointCut" />
</aop:aspect>
</aop:config>
4-1-3-配置切面
<aop:aspect>将一个已定义好的Spring Bean转换成切面Bean
4-1-4-配置切入点
当<aop:pointcut>元素作为<aop:config>元素的子元素定义时,表示该切入点是全局切入点,它可被多个切面所共享;
当<aop:pointcut>元素作为<aop:aspect>元素的子元素时,表示该切入点只对当前切面有效。
切入点表达式
execution(* com.itheima.jdk.*.*(..)) 是定义的切入点表达式,该切入点表达式的意思是匹配com.itheima.jdk包中任意类的任意方法的执行。
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
带有问号(?)的部分表示可选配置项,而其他部分属于必须配置项。
modifiers-pattern:定义的目标方法的访问修饰符,如public、private等
ret-type-pattern:定义的目标方法的返回值类型,如void、String等
declaring-type-pattern:定义的目标方法的类路径,如com.itheima.jdk.UserDaoImpl
name-pattern:具体需要被代理的目标方法,如add()方法
param-pattern:需要被代理的目标方法包含的参数
throws-pattern:需要被代理的目标方法抛出的异常类型
4-1-5-配置通知
5种常用通知
4-1-6-案例代码
1、导入AspectJ框架相关的包
spring-aspects-4.3.6.RELEASE.jar:实现包
aspectjweaver-1.8.10.jar:规范包
2、创建包、切面类
package com.itheima.aspectj.xml;
public class MyAspect {
// 前置通知
public void myBefore(JoinPoint joinPoint) {
System.out.print("前置通知 :模拟执行权限检查...,");
System.out.print("目标类是:"+joinPoint.getTarget() );
System.out.println(",被织入增强处理的目标方法为:"
+joinPoint.getSignature().getName());
}
// 后置通知
public void myAfterReturning(JoinPoint joinPoint) {
System.out.print("后置通知:模拟记录日志...," );
System.out.println("被织入增强处理的目标方法为:"
+ joinPoint.getSignature().getName());
}
/**
* 环绕通知
* ProceedingJoinPoint 是JoinPoint子接口,表示可以执行目标方法
* 1.必须是Object类型的返回值
* 2.必须接收一个参数,类型为ProceedingJoinPoint
* 3.必须throws Throwable
*/
public Object myAround(ProceedingJoinPoint proceedingJoinPoint)
throws Throwable {
// 开始
System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
// 执行当前目标方法
Object obj = proceedingJoinPoint.proceed();
// 结束
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...");
return obj;
}
// 异常通知
public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println("异常通知:" + "出错了" + e.getMessage());
}
// 最终通知
public void myAfter() {
System.out.println("最终通知:模拟方法结束后的释放资源...");
}
}
3、创建配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 1 目标类 -->
<bean id="userDao" class="com.itheima.jdk.UserDaoImpl" />
<!-- 2 切面 -->
<bean id="myAspect" class="com.itheima.aspectj.xml.MyAspect" />
<!-- 3 aop编程 -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="myAspect">
<!-- 3.1 配置切入点,通知最后增强哪些方法 -->
<aop:pointcut expression="execution(* com.itheima.jdk.*.*(..))"
id="myPointCut" />
<!-- 3.2 关联通知Advice和切入点pointCut -->
<!-- 3.2.1 前置通知 -->
<aop:before method="myBefore" pointcut-ref="myPointCut" />
<!-- 3.2.2 后置通知,在方法返回之后执行,就可以获得返回值
returning属性:用于设置后置通知的第二个参数的名称,类型是Object -->
<aop:after-returning method="myAfterReturning"
pointcut-ref="myPointCut" returning="returnVal" />
<!-- 3.2.3 环绕通知 -->
<aop:around method="myAround" pointcut-ref="myPointCut" />
<!-- 3.2.4 抛出通知:用于处理程序发生异常-->
<!-- * 注意:如果程序没有异常,将不会执行增强 -->
<!-- * throwing属性:用于设置通知第二个参数的名称,类型Throwable -->
<aop:after-throwing method="myAfterThrowing"
pointcut-ref="myPointCut" throwing="e" />
<!-- 3.2.5 最终通知:无论程序发生任何事情,都将执行 -->
<aop:after method="myAfter" pointcut-ref="myPointCut" />
</aop:aspect>
</aop:config>
</beans>
4、创建测试类
public class TestXmlAspectj {
public static void main(String args[]) {
String xmlPath =
"com/itheima/aspectj/xml/applicationContext.xml";
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext(xmlPath);
// 1 从spring容器获得内容
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
// 2 执行方法
userDao.addUser();
}
}
4-2-基于注解的声明式AspectJ
AspectJ框架为AOP的实现提供了一套注解,用以取代Spring配置文件中为实现AOP功能所配置的臃肿代码。
4-2-1-AspectJ的注解及其描述如下所示:
4-2-2-案例演示
1、创建包、复制切面类并修改
package com.itheima.aspectj.annotation;
@Aspect
@Component
public class MyAspect {
// 定义切入点表达式
@Pointcut("execution(* com.itheima.jdk.*.*(..))")
// 使用一个返回值为void、方法体为空的方法来命名切入点
private void myPointCut(){}
// 前置通知
@Before("myPointCut()")
public void myBefore(JoinPoint joinPoint) {
System.out.print("前置通知 :模拟执行权限检查...,");
System.out.print("目标类是:"+joinPoint.getTarget() );
System.out.println(",被织入增强处理的目标方法为:"
+joinPoint.getSignature().getName());
}
// 后置通知
@AfterReturning(value="myPointCut()")
public void myAfterReturning(JoinPoint joinPoint) {
System.out.print("后置通知:模拟记录日志...," );
System.out.println("被织入增强处理的目标方法为:"
+ joinPoint.getSignature().getName());
}
// 环绕通知
@Around("myPointCut()")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint)
throws Throwable {
// 开始
System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
// 执行当前目标方法
Object obj = proceedingJoinPoint.proceed();
// 结束
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...");
return obj;
}
// 异常通知
@AfterThrowing(value="myPointCut()",throwing="e")
public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println("异常通知:" + "出错了" + e.getMessage());
}
// 最终通知
@After("myPointCut()")
public void myAfter() {
System.out.println("最终通知:模拟方法结束后的释放资源...");
}
}
2、目标类上添加注解
@Repository("userDao")
public class UserDaoImpl implements UserDao{
3、创建配置文件
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 指定需要扫描的包,使注解生效 -->
<context:component-scan base-package="com.itheima" />
<!-- 启动基于注解的声明式AspectJ支持 -->
<aop:aspectj-autoproxy />
</beans>
4、创建测试类
public class TestAnnotationAspectj {
public static void main(String args[]) {
String xmlPath =
"com/itheima/aspectj/annotation/applicationContext.xml";
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext(xmlPath);
// 1 从spring容器获得内容
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
// 2 执行方法
userDao.addUser();
}
}
3 - Spring AOP相关推荐
- Spring AOP + Redis解决重复提交的问题
Spring AOP + Redis解决重复提交的问题 用户在点击操作的时候,可能会连续点击多次,虽然前端可以通过设置按钮的disable的属性来控制按钮不可连续点击,但是如果别人拿到请求进行模拟,依 ...
- 利用Spring AOP与JAVA注解为系统增加日志功能
Spring AOP一直是Spring的一个比较有特色的功能,利用它可以在现有的代码的任何地方,嵌入我们所想的逻辑功能,并且不需要改变我们现有的代码结构. 鉴于此,现在的系统已经完成了所有的功能的开发 ...
- Spring AOP的一些概念
切面(Aspect): 一个关注点的模块化,这个关注点可能会横切多个对象.事务管理是J2EE应用中一个关于横切关注点的很好的例子. 在Spring AOP中,切面可以使用通用类(基于模 ...
- Spring AOP与IOC
Spring AOP实现日志服务 pom.xml需要的jar <dependency><groupId>org.apache.commons</groupId>&l ...
- Spring AOP与IOC以及自定义注解
Spring AOP实现日志服务 pom.xml需要的jar <dependency><groupId>org.apache.commons</groupId>&l ...
- Spring Aop的应用
2019独角兽企业重金招聘Python工程师标准>>> AOP的基本概念 连接点( Jointpoint) : 表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化.方法执行 ...
- Spring AOP详解(转载)所需要的包
上一篇文章中,<Spring Aop详解(转载)>里的代码都可以运行,只是包比较多,中间缺少了几个相应的包,根据报错,几经百度搜索,终于补全了所有包. 截图如下: 在主测试类里面,有人怀疑 ...
- 关于spring aop Advisor排序问题
关于spring aop Advisor排序问题 当我们使用多个Advisor的时候有时候需要排序,这时候可以用注解org.springframework.core.annotation.Order或 ...
- 利用spring aop统一处理异常和打日志
利用spring aop统一处理异常和打日志 spring aop的概念,很早就写博客介绍了,现在在工作中真正使用. 我们很容易写出的代码 我们很容易写出带有很多try catch 和 logger. ...
- 我所理解的Spring AOP的基本概念
Spring AOP中的概念晦涩难懂,读官方文档更是像读天书,看了很多例子后,写一些自己理解的一些spring的概念.要理解面向切面编程,要首先理解代理模式和动态代理模式. 假设一个OA系统中的一个功 ...
最新文章
- 盛大文学推出“一人一书”计划,发布电子书战略
- iOS 获取屏幕最上层window以及响应者
- javascript获取textarea中光标的位置 兼容
- 请大家访问另一个我的博客!
- javascript构造函数继承
- C语言笔记:格式化输入输出(fprintf、fscanf、sscanf...)
- Linux-(C/C++)动态链接库生成以及使用(libxxx.so)
- 各种损失损失函数的使用场景和使用方法:KL散度
- Python界面 PyQT可视化开发(python3+PyQt5+Qt Designer)
- Linux 查看ssh登录日志 ssh登录记录
- HTTP中GET与POST的区别,99 %的人都理解错了
- SIFT算法原理介绍
- centos 查看版本号方法
- Google Cloud Platform
- SV学习(9)——随机函数、数组约束、随机控制
- Oracle调优总结--1(经典实践 重要)
- 如何重新认知性能优化及其度量方法
- Unity Cinemachine之第三人称摄像机CinemachineFreeLook属性详解
- (转)超棒的EXCEL使用技巧
- 计算机没考好的检讨书300百以上,考试反思检讨书300字范文7篇