详细说明Spring--AOP

这篇博客较长,耐心读完或许会有“柳暗花明又一村”的感觉哦!

为什么?

      我先不说AOP是什么,我先说说为什么要用AOP,依照惯例,我还是先举一个例子:

  1. 先把项目结构展现出来:
    在这里插入图片描述

  2. 我们先在com.jd.calculator.service里定义一个接口ICalculatorService

    package com.jd.calculator.service;
    
    public interface ICalculatorService {
    
    	//定义几个简单的加减法
    	//加法
        int add(int a, int b);
    	//除法
        int div(int a, int b);
    	//乘法
        int mul(int a, int b);
    }
    
  3. 然后去实现这个接口:

    package com.jd.calculator.service;
    
    import org.springframework.stereotype.Service;
    
    @Service
    public class CalculatorService implements ICalculatorService{
        @Override
        public int add(int a, int b) {
            System.out.println("The add method begins");
            System.out.println("The parameters of the add method are"+a+","+b);
            int result = a + b;
            return result;
        }
    
        @Override
        public int div(int a, int b) {
            System.out.println("The div method begins");
            System.out.println("The parameters of the div method are"+a+","+b);
            int result = a / b;
            return result;
        }
    
        @Override
        public int mul(int a, int b) {
            System.out.println("The mul method begins");
            System.out.println("The parameters of the mul method are"+a+","+b);
            int result = a * b;
            return result;
        }
    }
    

      我们会发现上面的三个方法除了方法名和具体的计算不同,其他的几乎完全一样,这就造成代码的冗余,开发的效率也会很低。
      虽然我们可以再定义一个方法,把相同的部分放进去,然后其他方法再调用它。但假如我们的需求变了,不单单是在输出结果之前要输出一些东西,我们还想要输出结果之后再输出一些东西,出错了也要输出一些东西,那这时如果还采取这种方法就也会显得很复杂了。那这时该怎么办呢?
      这时就要用到AOP了。

是什么?

AOP(Aspect Oriented Programming 面向切面编程)是一种指在程序运行期间动态的将某段代码切入到指定方法的指定位置进行运行的编程方式,这种编程方式实现了代码复用,是对传统OOP(Object Oriented Programming,面向对象编程 )的补充。目前,Aspectj是Java社区里最完整最流行的AOP框架,在Spring 2.0以上版本中可以通过Aspectj注解基于XML配置AOP

AOP用到上面的例子就是只关注代码重复的部分。

怎么做?

第一种方法:通过注解来实现AOP
@Before:
  1. 首先需要配置一些jar包:
    在这里插入图片描述
  2. 在Spring 配置文件(也就是application.xml)里添加:
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>,它可以自动生成代理类(不理解没关系,接着往下看)
  3. com.jd.aspects包里自定义一个切面类:CalculatorAspects
package com.jd.aspects;

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;

@Aspect //要想生成代理类,要加这个注解
@Component
public class CalculatorAspects {

    @Before("execution(public int com.jd.calculator.service.CalculatorService.*(..))")//匹配这些的才能生成代理类
    public void before(JoinPoint js){
        Object [] args = js.getArgs();//获取参数
        String name = js.getSignature().getName();//获取方法名
        System.out.println("The "+name+" method begins");
        System.out.println("The parameters of the "+name+" method are "+args[0]+","+args[1]);
    }
}

