AOP相关

AOP

(一)动态代理

1. 方式

  • 基于接口实现动态代理: JDK动态代理
  • 基于继承实现动态代理: Cglib、Javassist动态代理

2. 一个方法

Object proxyObject = Proxy.newProxyInstance(ClassLoader classLoader, Class[] interfces, InvocationHandler h)

方法的作用:动态的创建实现了interfaces数组中所有指定接口的实现类对象:

参数:

  • ClassLoader:类加载器,把.class文件加载到内存,形成Class对象。
  • Class[] interfaces:指定要实现的接口们
  • InvocationHandler:代理对象的所有方法(个别不执行,getClass())都会调用InvocationHandler的invoke()方法

3. InvocationHandler

public Object invoke(Object proxy, Method method, Object[] args)

这个invoke()方法什么时候被调用:在调用代理对象所实现接口中的方法时。

invoke()方法的参数:
  • Object proxy:当前对象,即代理对象(在调用谁的方法)
  • Method method:当前被调用的方法(目标方法)
  • Object[] args:实参(传入目标方法中需要传入的实际参数)

4. 动态代理作用

最终是学习AOP(面向切面编程),它与装饰者模式有点相似,它比装饰者模式还要灵活!

目标对象:被增强的对象
代理对象:需要目标对象,然后在目标对象上添加了增强后的对象!
目标方法:增强的内容

代理对象 = 目标对象 + 增强

5. 代码实现

public Object getProxy(){
  //获取当前类的类加载器,用来加载代理对象所属类
  ClassLoader loader = this.getClass().getClassLoader();
  //获取目标对象实现的所有接口的Class,代理类回合目标类实现相同的接口,最终通过代理对象实现功能
  Class[] interfaces = mathImpl.getClass().getInterfaces();
  
  return Proxy.newProxyInstance(loader,interfaces,new InvocationHander(){
    //代理对象实现功能的方式
    @Override
    public Object invoke(Object proxy,Method method,Object[] args)throws Throwale{
      try{
        MyLogger.before(method.getName(), Arrays.toString(args));//增强的功能
        Object result = method.invoke(mathImpl, args);//动态代理对象实现功能
        MyLogger.after(method.getName(), result);//增强的功能
        return result;
      } catch (Exception e) {
					MyLogger.throwing();
					e.printStackTrace();
				} finally {
					System.out.println("哪都有我");
				}
				return null;
    }
  });
}

(二)AOP概述

AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论,是对传统 OOP(Object-Oriented Programming,面向对象编程)的补充。

  • 面向对象 纵向继承机制

  • 面向切面 横向抽取机制

AOP编程操作的主要对象是切面(aspect),而切面用于模块化横切关注点(公共功能)。在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能应用在哪里,以什么方式应用,并且不必修改受影响的类。这样一来横切关注点就被模块化到特殊的类里——这样的类我们通常称之为“切面”

(三)AOP术语

横切关注点

每个方法中抽取出来了的同一类非核心业务

切面(Aspect)

封装横切关注点信息的类,每个关注点体现为一个通知方法。

通知(Advice)

切面必须要完成的各个具体工作,就是在其它类中的横切关注点。

分为五种通知

① @Before:前置通知,在方法执行之前执行

② @After:后置通知,在方法执行之后执行

③ @AfterRunning:返回通知,在方法返回结果之后执行

④ @AfterThrowing:异常通知,在方法抛出异常之后执行

⑥ @Around:环绕通知,围绕着方法执行

目标(Target)

被通知的对象,就是被的代理的对象

代理(Proxy)

向目标对象应用通知之后创建的代理对象

连接点(Joinpoint)

就是横切关注点抽取的位置,例如:类某个方法调用前、调用后、方法捕获到异常后等

切入点(pointcut)

相对于代理类来说的,就是找到连接点的位置,在此位置切入方法。

切入表达式

@Before(value = "execution(public int com.atguigu.spring.aop.MathImpl.add(int, int))")

(四)AspectJ

AspectJ:Java社区里最完整最流行的AOP框架。

1. 在Spring中启用AspectJ注解支持

1)导包

com.springsource.net.sf.cglib-2.2.0.jar

com.springsource.org.aopalliance-1.0.0.jar

com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar

spring-aop-4.0.0.RELEASE.jar

spring-aspects-4.0.0.RELEASE.jar

2)引入aop命名空间

3)配置

<aop:aspectj-autoproxy>
当Spring IOC容器侦测到bean配置文件中的<aop:aspectj-autoproxy>元素时,会自动为	与AspectJ切面匹配的bean创建代理


  <context:component-scan base-package="com.atguigu.spring.aop"></context:component-scan>

  <!-- 开启aspectJ的自动代理功能 -->
  <aop:aspectj-autoproxy />

