zoukankan      html  css  js  c++  java
  • 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>
    
  • 相关阅读:
    常用知识点集合
    LeetCode 66 Plus One
    LeetCode 88 Merge Sorted Array
    LeetCode 27 Remove Element
    LeetCode 26 Remove Duplicates from Sorted Array
    LeetCode 448 Find All Numbers Disappeared in an Array
    LeetCode 219 Contains Duplicate II
    LeetCode 118 Pascal's Triangle
    LeetCode 119 Pascal's Triangle II
    LeetCode 1 Two Sum
  • 原文地址:https://www.cnblogs.com/chaozhengtx/p/13792410.html
Copyright © 2011-2022 走看看