我们一点一点说上面的代码:

  1. 根据@Component可以在IOC容器中自动实例化(即生成对象,这里就不做过多讨论了);

  2. aspects切面的意思,你可以理解成从某一点(或一系列点)切入,然后再切出。这个CalculatorAspects类 加上@Aspect这个注解,就表示一个切面(即代理类),至于从哪里切入,且看第3点;

  3. @Before("execution(public int com.jd.calculator.service.CalculatorService.*(..))"),我们先看Before,before什么意思?之前的意思,就是说要做一件事之前,先做另一件事情,我们看到

    		Object [] args = js.getArgs();//获取参数
            String name = js.getSignature().getName();//获取方法名
            System.out.println("The "+name+" method begins");
            System.out.println("The parameters of the "+name+" method are "+args[0]+","+args[1]);
    

    这段代码和CalculatorService类中的

    		System.out.println("The add method begins");
            System.out.println("The parameters of the add method are"+a+","+b);
    

    这段代码一样,具有同样的作用,而且都要在计算输出结果之前输出。这时你能够理解@Before这个注解的作用了吧:就是在做某件事之前,先做另一件事。那这时你又疑惑了,是哪件事之前呢?换句话说,切入点在哪呢?切入点就在public int com.jd.calculator.service.CalculatorService.*(..)),(*代表所有;..代表参数,可以没有,也可以有多个),意思是说在执行com.jd.calculator.service包中的CalculatorService类 里的public int * ()这种方法之前,要先执行加了@Before注解的方法。

  4. 加了这个@Aspect注解之后就会生成一个目标类(即@Before所指向的类)的代理类,代理什么?代理的就是重复代码的那一部分
    (以下这段话不影响其他点的理解,可以不用看)那么还有一个问题,即是谁生成的这个代理类?直接告诉你答案,当在<aop:aspectj-autoproxy></aop:aspectj-autoproxy>中加proxy-target-class="true"时,是由CGLIB生成的代理类,这个代理类是目标类的子类;若不加proxy-target-class="",(默认值为false),则是由JDK生成的代理类,这个代理类是接口实现类。

  5. JoinPoint就是连接点,它表示在程序中明确指明的点,就是说切点附近的东西,它里面常有的方法有:

    方法名作用例子
    getSignature()getName()获取目标方法名 add
    getDeclaringType().getSimpleName()获取目标类的简单类名CalculatorService
    getDeclaringTypeName()获取目标类的完整类名com.jd.calculator.service.CalculatorService
    Modifier.toString(JoinPoint.getSignature().getModifiers())获取目标方法声明的类型public
    Object[] getArgs()获取传入目标方法的参数[列表](从0开始)a,b
    getThis()代理对象自己com.jd.calculator.service.CalculatorService@28d18df5
    getTarget()被代理的对象com.jd.calculator.service.CalculatorService@28d18df5

@After@AfterReturning@AfterThrowing@Around

       好了,通过注解的方式实现AOP的方法已经说的大概了,到这里你是否会猜想:既然有了@Before,那会不会有@After,还真让你给说对了,而且不但有@After,还有@AfterReturning@AfterThrowing@Around
       这时聪明的你又想:既然都这样了,那它们会不会和@Before的用法差不多啊!没错,果然smart,@After@AfterReturning@AfterThrowing的用法确实和@Before的用法差不多,只是@AfterReturning@AfterThrowing还多了一些东西,且看下面慢慢道来:

  • @After的用法和@Before的用法完全一样,不一样的地方就是@After修饰的方法是在目标方法执行后才执行的

  • @AfterReturning,从名字中我们可以猜到被@AfterReturning修饰的方法是在目标方法执行后才执行的而且还会有一个返回通知:通常是计算的结果。实现的代码例如:

    	@AfterReturning(value = "execution(public int com.jd.calculator.service.CalculatorService.*(..))",returning = "obj")//obj就是返回通知的内容
        public void afterReturning(JoinPoint js,Object obj){
            String name = js.getSignature().getName();//获取方法名
            System.out.println("The "+name+" method result:"+obj);
        }
    
  • @AfterThrowing,同样的,我们可以看出被@AfterThrowing修饰的方法是在目标方法执行后才执行的,那我就想问Throwing什么意思?异常的意思。意思就是说被@AfterThrowing修饰的方法是在目标方法执行过程中出错,才执行的;如果目标方法没有出错,就不执行。实现的代码例如:

    	@AfterThrowing(value = "execution(public int com.jd.calculator.service.CalculatorService.*(..))",throwing = "e")
        public void afterThrowing(JoinPoint js,Throwable e){
            System.out.println("错误的信息:"+e.getMessage());
            e.printStackTrace();//将错误打印出来
        }
    

       你发现前几个注解都是见名知意,那@Around是干什么用的?我们先来思考一番:如果要在执行一个目标方法时,我们想分别在目标方法执行前、执行后、执行过程出错时都先输出一些东西、且返回返回一些值,那这时我们要写四个方法,还要分别标注@Before@After@AfterReturning@AfterThrowing四个注解,这样很麻烦,这时就可以用@Around了,@Around就是用来整合前四种的,它的结构如下:

try {
	try {
		doBefore();// @Before注解所修饰的方法
		method.invoke();// 执行目标对象内的方法(即目标方法)
	} finally {
		doAfter();// @After注解所修饰的方法
	}
	doAfterReturning();// @AfterReturning注解所修饰的方法
} catch (Throwable e) {
	doAfterThrowing();// @AfterThrowing注解所修饰的方法
}

