Spring AOP

Spring AOP

​ AOP即面向切面编程,它与OOP(面对对象编程)相辅相成,提供了与OOP不同的抽象软件结构视角。在OOP中,以类作为程序的基本单元,而AOP中的基本单元是Aspect(切面)。Struts2的拦截器设计就是基于AOP的思想,是个比较经典的应用。

​ 在业务处理代码中常有日志记录、性能统计、安全控制、事务处理、异常处理等操作。AOP可以减少代码的重复操作等问题,但AOP不能取代OOP,而是OOP的补充。

AOP的常用术语有:

  • 切面(Aspect):是指封装横切到系统功能(例如事务处理)的类。

  • 连接点(Joinpoint):是指程序运行中的一些时间点,例如方法的调用或异常的抛出。

  • 切入点(Pointcut)是指要处理的连接点。在Spring AOP中,所有的方法执行都是连接点,而切入点是一个描述信息,它修饰的是连接点,通过切入点确定哪些连接点需要被处理。

  • 通知(Adivce)是由切面添加到特定的连接点(满足切入点规则)的一段代码,即在定义好的切入点出所要执行的程序代码,可以将其理解为切面开启后切面的方法,因此通知时切面的具体实现。

  • 引入(Introduction)允许在现有的实现类中添加自定义的方法和属性。

  • 目标对象(Target Object)是指所有被通知的对象。如果AOP框架使用运行时代理的方式(动态AOP)来实现切面,那么通知对象总是一个代理对象。

  • 代理(Proxy)是通知应用到目标对象之后被动创建的对象。

  • 织入(Weaving)是将切面代码插入到目标对象上,从而生成代理对象的过程。根据不同的实现技术,AOP织入有三种方式:编译期织入,需要有特殊的Java编译器;类装载期间织入,需要有特殊的类装载器;动态代理织入,在运行期为目标类添加通知生成子类的方式。Spring AOP框架默认采用动态代理织入,而AspectJ(基于Java的AOP框架)采用编译期织入和类装载织入。

    动态代理:

    ​ JDK动态代理是java.lang.reflect.*包提供的方式u,它必须借助一个接口才能产生代理对象。因此,对于使用业务接口的类,Spring默认使用JDK动态代理实现AOP。

    ​ 在JDK动态代理中代理类必须实现java.lang.reflect.InvocationHandler接口,并编写代理方法,在代理方法中需要通过Proxy实现动态代理,代理类演示代码如下:

    package dynamic.jdk;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import aspect.MyAspect;
    public class JDKDynamicProxy implements InvocationHandler {
    	//声明目标类接口对象(真实对象)
    	private TestDao testDao;
    	/*创建代理的方法,建立代理对象和真实对象的代理关系,并返回代理对象*/
    	public Object createProxy(TestDao testDao) {
    		this.testDao = testDao;
    		//1.类加载器
    		ClassLoader cld = JDKDynamicProxy.class.getClassLoader();
    		//2.被代理对象实现的所有接口
    		Class[] clazz = testDao.getClass().getInterfaces();
    		//3。使用代理类进行增强,返回代理后的对象
    		return Proxy.newProxyInstance(cld, clazz, this);
    	}
    	/**
    	 * 代理的逻辑方法,所有动态代理类的方法调用,都交给该方法处理
    	 * proxy被代理对象
    	 * method将要被执行的方法信息
    	 * args执行方法时需要的参数
    	 * return代理结果
    	 */
    	@Override
    	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    		//创建一个切面
    		MyAspect myAspect = new MyAspect();
    		//前增强
    		myAspect.check();
    		myAspect.except();
    		//在目标类上调用方法,并传入参数,相当于调用testDao里的方法
    		Object obj = method.invoke(testDao, args);
    		//后增强
    		myAspect.log();
    		myAspect.monitor();
    		return obj;
    	}
    	
    }
    
    

CGLIB动态代理:

因为JDK动态代理必须提供接口才能使用,对于没有提供接口的类,只能采用CGLIB动态代理。
CGLIB(Code Generation Library)是一个高性能的开源代码生成包,采用非常底层的字节码技术,对指定目标类生成一个子类,并对子类进行增强。在Spring Core包中已经继承了CGLIB所需要的JAR包,无需另外导入JAR包。

代理类演示代码如下:

package dynamic.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 aspect.MyAspect;

public class CglibDynamicProxy implements MethodInterceptor
{
	/**
	 * 创建代理的方法,生成CGLIB代理对象 target目标对象,需要增强的对象 返回目标对象的CGLIB代理对象
	 */
	public Object createProxy(Object target)
	{
		// 创建一个动态类对象,即增强类对象
		Enhancer enhancer = new Enhancer();
		// 确定需要增强的类,设置其父类
		enhancer.setSuperclass(target.getClass());
		// 确定代理逻辑对象为当前对象,要求当前对象实现MethodInterceptor的方法
		enhancer.setCallback(this);
		// 返回创建的代理对象
		return enhancer.create();
	}

