Spring中的AOP

目录

零、码仙励志

一、Spring AOP简介

1.什么是AOP

2.AOP术语

二、动态代理(了解)

1.JDK动态代理

2.CGLIB代理

三、基于代理类的AOP实现

1.Spring的通知类型

2.ProxyFactoryBean

四、AspectJ开发

1.基于 XML 的声明或 AspectJ

2.基于注解的声明式AspectJ


零、码仙励志

能力配不上野心,是所有烦扰的根源

一、Spring AOP简介

1.什么是AOP

  1. AOP的全称是Aspect-Oriented Programming,即面向切面编程(也称面向方面编程)。它是面向对象编程(OOP)的一种补充,目前已成为一种比较成熟的编程方式。
  2. 在传统的业务处理代码中,通常都会进行事务处理、日志记录等操作。虽然使用OOP可以通过组合或者继承的方式来达到代码的重用,但如果要实现某个功能(如日志记录),同样的代码仍然会分散到各个方法中。这样,如果想要关闭某个功能,或者对其进行修改,就必须要修改所有的相关方法。这不但增加了开发人员的工作量,而且提高了代码的出错率。
  3. 为了解决这一问题,AOP思想随之产生。AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方。这种采用横向抽取机制的方式,采用传统的OOP思想显然是无法办到的,因为OOP只能实现父子关系的纵向的重用。虽然AOP是一种新的编程思想,但却不是OOP的替代品,它只是OOP的延伸和补充。

类与切面的关系

Spring中的AOP

AOP的使用,使开发人员在编写业务逻辑时可以专心于核心业务,而不用过多的关注于其他业务逻辑的实现,这不但提高了开发效率,而且增强了代码的可维护性。

2.AOP术语

Spring中的AOP

  1. Advice(通知/增强处理):AOP框架在特定的切入点执行的增强处理,即在定义好的切入点处所要执行的程序代码。可以将其理解为切面类中的方法。
  2. Target Object(目标对象):指所有被通知的对象,也被称为被增强对象。如果AOP框架采用的是动态的AOP实现,那么该对象就是一个被代理对象。
  3. Proxy(代理):将通知应用到目标对象之后,被动态创建的对象。
  4. Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程。

二、动态代理(了解)

1.JDK动态代理

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

接下来,通过一个案例来演示 Spring 中 JDK 动态代理的实现过程,具体步骤如下。

1.创建一个名为chapter03的Web项目,导入Spring框架所需JAR包到项目的lib目录中,并发布到类路径下。

本篇文章使用的全部JAR包下载

链接:https://pan.baidu.com/s/1ZbGOznTXRPbrd2nWJwCrfw 
提取码:jit1 
(如果链接失效,请联系QQ194760901)

Spring中的AOP

2.在src目录下,创建一个com.maxian.jdk包,在该包下创建接口UserDao,并在该接口中编写添加和删除方法。

package com.maxian.jdk;

public interface UserDao {
	public void addUser();
	public void deleteUser();
}

3.在com.maxian.jdk包中,创建serDao接口的实现类UserDaoImpl

package com.maxian.jdk;
//目标类
public class UserDaoImpl implements UserDao {

	@Override
	public void addUser() {
		System.out.println("添加用户");
	}

	@Override
	public void deleteUser() {
		System.out.println("删除用户");
	}

}

4.在src目录下,创建一个com.maxian.aspect包,并在该包下创建切面类MyAspect,在该类中定义一个模拟权限检查的方法和一个模拟日志记录的方法,这俩个方法就表示切面中的通知

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

	public void log() {
		System.out.println("模拟记录日志...");
	}
}

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

package com.maxian.jdk;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.maxian.aspect.MyAspect;
/**
 * JDK代理类
 */
public class JdkProxy implements InvocationHandler {
	// 声明目标类接口
	private UserDao userDao;

	// 创建代理方法
	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);
	}

	@Override
	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()方法中包含三个参数,其中第1个参数是当前类的类加载器,第2个参数表示的是被代理对象实现的所有接口,第3个参数this代表的就是代理了JdkProxy本身。在invoke()方法中,目标类方法执行的前后,会分别执行切面类中的check_Permissions()方法和log()方法。

6.在com.maxian.jdk包中,创建测试类JdkTest。

package com.maxian.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();
	}

}

运行结果

Spring中的AOP

