zoukankan      html  css  js  c++  java
  • 基于注解的Sping AOP详解

    一、创建基础业务

    package com.kang.sping.aop.service;
    
    import org.springframework.stereotype.Service;
    
    //使用注解@Service声明bean
    @Service
    public class UserService {
        
        public void add(){
            System.out.println("此时调用add()方法....");
        }
        
        public String  delete(){
            System.out.println("此时调用delete()方法....");
            return "delete的原返回值";
        }
        
        public void edit(){
            System.out.println("此时调用edit()方法 ....");
            int i = 5/0;
        }
    }



    二、配置Sping的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"
    	xmlns:context="http://www.springframework.org/schema/context"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
    		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
    
    	<!-- 自动扫描的包 -->
    	<context:component-scan base-package="com.kang.sping.aop"></context:component-scan>
    
    	<!-- 使 AspectJ 的注解起作用 -->
    	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
    </beans>
    



    三、创建增强处理类

    package com.kang.sping.aop.entities;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    /*
     * @Component注解声明bean
     * @Aspect注解声明这是一个切面
     * 在同一个连接点上应用不止一个切面时, 除非明确指定, 否则它们的优先级是不确定的.
     * 切面的优先级可以利用 @Order 注解指定.值越小, 优先级越高。
     */
    
    @Component
    @Aspect
    //@Order(0) 设置优先级
    public class Operator {
    	/*
    	 * @Pointcut将一个切入点声明成简单的方法. 切入点的方法体通常是空的, 因为将切入点定义与应用程序逻辑混在一起是不合理的。
    	 * 切入点方法的访问控制符同时也控制着这个切入点的可见性. 如果切入点要在多个切面中共用, 最好将它们集中在一个公共的类中. 
    	 * 在这种情况下, 它们必须被声明为 public. 在引入这个切入点时, 必须将类名也包括在内. 
    	 * 如果类没有与这个切面放在同一个包中, 还必须包含包名.
    	 * 
    	 * Spring AOP支持的切入点指示符,execution用来匹配执行方法的连接点
    	 * 1、@Pointcut("execution(* com.kang.sping.aop.service..*.*(..))")
    	 * 第一个*表示匹配任意的方法返回值,..(两个点)表示零个或多个。上面的第一个..表示service包及其子包,
    	 * 第二个*表示所有类,第三个*表示所有方法,第二个..表示方法的任意参数个数。
    	 * 2、@Pointcut("within(com.kang.sping.aop.service.*)")
    	 * within限定匹配方法的连接点,上面的就是表示匹配service包下的任意连接点
    	 * 3、@Pointcut("this(com.kang.sping.aop.service.UserService)")
    	 * this用来限定AOP代理必须是指定类型的实例,如上,指定了一个特定的实例,就是UserService
    	 * 4、@Pointcut("bean(userService)") bean也是非常常用的,bean可以指定IOC容器中的bean的名称
    	 */
    	@Pointcut("execution(* com.kang.sping.aop.service..*.*(..))")
    	public void pointCut() {//声明切入点方法为pointCut()
    	}
    
    	/*
    	 * @Before 表示在目标方法执行之前执行,@Before 里面的是切入点表达式或切入点函数 可以在通知方法中声明一个类型为 JoinPoint
    	 * 的参数. 然后就能访问链接细节,如方法名称和参数值. 
    	 * 获取方法名:joinPoint.getSignature().getName()
    	 * 获取参数列表:joinPoint.getArgs();
    	 */
    	@Before("pointCut()")
    	public void doBefore(JoinPoint joinPoint) {
    		// joinPoint.getSignature().getName();
    		// joinPoint.getArgs();
    		System.out.println("Before通知方法...");
    	}
    
    	@After("pointCut()")
    	public void doAfter(JoinPoint joinPoint) {
    		System.out.println("After通知方法...");
    	}
    
    	/*
    	 * @AfterReturning(pointcut = "pointCut()", returning = "returnVal")
    	 * returning:指定一个返回值形参名,代表了目标方法的返回值, 增强处理定义的方法可以通过该参数访问目标方法的返回值。
    	 * 因此必须在通知方法的签名中添加一个同名参数.afterReturn(JoinPoint joinPoint, Object returnVal)
    	 * 在运行时, Spring AOP 会通过这个参数传递返回值. 与@After的区别:
    	 * After增强处理无论目标方法如何结束(执行成功或者异常终止),都会执行.
    	 * AfterReturning增强处理只有在目标方法执行成功后才会运行。
    	 */
    	@AfterReturning(pointcut = "pointCut()", returning = "returnVal")
    	public void afterReturn(JoinPoint joinPoint, Object returnVal) {
    		System.out.println("AfterReturning通知方法获得的代理方法返回值是:"+returnVal);
    		System.out.println("AfterReturning通知方法...");
    	}
    
    	/*
    	 * @AfterThrowing 只在连接点抛出异常时才执行异常通知。将 throwing 属性添加到 @AfterThrowing 注解中,
    	 * 也可以访问连接点抛出的异常。 Throwable 是所有错误和异常类的超类. 所以在异常通知方法可以捕获到任何错误和异常.
    	 * 如果只对某种特殊的异常类型感兴趣, 可以将参数声明为其他异常的参数类型. 然后通知就只在抛出这个类型及其子类的异常时才被执行.
    	 */
    	@AfterThrowing(pointcut = "pointCut()", throwing = "error")
    	public void afterThrowing(JoinPoint joinPoint, Throwable error) {
    		System.out.println("AfterThrowing通知方法捕获的异常是:"+error);
    		System.out.println("AfterThrowing通知方法...");
    	}
    
    	/*
    	 * @Around 对于环绕通知来说, 连接点的参数类型必须是 ProceedingJoinPoint . 它是 JoinPoint 的子接口,
    	 * 允许控制何时执行, 是否执行连接点。在环绕通知中需要明确调用 ProceedingJoinPoint 的 proceed()
    	 * 方法来执行被代理的方法,可以传入一个Object[]对象,该数组中的值将被传入目标方法作为执行方法的实参。 如果没有调用proceed()就会导致通知被执行了, 但目标方法没有被执行。 注意:
    	 * 环绕通知的方法需要返回目标方法执行之后的结果, 即调用 joinPoint.proceed(); 的返回值, 否则会出现空指针异常
    	 * 事实上,在Around通知方法中可以修改原方法的返回值。而AfterReturning得到的返回值也是由Around决定的。
    	 */
    	@Around("pointCut()")
    	public Object around(ProceedingJoinPoint pjp) {
    		
    		try {
    			System.out.println("Around通知方法,在原方法执行前...");
    			Object obj=pjp.proceed();
    			obj="Around通知方法方法的返回值";
    			System.out.println("Around通知方法,在原方法执行后...");
    			System.out.println("Around通知方法的返回值是:"+obj);
    			return obj;
    		} catch (Throwable e) {
    			e.printStackTrace();
    		}
    		return null;
    		
    	}
    
    }




    四、编写测试方法

    1、测试没有返回值的方法(add)

    package com.kang.sping.aop.main;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import com.kang.sping.aop.service.UserService;
    
    public class Test {
        public static void main(String[] args) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
            UserService userService = (UserService) ctx.getBean("userService");
            userService.add();
        }
    }


    测试结果


    分析以上结果可以得出以下结论 :

    (1)通知执行的优先级

    进入目标方法时,先织入Around,再织入Before,退出目标方法时,先织入Around,再织入AfterReturning,最后才织入After。
    注意:Spring AOP的环绕通知会影响到AfterThrowing通知的运行,不要同时使用!同时使用也没啥意义。

    (2)Around可以改变原方法的返回值,而AfterReturning得到的返回值实际上是Around通知方法的返回值。

    2、测试有返回值的方法(delete)

    package com.kang.sping.aop.main;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import com.kang.sping.aop.service.UserService;
    
    public class Test {
        public static void main(String[] args) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
            UserService userService = (UserService) ctx.getBean("userService");
     
            Object obj=userService.delete();
            System.out.println("delete方法的最终返回值是:"+obj);
        }
    }

    测试结果:




    可以看到,Around的返回值影响了AfterReturning得到的返回值和原方法执行后的最终返回值。


    3、测试出现异常的方法(edit)(这里将@Around通知方法注释掉,否则会影响AfterThrowing的运行

    package com.kang.sping.aop.main;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import com.kang.sping.aop.service.UserService;
    
    public class Test {
        public static void main(String[] args) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
            UserService userService = (UserService) ctx.getBean("userService");
     
            userService.edit();
        }
    }

    测试结果:




    可以看到AfterThrowing方法的执行在After方法之后。


    五、总结

    Around增强处理可以获得目标方法的最大控制权,既可以完全控制目标方法的执行,也可以改变执行目标方法的参数,还可以改变目标方法的返回值。任何通知(Advice)方法可以将第一个参数定义为 org.aspectj.lang.JoinPoint 类型。JoinPoint 接口提供了一系列有用的方法, 比如 getArgs() (返回方法参数)、getThis() (返回代理对象)、getTarget() (返回目标)、getSignature() (返回正在被通知的方法相关信息)和 toString() (打印出正在被通知的方法的有用信息。)


  • 相关阅读:
    springboot中如何向redis缓存中存入数据
    elasticSearch索引库查询的相关方法
    java客户端的elasticSearch索引库的相关操作
    lucene索引的增、删、改
    lucene的索引查询
    框架
    GG
    总结
    阿里的代码规范检查工具
    传统架构与SOA架构的区别和特点:
  • 原文地址:https://www.cnblogs.com/kangsir/p/6653244.html
Copyright © 2011-2022 走看看