2. 用AspectJ注解声明切面

  1. 要在 Spring 中声明 AspectJ 切面,只需要在IOC容器中将切面声明为 bean 实例

  2. 当在SpringIOC 容器中初始化 AspectJ 切面之后,SpringIOC 容器就会为那些与 AspectJ 切面相匹配的 bean 创建代理

  3. 在 AspectJ注解中,切面只是一个带有@Aspect 注解的 Java 类,他往往要包含很多通知。

  4. 通知是标识有某种注解的简单的Java 方法

  5. AspectJ 支持5中类型的通知注解:

    ① @Before:前置通知,在方法执行之前执行

    ② @After:后置通知,在方法执行之后执行

    ③ @AfterRunning:返回通知,在方法返回结果之后执行

    ④ @AfterThrowing:异常通知,在方法抛出异常之后执行

    ⑥ @Around:环绕通知,围绕着方法执行

(五)AOP细节

1. 切入点表达式

@Before(value="execution(* com.atguigu.spring.aop.*.*(..))")
     * 第一个*代表任意的访问修饰符和返回值类型
     * 第二个*代表任意类
     * 第三个*代表类中任意方法
     * ..代表任意的参数列表

注意:在AspectJ中,切入点表达式可以通过 “&&”、“||”、“!”等操作符结合起来

2. 当前连接点细节

切入点表达式通常都会是从宏观上定位一组方法,和具体某个通知的注解结合起来就能够确定对应的连接点。那么就一个具体的连接点而言,我们可能会关心这个连接点的一些具体信息,例如:当前连接点所在方法的方法名、当前传入的参数值等等。这些信息都封装在JoinPoint接口的实例对象中。

@Before(value="execution(* com.atguigu.spring.aop.*.*(..))")
public void beforeMethod(JoinPoint joinPoint) {
  Object[] args = joinPoint.getArgs();//获取方法的参数
  String methodName = joinPoint.getSignature().getName();//获取方法名
  System.out.println("method:"+methodName+",arguments:"+Arrays.toString(args));
}

3. 通知

  • 在具体的连接点上要执行的操作。

  • 一个切面可以包括一个或者多个通知。

  • 通知所使用的注解的值往往是切入点表达式

1)前置通知

  • 前置通知:在方法执行之前执行的通知

  • 使用@Before注解

2)后置通知

  • 后置通知:后置通知是在连接点完成之后执行的,即连接点返回结果或者抛出异常的时候。作用于方法的finally语句块,即不管有没有异常都会执行

  • 使用@After注解

3)返回通知

  • @AfterReturning:将方法标注为返回通知
  • 返回通知:作用于方法执行之后,就是try{} 语句块中的最后一句
  • 在返回通知中,只要将returning属性添加到@AfterReturning注解中,就可以访问连接点的返回值。该属性的值即为用来传入返回值的参数名称
  • 要想在方法中使用,必须在方法的形参中设置和变量名相同的参数名的参数(必须在通知方法的签名中添加一个同名参数。在运行时Spring AOP会通过这个参数传递返回值)

4)异常通知

  • 异常通知:@AfterThrowing:将方法标注为异常通知(例外通知),只在连接点抛出异常时才执行异常通知

  • 将throwing属性添加到@AfterThrowing注解中,也可以访问连接点抛出的异常。Throwable是所有错误和异常类的顶级父类,所以在异常通知方法可以捕获到任何错误和异常。

  • 如果只对某种特殊的异常类型感兴趣,可以将参数声明为其他异常的参数类型。然后通知就只在抛出这个类型及其子类的异常时才被执行

5)环绕通知

  • 环绕通知是所有通知类型中功能最为强大的,能够全面地控制连接点,甚至可以控制是否执行连接点。

  • 对于环绕通知来说,连接点的参数类型必须是ProceedingJoinPoint。它是 JoinPoint的子接口,允许控制何时执行,是否执行连接点。

  • 在环绕通知中需要明确调用ProceedingJoinPoint的proceed()方法来执行被代理的方法。如果忘记这样做就会导致通知被执行了,但目标方法没有被执行。

  • 注意:环绕通知的方法需要返回目标方法执行之后的结果,即调用 joinPoint.proceed();的返回值,否则会出现空指针异常。

代码

package com.atguigu.spring.aop;

import java.util.Arrays;

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.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Aspect//标注当前类为切面
@Order(1)//定义切面作用的优先级,值越小优先级越高,默认值为int的最大值
public class MyloggerAspect {
	//重用切入点定义
	@Pointcut(value="execution(* com.atguigu.spring.aop.*.*(..))")
	public void test() {}

	/**
	 * @Before:将方法指定为前置通知
	 * 必须设置value,其值为切入点表达式
	 * 前置通知:作用于方法执行之前
	 */
	//@Before(value = "execution(public int com.atguigu.spring.aop.MathImpl.add(int, int))")
	@Before(value="test()")
	public void beforeMethod(JoinPoint joinPoint) {
		Object[] args = joinPoint.getArgs();//获取方法的参数
		String methodName = joinPoint.getSignature().getName();//获取方法名
		System.out.println("method:"+methodName+",arguments:"+Arrays.toString(args));
	}
	