2.CGLIB代理

  1. 通过前面的学习可知,JDK的动态代理用起来非常简单,但它是有局限性的,使用动态代理的对象必须实现一个或多个接口。 
  2. 如果想代理没有实现接口的类,那么可以使用CGLIB代理。
  3. CGLIB(Code Generation Library)是一个高性能开源的代码生成包,它采用非常底层的字节码技术,对指定的目标类生成一个子类,并对子类进行增强。

 接下来,通过一个案例来演示 Spring 中 CGLIB 代理的实现过程,具体步骤如下。

1.在src目录下,创建一个com.maxian.cglib包,在该包下创建类UserDao,UserDao不需要实现任何接口,并在该类中编写添加和删除方法。

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

	public void deleteUser() {
		System.out.println("删除用户");
	}
}

2.在com.maxian.cglib包中,创建代理类CglibProxy,该代理类需要实现MethodInterceptor接口,并实现接口中的intercept()方法。

package com.maxian.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.maxian.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
	 * 方法的代理对象,用于执行父类的方法
	 */
	@Override
	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()方法来确定目标对象;接下来调用了setCallback()方法添加回调函数,其中this代表的就是代理类CglibProxy本身;最后通过return语句将创建的代理类对象返回。intercept()方法会在程序执行目标方法时被调用,方法运行时将会执行切面类中的增强方法。

3.在com.maxian.cglib包中,创建测试类CglibTest。

package com.maxian.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();
	}

}

运行结果

Spring中的AOP

三、基于代理类的AOP实现

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

1.Spring的通知类型

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

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

2.ProxyFactoryBean

ProxyFactoryBean是FactoryBean接口的实现类,FactoryBean负责实例化一个Bean,而ProxyFactoryBean负责为其他Bean创建代理实例。在Spring中,使用ProxyFactoryBean是创建AOP代理的基本方式。

ProxyFactoryBean类中的常用可配置属性如下:

Spring中的AOP

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

1.在核心JAR包的基础上,再添加俩个JAR包。

(1)spring-aop-4.3.6.RELEASE.jar:是Spring为AOP提供的实现包,Spring的包已经提供
(2)aopalliance-1.0.jar:是AOP联盟提供的规范包,该JAR包可以通过地址http://mvnrepository.com/artifact/aopalliance/aopalliance/1.0下载

Spring中的AOP

2.在src目录下,创建一个com.maxian.factorybean包,在该包中创建切面类MyAcpect。由于实现环绕通知需要实现 org.aopalliance.intercept.MethodInterceptor 接口,并实现 invoke() 方法,来执行目标方法。

package com.maxian.factorybean;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

//切面类
public class MyAcpect implements MethodInterceptor {

	@Override
	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.在com.maxian.factorybean包中,创建文件applicationContext.xml,并指定代理对象

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	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.maxian.jdk.UserDaoImpl" />
	<!-- 2 切面类 -->
	<bean id="myAspect" class="com.maxian.factorybean.MyAspect" />
	<!-- 3 使用Spring代理工厂定义一个名称为userDaoProxy的代理对象 -->
	<bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
		<!-- 3.1 指定代理实现的接口 -->
		<property name="proxyInterfaces" value="com.maxian.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>

4.在com.maxian.factorybean包中,创建测试类ProxyFactoryBeanTest,在类中通过Spring容器获取代理类对象的实例,并执行目标方法。

package com.maxian.factorybean;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.maxian.jdk.UserDao;

// 测试类
public class ProxyFactoryBeanTest {