上面的代码看懂了吧?好,看懂了我们来那上面的例子实战一番:

	@Around("execution(public int com.jd.calculator.service.CalculatorService.*(..))")
    public Object around(ProceedingJoinPoint js){
        try {
            Object [] args = js.getArgs();//获取参数
            String name = js.getSignature().getName();//获取方法名
            Object result = null;
            try {
                //Before
                System.out.println("The "+name+" method begins");
                System.out.println("The parameters of the "+name+" method are "+args[0]+","+args[1]);
                //目标方法
                result = js.proceed();
            }finally {
                //After
                System.out.println("The "+name+" method ends");
            }
            //AfterReturning
            System.out.println("The "+name+" method result:"+result);
            //返回结果
            return result;
        }catch (Throwable e){
            //AfterThrowing
            System.out.println("错误的信息:"+e.getMessage());
        }
        return -1;
    }

在这里有几点需要特别注意一下:

  1. 首先就是参数类型,@Around修饰的方法必须声明ProceedingJoinPoint类型的参数,该变量可以决定是否执行目标方法 ,这与上面四种生命的JoinPoint类型不同;
  2. @Around修饰的方法必须有返回值,且返回值类型为目标方法的返回值类型或其父类。而@Before@After@AfterRunning@AfterThrowing修饰的方法是没有返回值的。
@Pointcut

还有一点就是我们在实现AOP时,都是这样写注解的:

@Before("execution(public int com.jd.calculator.service.CalculatorService.*(..))")
@After("execution(public int com.jd.calculator.service.CalculatorService.*(..))")
@AfterReturning(value = "execution(public int com.jd.calculator.service.CalculatorService.*(..))",returning = "obj")
@AfterThrowing(value = "execution(public int com.jd.calculator.service.CalculatorService.*(..))",throwing = "e")
@Around("execution(public int com.jd.calculator.service.CalculatorService.*(..))")

       你发现了什么?哈哈,是不是还是代码冗余啊,所以这时可以用到另一个注解:@Pointcut,pointcut表示一组joinPoint(连接点),以上重复的代码可以通过单独自定义一个@Pointcut注解修饰的空方法来解决:

	@Pointcut("execution(public int com.jd.calculator.service.CalculatorService.*(..))")
    public void pointcut(){
    }

然后其他注解调用即可:

@Before("pointcut()")
@After("pointcut()")
@AfterReturning("pointcut()")
@AfterThrowing("pointcut()")
@Around("pointcut()")

是不是简便多了?

@order(num)

       如果一个目标方法匹配多个切面中相同类型增强方法(即上面四种注解修饰的方法),那这几个切面谁先执行、谁后执行呢?默认的都是按类名的字典顺序(A~Z)依次执行的;但如果加上 @order(num)就会按照指定的顺序依次执行所有匹配的切面,其中num数值越小,优先级越高(即被 @order(1)修饰的切面 比 被 @order(2)修饰的切面 先执行)。


好了,终于说完了用注解的方式去实现AOP,如果大家对AOP有了较好的理解,那接下来的用xml文件来配置AOP就更容易理解了。


第二种方法:通过xml文件来实现AOP:

这次我们通过xml文件来实现AOP,也就是说像什么@Aspects@Order@Before@After@Order等的注解都不需要再加了,

  1. 只需要自定义普通的类(但它通过配置后还起到切面的作用,所以我们就还叫它切面类)就可以了,例如:

    package com.jd.aspects;
    
    import org.aspectj.lang.JoinPoint;
    
    public class CalculatorAspects {
    
    	//定义普通的方法,然后在xml文件里进行配置
    
        public void before(JoinPoint js){
            Object [] args = js.getArgs();//获取参数
            String name = js.getSignature().getName();//获取方法名
            System.out.println("The "+name+" method begins");
            System.out.println("The parameters of the "+name+" method are "+args[0]+","+args[1]);
        }
    
        public void after(JoinPoint js){
            String name = js.getSignature().getName();//获取方法名
            System.out.println("The "+name+" method ends");
        }
    
        public void afterReturning(JoinPoint js,Object obj){
            String name = js.getSignature().getName();//获取方法名
            System.out.println("The "+name+" method result:"+obj);
        }
    
        public void afterThrowing(JoinPoint js,Throwable e){
            System.out.println("错误的信息:"+e.getMessage());
        }
    }
    
  2. 在Spring配置文件(也就是application.xml)里进行如下配置:

	<!--因为上面的CalculatorAspects类未加'@Component'注解,所以这里需要先通过<bean></bean>来实例化对象-->
	<bean id="calculatorAspects" class="com.jd.aspects.CalculatorAspects"></bean>
	<!--直接在xml里配置代理类-->
    <aop:config proxy-target-class="true">
        <aop:pointcut id="p" expression="execution(public int com.jd.calculator.service.CalculatorService.*(..))"/>
        
        <aop:aspect ref="calculatorAspects" order="1">
            <aop:before pointcut-ref="p" method="before"></aop:before>
            <aop:after pointcut-ref="p" method="after"></aop:after>
            <aop:after-returning pointcut-ref="p" method="afterReturning" returning="obj"></aop:after-returning>
            <aop:after-throwing pointcut-ref="p" method="afterThrowing" throwing="e"></aop:after-throwing>
        </aop:aspect>
    </aop:config>