	/**
	 * @After:将方法标注为后置通知
	 * 后置通知:作用于方法的finally语句块,即不管有没有异常都会执行
	 */
	//@After(value="execution(* com.atguigu.spring.aop.*.*(..))")
	@After(value="test()")
	public void afterMethod() {
		System.out.println("后置通知");
	}
	
	/**
	 * @AfterReturning:将方法标注为返回通知
	 * 返回通知:作用于方法执行之后
	 * 可通过returning设置接收方法返回值的变量名
	 * 要想在方法中使用,必须在方法的形参中设置和变量名相同的参数名的参数
	 */
	//@AfterReturning(value="execution(* com.atguigu.spring.aop.*.*(..))", returning="result")
	@AfterReturning(value="test()", returning="result")
	public void afterReturningMethod(JoinPoint joinPoint, Object result) {
		String methodName = joinPoint.getSignature().getName();
		System.out.println("method:"+methodName+",result:"+result);
	}
	
	/**
	 * @AfterThrowing:将方法标注为异常通知(例外通知)
	 * 异常通知(例外通知):作用于方法抛出异常时
	 * 可通过throwing设置接收方法返回的异常信息
	 * 在参数列表中课通过具体的异常类型,来对指定的异常信息进行操作
	 */
	//@AfterThrowing(value="execution(* com.atguigu.spring.aop.*.*(..))", throwing="ex")
	@AfterThrowing(value="test()", throwing="ex")
	public void afterThrowingMethod(ArithmeticException ex) {
		System.out.println("有异常了,messages:"+ex);
	}
	
	//环绕通知
	@Around(value="execution(* com.atguigu.spring.aop.*.*(..))")
	public Object aroundMethod(ProceedingJoinPoint joinPoint) {
		
		Object result = null;
		
		try {
			//前置通知
			System.out.println("前置通知");
			result = joinPoint.proceed();//执行方法
			//返回通知
			System.out.println("返回通知");
			return result;
		} catch (Throwable e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			//异常通知
			System.out.println("异常通知");
		} finally {
			//后置通知
			System.out.println("后置通知");
		}
		
		return -1;
	}
}

4. 重用切入点定义 和 指定切面的优先级

优先级

  1. 在同一个连接点上应用不止一个切面时,除非明确指定,否则它们的优先级是不确定的。

  2. 切面的优先级可以通过实现Ordered接口或利用@Order注解指定。

  3. 实现Ordered接口,getOrder()方法的返回值越小,优先级越高。

  4. 若使用@Order注解,序号出现在注解中

(六)以XML方式配置切面

​ 除了使用AspectJ注解声明切面,Spring也支持在bean配置文件中声明切面。这种声明是通过aop名称空间中的XML元素完成的。

​ 正常情况下,基于注解的声明要优先于基于XML的声明。通过AspectJ注解,切面可以与AspectJ兼容,而基于XML的配置则是Spring专有的。由于AspectJ得到越来越多的 AOP框架支持,所以以注解风格编写的切面将会有更多重用的机会。

1. 配置

在bean配置文件中,所有的Spring AOP配置都必须定义在<aop:config>元素内部。对于每个切面而言,都要创建一个<aop:aspect>元素来为具体的切面实现引用后端bean实例。

切面bean必须有一个标识符,供<aop:aspect>元素引用。

2. 声明切入点

  1. 切入点使用<aop:pointcut>元素声明。

  2. 切入点必须定义在<aop:aspect>元素下,或者直接定义在<aop:config>元素下。

​ ① 定义在<aop:aspect>元素下:只对当前切面有效

​ ② 定义在<aop:config>元素下:对所有切面都有效

  1. 基于XML的AOP配置不允许在切入点表达式中用名称引用其他切入点。

3. 声明通知

  1. 在aop名称空间中,每种通知类型都对应一个特定的XML元素。

  2. 通知元素需要使用<pointcut-ref>来引用切入点,或用<pointcut>直接嵌入切入点表达式。

  3. method属性指定切面类中通知方法的名称

	<context:component-scan base-package="com.atguigu.spring.aopxml"></context:component-scan>
	<!-- 配置 -->
	<aop:config>
    
    <!-- 声明切面 -->
		<aop:aspect ref="myLogger">
      
      <!-- 声明切入点 -->
			<aop:pointcut expression="execution(* com.atguigu.spring.aopxml.*.*(..))" id="cut"/>
			
      <!-- 声明通知的两种方式 -->
      <!-- <aop:before method="before" pointcut="execution(* com.atguigu.spring.aopxml.*.*(..))"/> -->
			<aop:before method="before" pointcut-ref="cut"/>
      
		</aop:aspect>
	</aop:config>
原文地址:https://www.cnblogs.com/chaozhengtx/p/13792410.html