	public static void main(String[] args) {
		String xmlPath = "com/maxian/factorybean/applicationContext.xml";
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
		// 从Spring容器获得代理对象
		UserDao userDao = (UserDao) applicationContext.getBean("userDaoProxy");
		// 执行方法
		userDao.addUser();
		userDao.deleteUser();
	}

}

运行结果

Spring中的AOP

四、AspectJ开发

AspectJ 是一个基于 Java 语言的 AOP 框架,它提供了强大的 AOP 功能 Spring 2.0 以后,Spring AOP 引入了对 AspectJ 的支持,并允许直接使用 AspectJ 进行编程,而 Spring 自身的AOPAPI 也尽量与 AspectJ保持一致 新版本的 Spring 框架,也建议使用 AspectJ 来开发 AOP使用 AspectJ 实现 AOP 有两种方式:一种是基于 XML 的声明式 AspectJ ,另一种是基于注解的声明式 AspectJ 接下来的两个小节中,将对这两种 AspectJ 的开发方式进行讲解

1.基于 XML 的声明或 AspectJ

基于XML的声明式AspectJ是指通过XML文件来定义切面、切入点及通知,所有的切面、切入点和通知都必须定义在<aop:config>元素内。<aop:config>元素及其子元素如下:(图中灰色部分标注的元素即为常用的配置元素)

Spring中的AOP

Spring 配置文件中的 <beans> 元素下可以包含多个<aop config> 元素,一个<aop:config> 元素中又可以包含属性和子元素,其子元素包括<aop:pointcut> <aop:advisor><aop :aspect> 在配置时,这3个子元素必须按照此顺序来定义 <aop:aspect>元素下,同样包含了属性和多个子元素,通过使用 <aop aspect>元素及其子元素就可以在 XML 文件中配置切面 、切入点和通知 部分标注的元素即为常用的配置元素,这些常用元素的配置代码如下所示

<!-- 1 目标类 -->
<bean id="userDao" class="com.maxian.jdk.UserDaoImpl" />
<!-- 2 切面 -->
<bean id="myAspect" class="com.maxian.aspectj.xml.MyAspect" />
<!-- 3 aop编程 -->
<aop:config>
	<!-- 配置切面 -->
	<aop:aspect ref="myAspect">
		<!-- 3.1 配置切入点,通知最后增强哪些方法 -->
		<aop:pointcut expression="execution(* com.maxian.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>

为了让读者能够清楚地掌握上述代码配置信息,下面对上述代码的配置内容进行详细讲解

1.配置切面

在Spring的配置文件中,配置切面使用的是<aop:aspect>元素,该元素会将一个已定义好的Spring Bean转换成切面Bean,所以要在配置文件中先定义一个普通的Spring Bean。

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

Spring中的AOP

2.配置切入点

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

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

Spring中的AOP

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

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

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

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

  1. modifiers-pattern: 表示定义的目标方法的访问修饰符,如 public、private
  2. ret-type-pattern: 表示定义的目标方法的返回值类型,如 void、String
  3. declaring-type-pattern: 表示定义的目标方法的类路径,如 com.maxian.jdk.UserDaolmpl
  4. name-pattern: 表示具体需要被代理的目标方法,如add()方法
  5. param-pattern 表示需要被代理的目标方法包含的参数,本章示例中目标方法参数都为空
  6. throws-pattern: 表示需要被代理的目标方法抛出的异常类型
  7. 其中带有问号(? )的部分,如 modifiers-pattern declaring-type-pa ern throws-pattern表示可配置项;而其他部分属于必须配置项

3.配置通知

使用<aop:aspect>的子元素可以配置5种常用通知,这5个子元素不支持使用子元素,但在使用时可以指定一些属性,其常用属性及其描述如下:

Spring中的AOP

了解了如何在 XML 中配置切面、切入点和通知后,接下来通过一个案例来演示如何在 Spring中使用基于 XML 的声明式 AspectJ ,具体实现步骤如下

1.导入 AspectJ 框架相关的 JAR 包,具体如下

  • spring-aspects-4.3.6.RELEASE.jar: Spring为AspectJ 提供的实现,Spring 的包中已经提供。
  • aspectjweaver-1.8.1 O.jar: AspectJ 框架所提供的规范,读者可以通过网址http://mvnrepository.com/artifact/org.aspectj/aspectjweaver/1.8.10下载

Spring中的AOP

2.在 chapter03 项目的 src 目录下,创建一个 com.maxian.aspectj.xml 包,在该包中创建切面类 MyAspect ,并在类中分别定义不同类型的通知

package com.maxian.aspectj.xml;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 切面类,在此类中编写通知
 */
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("最终通知:模拟方法结束后的释放资源...");
	}
}

上面代码中分别定义了5种不同类型的通知,在通知中使用JoinPoint接口及其子接口 ProceedingJoinPoint 作为参数来获得目标对象的类名、目标方法名和目标方法参数等。需要注意的是,环绕通知必须接收一个类型为 ProceedingJoinPoint的参数,返回值也必须是Object 类型,且必须抛出异常。异常通知中可以传入 Throwable 类型的参数来输出异常信息 。

3.com.maxian.aspectj.xml 包中,创建配置文件applicationContext.xml并编写相关配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
        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.maxian.jdk.UserDaoImpl" />
	<!-- 2 切面 -->
	<bean id="myAspect" class="com.maxian.aspectj.xml.MyAspect" />
	<!-- 3 aop编程 -->
	<aop:config>
		<!-- 配置切面 -->
		<aop:aspect ref="myAspect">
			<!-- 3.1 配置切入点,通知最后增强哪些方法 -->
			<aop:pointcut expression="execution(* com.maxian.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>