我们会发现这个配置和使用注解有很多相似的地方,但更加简便:

  1. 其中的 proxy-target-class=""和上面讲的一样,这里就不再赘述了;

  2. 接着是pointcut和上面讲的也基本一样,这里指定一个id便于下面调用,expression=""指向目标类;

  3. 再往下:

    <aop:aspect ref="calculatorAspects" order="1">
    
    </aop:aspect>
    

    这个就是具体的某个切面类了,其中ref=""就是指向某个切面类,order=""就是这个切面类的优先执行顺序;

  4. 具体的:

    	<aop:before pointcut-ref="p" method="before"></aop:before>
        <aop:after pointcut-ref="p" method="after"></aop:after>
        <aop:after-returning pointcut-ref="p" method="afterReturning" returning="obj"></aop:after-returning>
        <aop:after-throwing pointcut-ref="p" method="afterThrowing" throwing="e"></aop:after-throwing>
    

    pointcut-ref=""调用上面的pointcut,指向目标类,method=""执行切面类中具体的方法,其中after-returningafter-throwing分别多了一个returning=""throwing=""

  5. 若直接使用around(环绕增强),则可以在CalculatorAspects类(代理类)中只定义一个aroud()方法即可:

    package com.jd.aspects;
    
    import org.aspectj.lang.JoinPoint;
    
    public class CalculatorAspects {
    
    	//定义普通的方法,然后在xml文件里进行配置
    
        public Object around(ProceedingJoinPoint js){
            try {
                Object [] args = js.getArgs();//获取参数
                String name = js.getSignature().getName();//获取方法名
                Object result = null;
                try {
                    //before
                    System.out.println("The "+name+" method begins");
                    System.out.println("The parameters of the "+name+" method are "+args[0]+","+args[1]);
                    //目标对象中的方法
                    result = js.proceed();
                }finally {
                    //after
                    System.out.println("The "+name+" method ends");
                }
                //afterReturning
                System.out.println("The "+name+" method result:"+result);
                return result;
            }catch (Throwable e){
                //AfterThrowing
                System.out.println("+++++++++++++++"+e.getMessage());
            }
            return -1;
        }
    }
    

    需要注意的点也与上面的一样!

    在:

    <aop:aspect ref="calculatorAspects" order="1">
    
    </aop:aspect>
    

    里只需要写入以下配置即可:

    <aop:around pointcut-ref="p" method="around"></aop:around>
    

好了(长舒一口气…),终于把AOP相关的主要内容给说完了,不知你从我这里收获到一些没有?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring AOP(面向切面编程)是 Spring 框架的一个重要组成部分,它允许我们在程序运行时动态地将代码织入到现有的 Java 类中,以实现横向关注点的划分,从而提高代码的可重用性、可维护性和可扩展性。 Spring AOP 的核心是切面(Aspect)、切点(Pointcut)和通知(Advice): 1. 切面(Aspect):切面是一个类似于 Java 类的组件,它定义了一些横切关注点(Crosscutting Concerns),例如日志记录、事务管理、安全控制等,这些横切关注点通常在多个对象和方法中都需要使用。 2. 切点(Pointcut):切点是一组匹配连接点(Join Point)的表达式,连接点是程序执行过程中的某个特定点,例如方法调用、异常抛出、字段访问等。 3. 通知(Advice):通知是在切面的某个切点上执行的一段代码,它定义了在何时、何地和如何织入到目标对象中。 Spring AOP 支持以下几种通知类型: - 前置通知(Before Advice):在目标方法执行之前执行的通知。 - 后置通知(After Advice):在目标方法执行之后执行的通知。 - 返回通知(After Returning Advice):在目标方法返回结果之后执行的通知。 - 异常通知(After Throwing Advice):在目标方法抛出异常之后执行的通知。 - 环绕通知(Around Advice):在目标方法执行之前和之后都可以执行的通知。 Spring AOP 还提供了一些其他的特性,例如切面优先级、引入(Introduction)、注解支持等。 使用 Spring AOP 可以帮助我们将横切关注点与业务逻辑进行分离,从而提高代码的可维护性和可扩展性,同时也可以减少重复代码的编写,提高开发效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不动声色的小蜗牛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值