	/**
	 * intercept方法会在程序执行目标方法时被调用 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();
		myAspect.except();
		// 目标方法执行,返回代理结果
		Object obj = methodProxy.invokeSuper(proxy, args);
		// 后增强
		myAspect.log();
		myAspect.monitor();
		return obj;
	}

}

基于代理类的AOP实现

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

通知类型:

  1. 环绕通知(org.aopalliance.intercept.MethodInterceptor):是指在目标方法执行前和执行后实施增强,可应用于日志记录、事务处理等功能。
  2. 前置通知(org.springframework.aop.MethodBeforeAdvice):是在目标方法执行前实施增强,可应用于权限管理等功能。
  3. 后置返回通知(org.springframework.aop.AfterReturningAdvice):是在目标方法成功执行后实施增强,可应用于关闭流、删除临时文件等功能。
  4. 后置通知(org.springframework.aop.AfterAdvice):是在目标方法执行后实施增强,与后置返回通知不同的是,不管是否发生异常都要执行该类通知,该类通知可应用于释放资源。
  5. 异常通知(org.springframework.aop.ThrowsAdvice)是在方法抛出异常后实施增强,可应用于处理异常、记录日志等功能。
  6. 引入通知(org.springframework.aop.IntroductionInterceptor):是在目标类中添加一些新的方法和属性,可应用于修改目标类(增强类)。

ProxyFactoryBean

​ 是org.springframework.beans.factory.FactoryBean接口的实现类,FactoryBean负责实例化一个Bean实例,ProxyFactoryBean负责为其他Bean实例创建代理实例。

常见属性:

属 性 描 述
traget 代理的目标对象
proxyInterfaces 代理需要实现的接口列表。
interceptorNames 需要织入目标的Advice
proxyTargetClass 是否对类代理而不是接口,默认为false,使用JDK代理;当为true时,使用CGLIB动态代理
singleton 返回代理实例是否为单例,默认为true
optimize 当设置true时强制使用CGLIB动态代理

ProxyFactoryBean配置文件代码示例如下:

<?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.xsd">
	<!-- 定义目标对象 -->
	<bean id="testDao" class="dynamic.jdk.TestDaoImpl" />
	<!-- 创建一个切面 -->
	<bean id="myAspect" class="spring.proxyfactorybean.MyAspect" />
	<!-- 使用Spring代理工厂定义一个名为testDaoProxy的代理对象 -->
	<bean id="testDaoProxy"	class="org.springframework.aop.framework.ProxyFactoryBean">
		<!-- 指定代理实现的接口 -->
		<property name="proxyInterfaces" value="dynamic.jdk.TestDao" />
		<!-- 指定目标对象 -->
		<property name="target" ref="testDao" />
		<!-- 指定切面,植入环绕通知 -->
		<property name="interceptorNames" value="myAspect" />
		<!-- 指定代理方式,true指定CGLIB动态代理;默认false,指定JDK动态代理 -->
		<property name="proxyTargetClass" value="true" />
	</bean>
</beans>

基于注解开发AspectJ

常用注解如下:

注解名称 描述
@Aspect 用于定义一个切面,注解在切面类上
@Pointcut 用于定义切入点表达式。在使用时需要定义一个切入点方法,该方法时一个返回值void且方法体为空的普通方法
@Before 用于定义前置通知。在使用时通常为其指定value属性值,该值可以是已有的切入点,也可以直接定义切入点表达式
@AfterReturning 用于定义后置返回通知。在使用时通常为其指定value属性值,该值可以是已有的切入点,也可以直接定义切入点表达式
@Around 用于定义环绕通知。在使用时通常为其指定value属性值,该值可以是已有的切入点,也可以直接定义切入点表达式
@AfterThrowing 用于定义异常通知。在使用时通常为其指定value属性值,该值可以是已有的切入点,也可以直接定义切入点表达式。另外,还有一个throwing属性用于访问目标方法抛出的异常,该属性值与异常通知方法中同名的形参一致
@After 用于定义后置(最终)通知。在使用时通常为其指定value属性值,该值可以是已有的切入点,也可以直接定义切入点表达式

示例代码如下:

package 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 // 对应<aop:aspect ref="myAspect">
@Component // 对应<bean id="myAspect" class="aspectj.xml.MyAspect"/>
public class MyAspect
{
	/**
	 * 定义切入点
	 */
	@Pointcut("execution(* dynamic.jdk.*.*(..))")
	private void myPointCut()
	{
		// 对应<aop:pointcut expression="execution(* dynamic.jdk.*.*(..))"
		// id="myPointCut"/>
	}

	/**
	 * 前置通知,使用Joinpoint接口作为参数获得目标对象信息
	 */
	@Before("myPointCut()") // 对应<aop:before method="before" pointcut-ref="myPointCut"/>
	public void before(JoinPoint jp)
	{
		System.out.print("前置通知:模拟权限控制");
		System.out.println(",目标类对象:" + jp.getTarget() + ",被增强处理的方法:" + jp.getSignature().getName());
	}

	/**
	 * 后置返回通知
	 */
	@AfterReturning("myPointCut()")
	public void afterReturning(JoinPoint jp)
	{
		System.out.print("后置返回通知:" + "模拟删除临时文件");
		System.out.println(",被增强处理的方法:" + jp.getSignature().getName());
	}

	/**
	 * 环绕通知 ProceedingJoinPoint是JoinPoint子接口,代表可以执行的目标方法 返回值类型必须是Object
	 * 必须一个参数是ProceedingJoinPoint类型 必须throws Throwable
	 */
	@Around("myPointCut()")
	public Object around(ProceedingJoinPoint pjp) throws Throwable
	{
		// 开始
		System.out.println("环绕开始:执行目标方法前,模拟开启事务");
		// 执行当前目标方法
		Object obj = pjp.proceed();
		// 结束
		System.out.println("环绕结束:执行目标方法后,模拟关闭事务");
		return obj;
	}

	/**
	 * 异常通知
	 */
	@AfterThrowing(value = "myPointCut()", throwing = "e")
	public void except(Throwable e)
	{
		System.out.println("异常通知:" + "程序执行异常" + e.getMessage());
	}

	/**
	 * 后置(最终)通知
	 */
	@After("myPointCut()")
	public void after()
	{
		System.out.println("最终通知:模拟释放资源");
	}

}

参考教材《JavaEE框架整合开发入门到实战》侵删。
本人现役大三小白一枚,如有不足之处还望指正。

posted @ 2020-03-12 16:26  Aaron`Joe  阅读(104)  评论(0)    收藏  举报