在 AOP 的配置信息中,使用 <aop:after-returning> 配置的后置通知和使用 <aop:after> 配置的最终通知虽然都是在目标方法执行之后执行,但它们也是有所区别的。后置通知只有在目标方法成功执行后才会被织入,而最终通知不论目标方法如何结束(包括成功执行和异常中止两种情况) ,它都会被织入。

4.com.maxian.aspectj.xml包下,创建测试类 TestXmlAspectj ,在类中为了更加清晰地演示几种通知的执行情况,这里只对 addUser()方法进行增强测试

package com.maxian.aspectj.xml;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.maxian.jdk.UserDao;

// 测试类
public class TestXmlAspectj {
	public static void main(String args[]) {
		String xmlPath = "com/maxian/aspectj/xml/applicationContext.xml";
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
		// 1 从spring容器获得内容
		UserDao userDao = (UserDao) applicationContext.getBean("userDao");
		// 2 执行方法
		userDao.addUser();
	}
}

运行结果 

Spring中的AOP

要查看异常通知的执行效果,可以在 UserDaolmpl 类的 addUser()方法中添加错误代码,如"int i = 10/0;" ,重新运好测试类,将可以看到异常通知的执行

package com.maxian.jdk;

//目标类
public class UserDaoImpl implements UserDao {

	@Override
	public void addUser() {
		System.out.println("添加用户");
		int i = 10 / 0;
	}

	@Override
	public void deleteUser() {
		System.out.println("删除用户");
	}

}

Spring中的AOP

2.基于注解的声明式AspectJ

AspectJ框架为AOP的实现提供了一套注解,用以取代Spring配置文件中为实现AOP功能所配置的臃肿代码。AspectJ的注解及其描述如下所示:

Spring中的AOP

为了使读者可以快速地掌握这些注解,接下来重新使用注解的形式来实现上面的例子

1.在chapter03 项目的 src 目录下,创建 com.maxian.aspectj.annotation 包,将切面类 MyAspect 复制到该包下,并对该文件进行编辑

package com.maxian.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.maxian.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("最终通知:模拟方法结束后的释放资源...");
	}
}

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

2.在目标类 com.maxian.jdk.UserDaoImpl 添加注解 @Repository("userDao")

package com.maxian.jdk;

import org.springframework.stereotype.Repository;

//目标类
@Repository("userDao")
public class UserDaoImpl implements UserDao {

	@Override
	public void addUser() {
		System.out.println("添加用户");
	}

	@Override
	public void deleteUser() {
		System.out.println("删除用户");
	}

}

3.在com.maxian.aspectj.annotation 包下,创建配置文件 applicationContext.xml,并对该文件进行编辑

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
	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.maxian" />
	<!-- 启动基于注解的声明式AspectJ支持 -->
	<aop:aspectj-autoproxy />
</beans>

在上面代码中,首先引入了 context 约束信息,然后使用 <context> 元素设置了需要扫描的包,使注解生效。由于此案例中的目标类位于 com.maxian.jdk 包中,所以这里设置base-package 的值为 "com.maxian" 最后,使用 <aop:aspectj-autoproxy />来启动 Spring对基于注解的声明式 AspectJ 支持。

4.在 com.maxian.aspectj.annotation 包中,创建测试类TestAnnotationAspectj

package com.maxian.aspectj.annotation;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.maxian.jdk.UserDao;

// 测试类
public class TestAnnotationAspectj {
	public static void main(String args[]) {
		String xmlPath = "com/maxian/aspectj/annotation/applicationContext.xml";
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
		// 1 从spring容器获得内容
		UserDao userDao = (UserDao) applicationContext.getBean("userDao");
		// 2 执行方法
		userDao.addUser();
	}
}

运行结果

Spring中的AOP

要查看异常通知的执行效果,可以在 UserDaolmpl 类的 addUser()方法中添加错误代码,如"int i = 10/0;" ,重新运好测试类,将可以看到异常通知的执行

package com.maxian.jdk;

import org.springframework.stereotype.Repository;

//目标类
@Repository("userDao")
public class UserDaoImpl implements UserDao {

	@Override
	public void addUser() {
		System.out.println("添加用户");
		int i = 10 / 0;
	}

	@Override
	public void deleteUser() {
		System.out.println("删除用户");
	}

}

运行结果

Spring中的AOP

 基于注解的方式与基于 XML 的方式的执行结果相同,只是在目标方法前后通知的执行顺序发生了变化。相对来说,使用注解的方式更加简单、方便,所以在实际开发中推荐使用注解的方式进行 AOP 开发。

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

本篇博客来自于传智播客****的总结以及笔记的整理,仅供学习交流,切勿用于商业用途,如有侵权,请联系博主删除,博主QQ:194760901