Spring AOP知识点简介

文章目录

  • 1、什么是AOP
    • 1.1、AOP术语
    • 1.2、AOP框架
  • 2、动态代理
    • 2.1、JDK动态代理
    • 2.2、CGLIB动态代理
  • 3、基于代理类的AOP实现
    • 3.1、Spring的通知类型
    • 3.2、ProxyFactoryBean
  • 4、AspectJ开发
    • 4.1、基于XML的声明式AspectJ
    • 4.2、基于注解的声明式AspectJ

1、什么是AOP

面向切面编程(Aspect Oriented Programming)提供了另一种角度来思考程序的结构,通过这种方式弥补面向对象编程(Object Oriented Programming)的不足。除了类以外,AOP提供了切面,切面对关注点进行模块化,例如横切多个类型和对象的事务管理(这些关注点术语通常称作横切(crosscutting)关注点)。Spring AOP是Spring的一个重要组件,但是Spring IOC并不依赖于Spring AOP,这意味着你可以自由选择是否使用AOP,AOP提供了强大的中间件解决方案,这使得Spring IOC更加完善。我们可以通过AOP来实现日志监听,事务管理,权限控制等等。
在这里插入图片描述

1.1、AOP术语

  • 切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是Java应用程序中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用通过类(基于模式(XML)的风格)或者在普通类中以@Aspect注解(AspectJ风格)来实现。
  • 连接点(Join point):程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中一个连接点总是代表一个方法的执行。个人理解:AOP拦截到的方法就是一个连接点。通过声明一个org.aspectj.lang.JoinPoint类型参数我们可以在通知(Advice)中获得连接点的信息。这个在稍后会给出案例。
  • 通知(Advice):在切面(Aspect)的某个特定连接点上(Join point)执行的动作。通知的类型包括"around",“before”,"after"等等。通知的类型将在后面进行讨论。许多AOP框架,包括Spring 都是以拦截器作为通知的模型,并维护一个以连接点为中心的拦截器链。总之就是AOP对连接点的处理通过通知来执行。个人理解:Advice指当一个方法被AOP拦截到的时候要执行的代码。
  • 切入点(Pointcut):匹配连接点(Join point)的断言。通知(Advice)跟切入点表达式关联,并在与切入点匹配的任何连接点上面运行。切入点表达式如何跟连接点匹配是AOP的核心,Spring默认使用AspectJ作为切入点语法。个人理解:通过切入点的表达式来确定哪些方法要被AOP拦截,之后这些被拦截的方法会执行相对应的Advice代码。
  • 引入(Introduction):声明额外的方法或字段。Spring AOP允许你向任何被通知(Advice)对象引入一个新的接口(及其实现类)。个人理解:AOP允许在运行时动态的向代理对象实现新的接口来完成一些额外的功能并且不影响现有对象的功能。
  • 目标对象(Target object):被一个或多个切面(Aspect)所通知(Advice)的对象,也称作被通知对象。由于Spring AOP是通过运行时代理实现的,所以这个对象永远是被代理对象。个人理解:所有的对象在AOP中都会生成一个代理类,AOP整个过程都是针对代理类在进行处理。
  • AOP代理(AOP proxy):AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能),在Spring中AOP可以是JDK动态代理或者是CGLIB代理。
  • 织入(Weaving):把切面(aspect)连接到其他的应用程序类型或者对象上,并创建一个被通知对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯AOP框架一样,在运行时完成织入。个人理解:把切面跟对象关联并创建该对象的代理对象的过程。

1.2、AOP框架

  • Spring AOP:Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类织入增强的代码。
  • AspectJ:AspectJ是一个基于Java的AOP框架,从Spring2.0开始,SpringAOP引入了对AspectJ的支持,AspectJ扩展了Java语言,提供了一个专门的编辑器,在编译时提供横向代码的织入。

2、动态代理

Spring中的AOP代理,可以是JDK动态代理,也可以是CGLIB代理。

2.1、JDK动态代理

JDK动态代理是通过java.lang.reflect.Proxy类来实现的,我们可以调用Proxy类的newProxyInstance()方法来创建代理对象。对于使用业务接口的类,Spring默认会使用JDK动态代理来实现AOP。

接下来通过一个案例演示Spring中的JDK动态代理

1.创建Java工程,导入Spring框架需要的JAR包并发布到类路径下,如下图所示:
在这里插入图片描述
2.创建接口,并编写添加和删除方法

package com.nynu.qdy.jdk;public interface UserDao {public void addUser();public void deleteUser();
}

3.创建接口实现类,实现接口中的方法,并在方法中输出一条语句

package com.nynu.qdy.jdk;//目标类
public class UserDaoImpl implements UserDao {public void addUser() {System.out.println("添加用户");}public void deleteUser() {System.out.println("删除用户");}
}

4.创建切面类MyAspect,并在该类中定义模拟权限检查和日志记录的方法。这两个方法表示切面中的通知

package com.nynu.qdy.aspect;//切面类:可以存在多个通知Advice(即增强的方法)
public class MyAspect {public void check_Permissions() {System.out.println("模拟检查权限.....");}public void log() {System.out.println("模拟记录日志....");}
}

5.创建代理类,该类需要实现InvocationHandller接口,并编写代理方法。在代理方法中,需要通过Proxy类实现动态代理

package com.nynu.qdy.jdk;import com.nynu.qdy.aspect.MyAspect;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** JDK代理类*/
public class JdkProxy implements InvocationHandler {// 声明目标接口private UserDao userDao;// 创建代理方法@SuppressWarnings("rawtypes")public Object createProxy(UserDao userDao) {this.userDao = userDao;// 1.类加载器ClassLoader classLoader = JdkProxy.class.getClassLoader();// 2.被代理对象实现的所有接口Class[] clazz = userDao.getClass().getInterfaces();// 3.使用代理类,进行增强,返回的是代理后的对象return Proxy.newProxyInstance(classLoader, clazz, this);}/*** 所有动态类的方法调用,都会交有invoke()方法来处理 * proxy : 被代理后的对象 * method : 将要被执行的方法信息(反射)* args : 执行方法时需要的参数*/public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 声明切面MyAspect myAspect = new MyAspect();// 前增强myAspect.check_Permissions();// 在目标类上调用方法,并传入参数Object obj = method.invoke(userDao, args);// 后增强myAspect.log();return obj;}
}

jdkProxy类实现了InvocationHandler接口,并实现了接口中的invoke方法, 所有动态代理类所调用的方法都会交由该方法处理。在创建的代理方法createProxy()中,使用了Proxy类的newProxyInstance()方法来创建代理对象。newProxyInstance()方法中包含3个参数,其中第一个参数是当前类的类加载器,第2个参数表示的是被代理对象实现的所有接口,第3个参数this代表的就是代理类jdkProxy本身。在invoke()方法中,目标类方法执行的前后分别执行切面类中的check_Permissions()方法和log()方法。

6.测试类

  1. 创建代理对象和目标对象
  2. 从代理对象中获得对目标对象userDao增强后的对象
  3. 最后调用该对象中的添加和删除方法
package com.nynu.qdy.jdk;public class jdkTest {public static void main(String[] args) {// 创建代理对象JdkProxy jdkProxy = new JdkProxy();// 创建目标对象UserDao userDao = new UserDaoImpl();// 从代理对象中获取增强后的目标对象UserDao userDao1 = (UserDao) jdkProxy.createProxy(userDao);// 执行方法userDao1.addUser();userDao1.deleteUser();}
}

7.结果

模拟检查权限.....
添加用户
模拟记录日志....
模拟检查权限.....
删除用户
模拟记录日志....

从结果可以看出,userDao实例中的添加用户和删除用户的方法已被成功调用,并且在调用前后分别增加了权限检查和记录日志的功能。这种实现了接口的动态代理方式,就是Spring中的JDK动态代理。

2.2、CGLIB动态代理

JDK动态代理的使用非常简单,但它还有一定的局限性一使 用动态代理的对象必须实现一个或多个接口。如果要对没有实现接只的类进行代理,那么可以使用CGLIB代理。

CGLIB ( Code Generation Library )是一个高性能开源的代码生成包,它采用非常底层的字 节码技术,对指定的目标类生成一个子类, 并对子类进行增强。在Spring的核心包中已经集成了CGLIB所需要的包,所以开发中不需要另外导入JAR包。

下面通过一个案例来演示CGLIB动态代理的实现过程

1.创建目标类,目标类不需要实现任何接口,只需定义所需要的方法

package com.nynu.qdy.cglib;//目标类
public class UserDao {public void addUser() {System.out.println("添加用户");}public void deleteUser() {System.out.println("删除用户");}}

2.创建代理类,该代理类需要实现MethodInterceptor接口,并实现接口中的intercept()方法

package com.nynu.qdy.cglib;import java.lang.reflect.Method;import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;import com.nynu.qdy.aspect.MyAspect;//代理类
public class CglibProxy implements MethodInterceptor {// 代理方法public Object createProxy(Object target) {// 创建一个动态对象Enhancer enhancer = new Enhancer();// 确定需要增强的父类,设置其父类enhancer.setSuperclass(target.getClass());// 添加回调函数enhancer.setCallback(this);// 返回创建的代理类return enhancer.create();}/*** proxy CGlib根据指定父类生成代理对象 method 拦截的方法 args 拦截的参数数组 methodProxy* 方法的代理对象,用于执行父类的方法*/public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {// 创建切面类对象MyAspect myAspect = new MyAspect();// 前增强myAspect.check_Permissions();// 目标方法执行Object obj = methodProxy.invokeSuper(proxy, args);// 后增强myAspect.log();return obj;}}

在该代理方法中,首先创建了一个动态类对象Enhancer,它是CGLIB的核心类;然后调用了Enhancer类的setSuperclass()方法来确定目标对象;加下例调用了setCalllback()方法添加回调函数,其中的this代表的就是代理类CglibProxy本身;最后通过return语句将创建的代理类对象返回。intercept()方法会在程序执行目标方法时被调用,方法运行时将会执行切面类中的增强方法。

3.测试
创建测试类CglibTest,在该类的main方法中首先创建代理对象和目标对象,然后从代理对象中获得增强后的目标对象,最后调用对象中的添加和删除方法。

package com.nynu.qdy.cglib;public class CglibTest {public static void main(String[] args) {// 创建代理对象CglibProxy cglibProxy = new CglibProxy();// 创建目标对象UserDao userDao = new UserDao();// 获取增强后的对象UserDao userDao1 = (UserDao) cglibProxy.createProxy(userDao);// 执行方法userDao1.addUser();userDao1.deleteUser();}
}

4.结果

模拟检查权限.....
添加用户
模拟记录日志....
模拟检查权限.....
删除用户
模拟记录日志....

从结果可以看出,目标类UserDao中的方法被成功调用并增强了。这种没有实现接口的代理方式,就是CGLIB代理。

3、基于代理类的AOP实现

Spring中的AOP代理默认就是使用JDK动态代理的方式实现的。在Spring中,使用ProxyFactoryBean是创建AOP代理的最基本方式。

3.1、Spring的通知类型

Spring中的通知按在目标类方法的连接点位置,可以分为以下5种类型:

  • org.allane.intercept.Methodinterceptor (环绕通知)
    在目标方法执行前后实施增强,可以应用于日志、事务管理等功能。
  • org.springframework.aop.MethodBeforeAdvice (前置通知)
    在目标方法执行前实施增强,可以应用于权限管理等功能。
  • org.springframework aop .AfterReturningAdvice (后置通知)
    在目标方法执行后实施增强,可以应用于关闭流、上传文件、删除临时文件等功能。
  • org.springframework.aop.ThrowsAdvice (异常通知)
    在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能。
  • org.springtramework.aop.IntroductionInterceptor (引介通知)
    在目标类中添加一些新的方法和属性,可以应用于修改老版本程序(增强类)。

3.2、ProxyFactoryBean

ProxyFactoryBean是FactoryBean接口的实现类,FactoryBean负责实例化一个Bean,而ProxyFactoryBean负责为其他Bean创建代理实例。ProxyFactoryBean类中的常用配置属性如下边所示:

属性名称描述
target代理的目标对象
proxyInterfaces代理要实现的接口,如果是多个接口,可以使用以下格式赋值
proxyTargetClass是否对类代理而不是接口,设置为true时,使用CGLIB代理
interceptornames需要织入目标的Advice
singleton返回的代理是否为单实例,默认为true(即返回单实例)
optimize当设置为trues时,强制使用CGLIB

对ProxyFactoryBean类有了初步的了解后,接下来通过一个典型的环绕通知案例,来演示Spring使用ProxyFactroyBean创建AOP代理

1.创建Java工程,导入Spring框架需要的JAR包并发布到类路径下,如下图所示:
在这里插入图片描述
2.创建切面类,实现MethodInterceptor接口,并实现接口中的invoke()方法,来执行目标方法

package com.nynu.qdy.factorybean;import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;//切面类
public class MyAspect implements MethodInterceptor {public Object invoke(MethodInvocation mi) throws Throwable {check_Permissions();// 执行目标方法Object obj = mi.proceed();log();return obj;}public void check_Permissions() {System.out.println("模拟检查权限...");}public void log() {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"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-4.3.xsd"><!-- 1 目标类 --><bean id="userDao" class="com.nynu.qdy.jdk.UserDaoImpl" /><!-- 2 切面类 --><bean id="myAspect" class="com.nynu.qdy.factorybean.MyAspect" /><!-- 3 使用Spring代理工厂定义一个名称为userDaoProxy的代理对象 --><bean id="userDaoProxy"class="org.springframework.aop.framework.ProxyFactoryBean"><!-- 3.1 指代理实现的接口 --><property name="proxyInterfaces"value="com.nynu.qdy.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>

通过< bean >元素定义了目标类和切面,然后使用ProxyFactoryBean类定义了代理对象。在定义的代理对象中,分别通过< property >子元素指定了代理实现的接口、代理的目标对象、需要织入目标类的通知以及代理方式。

4.创建测试类

package com.nynu.qdy.factorybean;import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;import com.nynu.qdy.jdk.UserDao;public class ProxyFactoryBeanTest {
//测试类@SuppressWarnings("resource")public static void main(String[] args) {/** String xmlPath = "com/nynu/qdy/factorybean/applicationContext.xml";* * @SuppressWarnings("resource") ApplicationContext applicationContext = new* ClassPathXmlApplicationContext(xmlPath);*/ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");// 从spring容器中获得内容UserDao userDao = (UserDao) applicationContext.getBean("userDaoProxy");// 执行方法userDao.addUser();userDao.deleteUser();}
}

5.结果

模拟检查权限...
添加用户
模拟记录日志...
模拟检查权限...
删除用户
模拟记录日志...

4、AspectJ开发

AspectJ是一个基于Java语言的AOP框架,它提供了强大的AOP功能。

使用AspectJ实现AOP的方式:

  • 基于XML的声明式AspectJ
  • 基于注解的声明式AspectJ

4.1、基于XML的声明式AspectJ

基于XML的声明式AspectJ是指通过XML文件来定义切面、切入点以及通知,所有的切面、切入点和通知都必须定义在< aop:config >元素内。

1. 配置切面

在Spring的配置文件中,配置切面使用的是<aop:aspect >元素,该元素会将一个已定 义好的Spring Bean转换成切面Bean,所以要在配置文件中先定义一个普通的Spring Bean (如上述代码中定义的myAspect)。 定义完成后,通过<aop:aspect >元素的ref 属性即可引用该Bean。

配置<aop:aspect >元素时,通常会指定id和ref两个属性,如下表所示。

属性名称描述
id用于定义该切面的唯一标识名称
ref用于引用普通的Spring Bean

2.配置切入点

在Spring的配置文件中,切入点是通过<aop:pointcut >元素来定义的。当<aop:pointcut >元素作为<aop:config >元素的子元素定义时,表示该切入点是全局切入点,它可被多个切面所共享;当<aop:pointcut >元素作为<aop:aspect >元素的子元素时,表示该切入点只对当前切面有效

在定义<aop:pointcut >元素时,通常会指定id和expression两个属性,如下表所示。

属性名称描述
id用于指定切入点的唯一标识名称
expression用于指定切入点关联的切入点表达式

在上述配置代码片段中,execution(* com.itheima.jdk.. (…))就是定义的切入点表达式,该切入点表达式的意思是匹配com.itheima.jdk包中任意类的任意方法的执行。其中execution()是 表达式的主体,第1个**表示的是返回类型,使用代表所有类型; com.itheima.jdk 表示的是需要拦截的包名,后面第2个表示的是类名,使用代表所有的类;第3个表示的是方法名,使用表示所有方法;后面(…)表示方法的参数,其中的“”表示任意参数。需要注意的是,**第1个与包名之间有-个空格。**

上面示例中定义的切入点表达式只是开发中常用的配置方式,而Spring AOP中切入点表达式的基本格式如下:

execution (modi fiers-pattern? ret-type-pattern declaring-type-pattern?
name -pattern (param-pattern) throws-pattern?)

上述格式中,各部分说明如下。

  • modifiers- pattern:表示定义的目标方法的访问修饰符,如public、 private 等。
  • ret-type- pattern:表示定义的目标方法的返回值类型,如void、String 等。
  • delaring-type- pattern: 表示定义的目标方法的类路径,如com.itheima.jdk.UserDaolmpl. ,
  • name- -pattern: 表示具体需要被代理的目标方法,如add()方法。
  • param- pattern:表示需要被代理的目标方法包含的参数,本章示例中目标方法参数都为空。
  • throws- pattern:表示需要被代理的目标方法抛出的异常类型。

其中带有问号(? ) 的部分,如mdifers- patterm、 declaring-type- pattern和throws -pattern表示可配置项;而其他部分属于必须配置项。

3.配置通知

在配置代码中,分别使用<aop:aspeq >的子元素配置了5种常用通知,这5个子元素不支持使用子元素,但在使用时可以指定一些属性,如下表所示。

属性名称描述
pointcut该属性用于指定-一个切入点表达式, Spring 将在匹配该表达式的连接点时织入该通知
pointcut-ref该属性指定一个已经存在的切入点名称,如配置代码中的myPointCut。通常pointut和pointcut-ref两个属性只需要使用其中之一
method该属性指定一个方法名,指定将切面Bean中的该方法转换为增强处理
throwing该属性只对< after-throwing >元素有效, 它用于指定一个形参名,异常通知方法可以通过该形参访问目标方法所抛出的异常
returning该属性只对< after-returning >元素有效, 它用于指定一个形参名,异常通知方法可以通过该形参访问目标方法的返回值

了解了如何在XML文件中配置切面、切入点和通知后,接下来通过一个案例来演示如何在Spring中基于XML的声明式Aspect J

1.创建切面类,并在类中分别定义不同类型的通知

package com.nynu.qdy.aspectj.xml;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.ProceedingJoinPoint;public class MyAspect {// 前置通知public void myBefore(JoinPoint joinPoint) {System.out.println("前置通知:模拟执行权限检查:");System.out.println("目标类是:" + joinPoint.getTarget());System.out.println(",被植入增强的目标方法为: "+ joinPoint.getSignature().getName());}// 后置通知public void myAfterReturning(JoinPoint joinPoint) {System.out.println("后置通知:模拟记录日志:");System.out.println("被植入增强处理的目标方法为: "+ joinPoint.getSignature().getName());}/*** 环绕通知 ProceedingJoinPoint 是JoinPoint子接口。表示可以执行目标方法 1.必须是Object类型的返回值 2.* 必须接受一个参数,类型为ProceedingJoinPoint 3. 必须throws Throwable* * @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("最终通知:模拟方法结束后的释放资源");}
}

在切面类中,分别定义了5种不同类型的通知,在通知中使用了JoinPoint 接口及其子接口ProceedingJoinPoint作为参数来获得目标对象的类名、目标方法名和目标方法参数等。

需要注意的是,环绕通知必须接收一个类型为ProceedingJoinPoint的参数,返回值也必须是Object类型,且必须抛出异常。异常通知中可以传入Throwable类型的参数来输出异常信息。

2.创建配置文件,并编写相关配置

<?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/beanshttp://www.springframework.org/schema/beans/spring-beans-4.3.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-4.3.xsd"><!-- 1 目标类 --><bean id="userDao" class="com.nynu.qdy.jdk.UserDaoImpl" /><!-- 2 切面 --><bean id="myAspect" class="com.nynu.qdy.aspectj.xml.MyAspect" /><!-- 3 aop编程 --><aop:config><!-- 切面配置 --><aop:aspect ref="myAspect"><!-- 3.1 配置切入点,通知最后增强哪些方法 --><aop:pointcut expression="execution(* com.nynu.qdy.jdk.*.*(..))"id="myPointCut" /><!-- 3.2 关联通知Advice和切入点pointCut --><!-- 3.2.1 前置通知 --><aop:before method="myBefore" pointcut-ref="myPointCut" /><aop:after-returning method="myAfterReturning"pointcut-ref="myPointCut" returning="returnVal" /><!-- 3.2.3 环绕通知 --><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>
</beans>

3.创建测试类,并在类中对方法进行增强

package com.nynu.qdy.aspectj.xml;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import com.nynu.qdy.jdk.UserDao;public class TestXmlAspectj {@SuppressWarnings("resource")public static void main(String[] args) {String xmlPath = "com/nynu/qdy/aspectj/xml/applicationContext.xml";ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);UserDao userDao = (UserDao) applicationContext.getBean("userDao");userDao.addUser();}
}

4.结果

前置通知:模拟执行权限检查:
目标类是:com.nynu.qdy.jdk.UserDaoImpl@184cf7cf
,被植入增强的目标方法为: addUser
环绕开始:执行目标方法之前,模拟开启事物。。。。
添加用户
最终通知:模拟方法结束后的释放资源
环绕结束:执行目标方法之后。关闭模拟事物。。。
后置通知:模拟记录日志:
被植入增强处理的目标方法为: addUser

4.2、基于注解的声明式AspectJ

与基于代理类的AOP实现相比,基于XML的声明式ApectJ要便捷得多,但是它也存在着一些 缺点,那就是要在Spring文件中配置大量的代码信息。为了解决这个问题,AspectJ 框架为AOP的实现提供了一套注解,用以取代Spring配置文件中为实现AOP功能所配置的臃肿代码。

关于AspectJ注解的介绍,如下表所示:

注解名称描述
@Aspect用于定义一个切面
@Pointcut用于定义切入点表达式。在使用时还需定义一个包含名字和任意参数的方法签名来表示切入点 名称。实际上,这个方法签名就是一一个返回值为 void,且方法体为空的普通的方法
@Before用于定义前置通知,相当于BeforeAdvice。在使用时,通常需要指定一一个value属性值,该属性值用于指定一个切入点表达式 (可以是已有的切入点,也可以直接定义切入点表达式)
@AfterReturning用于定义后置通知,相当于AfterReturningAdvice。 在使用时可以指定pointcut/value 和 returning属性,其中pointcut/value 这两个属性的作用一样,都用于指定切入点表达式。returning属性值用于表示Advice方法中可定义与此同名的形参,该形参可用于访问目标方法的返回值
@Around用于定义环绕通知,相当于MethodInterceptor.在使用时需要指定一个value属性,该属性用于指定该通知被植入的切入点
@After Throwing用于定义异常通知来处理程序中未处理的异常,相当于ThrowAdvice 。在使用时可指定pointcut/value和throwing属性。其中pointcutvalue用于指定切入点表达式,而throwing属性值用于指定一个形参名来表示Advice方法中可定义与此同名的形参,该形参可用于访问目标方法抛出的异常
@After用于定义最终final通知,不管是否异常,该通知都会执行。使用时需要指定一个 value属性,该属性用于指定该通知被植入的切入点
@DeclareParents用于定义引介通知,相当于IntroductionInterceptor (不要求掌握)

为了使读者可以快速地掌握这些注解,接下来重新使用注解的形式来实现上一小节的案例,具体步骤如下

1.复制上一节切面类,并做相应修改

package com.nynu.qdy.aspectj.annotation;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;@Aspect
@Component
public class MyAspect {// 切入点表达式@Pointcut("execution(* com.nynu.qdy.jdk.*.*(..))")// 使用一个返回值为void ,方法体为空的方法来命名切入点private void myPointCut() {}// 前置通知@Before("myPointCut()")public void myBefore(JoinPoint joinPoint) {System.out.println("前置通知:模拟执行权限检查:");System.out.println("目标类是:" + joinPoint.getTarget());System.out.println(",被植入增强的目标方法为: " + joinPoint.getSignature().getName());}// 后置通知@AfterReturning("myPointCut()")public void myAfterReturning(JoinPoint joinPoint) {System.out.println("后置通知:模拟记录日志:");System.out.println("被植入增强处理的目标方法为: " + joinPoint.getSignature().getName());}/*** 环绕通知 ProceedingJoinPoint 是JoinPoint子接口。表示可以执行目标方法 1.必须是Object类型的返回值 2.* 必须接受一个参数,类型为ProceedingJoinPoint 3. 必须throws Throwable* * @throws Throwable*/@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("最终通知:模拟方法结束后的释放资源");}
}

在切面类中,首先使用@Aspect注解定义了切面类,由于该类在Spring中是作为组件使用的,所以还需要添加@Component注解才能生效。然后使用了@Poincut注解来配置切入点表达式,并通过定义方法来表示切入点名称。接下来在每个通知相应的方法上添加了相应的注解,并将切入点名称“myPointCut"作为参数传递给需要执行增强的通知方法。如果需要其他参数(如异常通知的异常参数),可以根据代码提示传递相应的属性值。

2.目标类com.itheima.jdk.UserDaolmpl中,添加注解@Repository(“userDao”)

package com.nynu.qdy.jdk;import org.springframework.stereotype.Repository;@Repository("userDao")
public class UserDaoImpl implements UserDao {public void addUser() {System.out.println("添加用户");}public void deleteUser() {System.out.println("删除用户");}}

3.创建配置文件applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-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/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd "><!-- 指定需要扫描的包,使注解生效 --><context:component-scanbase-package="com.nynu.qdy" /><!-- 启动基于注解的声明式AspectJ支持 --><aop:aspectj-autoproxy/></beans>

4.创建测试类

package com.nynu.qdy.aspectj.annotation;import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;import com.nynu.qdy.jdk.UserDao;public class TestAnnotation {@SuppressWarnings("resource")public static void main(String[] args) {String xmlPath = "com/nynu/qdy/aspectj/annotation/applicationContext.xml";ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);// 1 从容器中获得内容UserDao userDao = (UserDao) applicationContext.getBean("userDao");// 2 执行方法userDao.addUser();userDao.deleteUser();}
}

5.结果

环绕开始:执行目标方法之前,模拟开启事物。。。。
前置通知:模拟执行权限检查:
目标类是:com.nynu.qdy.jdk.UserDaoImpl@71809907
,被植入增强的目标方法为: addUser
添加用户
环绕结束:执行目标方法之后。关闭模拟事物。。。
最终通知:模拟方法结束后的释放资源
后置通知:模拟记录日志:
被植入增强处理的目标方法为: addUser
环绕开始:执行目标方法之前,模拟开启事物。。。。
前置通知:模拟执行权限检查:
目标类是:com.nynu.qdy.jdk.UserDaoImpl@71809907
,被植入增强的目标方法为: deleteUser
删除用户
环绕结束:执行目标方法之后。关闭模拟事物。。。
最终通知:模拟方法结束后的释放资源
后置通知:模拟记录日志:
被植入增强处理的目标方法为: deleteUser

如果同一个连接点有多个通知需要执行,那么在同一切面中,目标方法之前的前置通知和环绕通知的执行顺序是未知的,目标方法之后的后置通知和环绕通知的执行顺序也是位置的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/323021.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SQL2017 Azure SQL新功能:图形数据库

图形数据库是什么呢&#xff1f;如果从字面理解是进行图形处理的数据库&#xff0c;那么你就错了哈哈。 我们先来解释什么是图形数据库。 图形数据库是NoSQL数据库的一种类型&#xff0c;它应用图形理论存储实体之间的关系信息。最常见的例子&#xff0c;就是社会网络中人与人之…

Hadoop入门(十三)远程提交wordCout程序到hadoop集群

一、项目结构 用到的文件有WordCount.java、core-site.xml、mapreduce-site.xml、yarn-site.xml、log4j.properties、pom.xml 二、项目源码 &#xff08;1&#xff09;WordCount.java package com.mk.mapreduce;import org.apache.hadoop.conf.Configuration; import org.ap…

腾讯云短信服务使用记录与.NET Core C#代码分享

1、即使是相同的短信签名与短信正文模板&#xff0c;也需要针对“国内文本短信”与“海外文本短信”分别申请。开始不知道&#xff0c;以为只要申请一次&#xff0c;给国外手机发短信时给api传对应的国家码就行&#xff0c;后来才发现需要分别申请。 2、短信服务web api响应“手…

Hadoop入门(九)Mapreduce高级shuffle之Combiner

一、Combiner的出现 &#xff08;1&#xff09;为什么需要进行Map规约操 作 在上述过程中&#xff0c;我们看到至少两个性能瓶颈&#xff1a; &#xff08;1&#xff09;如果我们有10亿个数据&#xff0c;Mapper会生成10亿个键值对在网络间进行传输&#xff0c;但如果我们只…

欢乐纪中某A组赛【2019.1.19】

前言 因为BBB有一堆(两道)题都做过&#xff0c;于是就来做A组了。 成绩 RankRankRank是有算别人的 RankRankRankPersonPersonPersonScoreScoreScoreAAABBBCCC3332017myself2017myself2017myself2102102101001001001001001001010102222222017lrz2017lrz2017lrz1001001000001001…

使用Identity Server 4建立Authorization Server (2)

第一部分: 使用Identity Server 4建立Authorization Server (1) 第一部分主要是建立了一个简单的Identity Server. 接下来继续: 建立Web Api项目 如图可以在同一个解决方案下建立一个web api项目: (可选)然后修改webapi的launchSettings.json, 我习惯使用控制台, 所以把IISExpr…

建立Vue脚手架的必要性

首先所有文件都放到一个html&#xff0c;代码多了之后阅读体验非常差。 其次建立这样的文件夹后&#xff0c;发现竟然不能随时更新&#xff0c;有缓存的情况

【实验手册】使用Visual Studio Code 开发.NET Core应用程序

.NET Core with Visual Studio Code 目录 概述... 2 先决条件... 2 练习1&#xff1a; 安装和配置.NET Core以及Visual Studio Code 扩展... 2 任务1&#xff1a;安装Visual Studio Code和.NET Core. 2 任务2&#xff1a;安装插件... 4 练习2&#xff1a;使用命令行界面构建. N…

Hadoop入门(八)Mapreduce高级shuffle之Partitioner

一、Partitioner概述 Map阶段总共五个步骤&#xff0c;2就是一个分区操作 哪个key到哪个Reducer的分配过程&#xff0c;是由Partitioner规定的。 二、Hadoop内置Partitioner MapReduce的使用者通常会指定Reduce任务和Reduce任务输出文件的数量&#xff08;R&#xff09;。 用…

在ASP.NET Core中使用AOP来简化缓存操作

前言 关于缓存的使用&#xff0c;相信大家都是熟悉的不能再熟悉了&#xff0c;简单来说就是下面一句话。 优先从缓存中取数据&#xff0c;缓存中取不到再去数据库中取&#xff0c;取到了在扔进缓存中去。 然后我们就会看到项目中有类似这样的代码了。 public Product Get(int p…

Hadoop入门(七)Mapreduce高级Shuffle

一、Shuffle概述 Reduce阶段三个步骤&#xff0c;Shuffle就是一个随机、洗牌操作 Shuffle是什么 针对多个map任务的输出按照不同的分区&#xff08;Partition&#xff09;通过网络复制到不同的reduce任务节点上&#xff0c;这个过程就称作为Shuffle。 二、Shuffle过程 &#…

methods中axios里的数据无法渲染到页面

最近在研究axios聊天室室遇到一个问题 将axios获取到的数据传递给data&#xff0c;从而改变页面中的数值&#xff0c;但是结果令人失望 这是data里的数据 原想将data中的items数组换成axios里的response.data&#xff0c;后来发现items一直为空&#xff0c;就拿字符串做实验了…

.NET Core跨平台的奥秘[上篇]:历史的枷锁

微软推出的第一个版本的.NET Framework是一个面向Windows桌面和服务器的基础框架&#xff0c;在此之后&#xff0c;为此微软根据设备自身的需求对.NET Framework进行裁剪&#xff0c;不断推出了针对具体设备类型的.NET Framework版本以实现针对移动、平板和嵌入式设备提供支持。…

Hadoop入门(十)Mapreduce高级shuffle之Sort和Group

一、排序分组概述 MapReduce中排序和分组在哪里被执行 第3步中需要对不同分区中的数据进行排序和分组&#xff0c;默认情况按照key进行排序和分组 二、排序 在Hadoop默认的排序算法中&#xff0c;只会针对key值进行排序 任务&#xff1a; 数据文件中&#xff0c;如果按照第一…

使用Identity Server 4建立Authorization Server (3)

预备知识: 学习Identity Server 4的预备知识 第一部分: 使用Identity Server 4建立Authorization Server (1) 第二部分: 使用Identity Server 4建立Authorization Server (2) 上一部分简单的弄了个web api 并通过Client_Credentials和ResourceOwnerPassword两种方式获取token然…

php接口跨域问题

报错是因为接口跨域&#xff0c;不允许访问 只需在php头部加入此行代码就行了 header(Access-Control-Allow-Origin:*);

spring boot输出hello world几种方法

1、手动配置&#xff0c;三个文件 打开创建maven,创建这三个文件从上到下依次复制即可 配置文件&#xff08;重要&#xff09;&#xff08;否则后面会报错&#xff09; pom.xml <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w…

欢乐纪中某B组赛【2019.1.20】

前言 有回来做BBB组了&#xff0c;话说第3道题就是AAA组第一道。 成绩 RankRankRank是有算别人的 今天XJQXJQXJQ不在 RankRankRankPersonPersonPersonScoreScoreScoreAAABBBCCC1112017wyc2017wyc2017wyc2702702701001001001001001007070701010102017hjq2017hjq2017hjq13013013…

向ASP.NET Core迁移

我们首先来看看ASP.NET Core有哪些优势&#xff1f; 跨平台&#xff1a;可以部署到Linux服务器上 内置一套对云和部署环境非常友好的配置模块 内置依赖注入 IIS或者Kestrel&#xff08;或者其它自定义&#xff09; 轻量级、高性能、模块化的Http处理管线 .NET Core 是开源…

ASP.NET Core集成现有系统认证

我们现在大多数转向ASP.NET Core来使用开发的团队&#xff0c;应该都不是从0开始搭建系统&#xff0c;而是老的业务系统已经在运行&#xff0c;ASP.NET Core用来开发新模块。那么解决用户认证的问题&#xff0c;成为我们的第一个拦路虎。 认证与授权 什么是认证&#xff1f; …