SpringAOP详解

SpringAOP简介

AOP(Aspect Oriented Program):即面向切面编程

在面向切面编程的思想里面,把功能分为核心业务功能和周边功能

  • 所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务

  • 所谓的周边功能,比如性能统计,日志,事务管理等等

SpringAOP中将这些周边功能称为切面,将核心业务逻辑与这些周边功能分别独立开始,然后将它们交织在一起就是AOP需要做的事情

AOP将业务模块锁公共调用的、非业务逻辑的功能封装起来;这样的好处?

一:所以这些非核心业务功能都集中在一起,而不是分散到多处代码中,便于减少系统的重复代码,降低模块间的耦合度,有利于维护

二:服务模块更简洁,因为它们只包含主要关注点或核心功能的代码,次要的非业务功能(周边功能)被转移到切面中

AOP当中的概念

  • 通知(Advice):通知定义了切面是什么以及何时使用
  • 连接点(Join point):连接点指的是在应用执行过程中插入切面的一个点。
  • 切点(Pointcut):在哪些类或方法中加入周边功能
  • 切面(Aspect):切面是通知和切点的结合,简单来说:切面是什么以及在何时和何处完成其功能
  • 织入(Weaving):将切面在指定的连接点织入到目标对象中

使用注解来实现SpringAOP

首先我们新建一个SpringAop的包,创建一个HavingDinner类

package SpringAop;

import org.springframework.stereotype.Component;

@Component
public class HavingDinner{

    public void eat(){
        System.out.println("吃晚饭!");
    }
}

然后创建我们的切面类AOP

package SpringAop;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class AOP {
    @Before("execution(* SpringAop.HavingDinner.eat(..))")
    public void BuyIngredients(){
        System.out.println("购买食材!");
    }
    @Before("execution(* SpringAop.HavingDinner.eat(..))")
    public void Cook(){
        System.out.println("将食材进行烹饪!");
    }
    @After("execution(* SpringAop.HavingDinner.eat(..))")
    public void WashDishes(){
        System.out.println("吃完后洗碗!");
    }
}

然后创建一个配置类进行显示配置一下

package SpringAop;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class AopConfig {

}

创建我们的测试类

import SpringAop.AopConfig;
import SpringAop.HavingDinner;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestAop {

    @Test
    public void Test(){
        ApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
        HavingDinner havingDinner = (HavingDinner)context.getBean("havingDinner");
        havingDinner.eat();
    }
}

先看一下运行结果

简单解释一下

  • @Component注解表明这是一个组件类,并告诉Spring为这个类创建一个bean
  • @Aspect注解表明这不仅仅是一个Java类,还是一个切面
  • 至于AopConfig的作用以及@Component注解建议看我的这篇文章:Spring IoC和DI详解以及装配Bean
  • @EnableAspectJAutoProxy注解:启用自动代理功能
  • 如果不使用@EnableAspectJAutoProxy,那么AOP类即使使用@AspectJ注解,Spring也不会将其视为切面,AOP类中的注解也不会被解析,Spring也不会创建将其转换为切面的代理

下面仔细解释一下AOP类中的注解

下面是AspectJ的一些注解,上面只用了@Before和@After

注解通知
@After通知方法会在目标方法返回或抛出异常后调用
@AfterReturning通知方法会在目标方法返回后调用
@AfterThrowing通知方法会在目标方法抛出异常后调用
@Around通知方法会将目标方法封装起来
@Before通知方法会在目标方法调用之前执行

定义切点

execution(* SpringAop.HavingDinner.eat(..))
  • execution:代表执行方法的时候会触发
  • *:代表任意返回类型
  • SpringAop.HavingDinner:代表类的全限定名
  • eat():被拦截的方法名称

不过在上面的表达式中每个方法都拦截eat()方法,表达式因此很长,我们也可以改成下面这样

package SpringAop;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class AOP {
    @Pointcut("execution(* SpringAop.HavingDinner.eat(..))")
    public void eat(){}

    @Before("eat()")
    public void BuyIngredients(){
        System.out.println("购买食材!");
    }
    @Before("eat()")
    public void Cook(){
        System.out.println("将食材进行烹饪!");
    }
    @After("eat()")
    public void WashDishes(){
        System.out.println("吃完后洗碗!");
    }
}

上面我们使用@Pointcut注解,在切面中定义了一个可重用的切点

下面一起来看看功能最强大的环绕通知,它集成了前置通知和后置通知

package SpringAop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class AOP {
    @Pointcut("execution(* SpringAop.HavingDinner.eat(..))")
    public void eat(){}

    @Around("eat()")
    public void havingEat(ProceedingJoinPoint joinPoint){
        System.out.println("购买食材!");
        System.out.println("将食材进行烹饪!");
        try{
            joinPoint.proceed();
        } catch (Throwable e ){
            e.printStackTrace();
        }
        System.out.println("吃完后洗碗!");

    }

    /*@Before("eat()")
    public void BuyIngredients(){
        System.out.println("购买食材!");
    }
    @Before("eat()")
    public void Cook(){
        System.out.println("将食材进行烹饪!");
    }
    @After("eat()")
    public void WashDishes(){
        System.out.println("吃完后洗碗!");
    }

    @AfterThrowing("eat()")
    public void exe(){
        System.out.println("方法调用出现异常");
    }*/
}

运行代码

我们可以在havingEat方法中做任何事,当我们需要将控制权交给被通知方法时,调用proceed()方法即可,所以这个新的通知方法必须要接受一个ProceedingJoinPoint对象

通过XML配置开发SpringAOP

AOP 配置元素用途
<aop:advisor>定义AOP通知器
<aop:after>定义AOP后置通知(不管被通知的方法是否执行成功)
<aop:after-returning>定义AOP返回通知
<aop:after-throwing>定义AOP异常通知
<aop:around>定义AOP环绕通知
<aop:aspect>定义一个切面
<aop:aspectJ-autoproxy>启用@AspectJ注解驱动的切面
<aop:before>定义一个AOP前置通知
<aop:config>

                                                  顶层的AOP配置元素

<aop:declare-parents>以透明的方式为被通知的对象引入额外的接口
<aop:pointcut>定义切点

 

下面将上述例子改成XML配置的形式

首先是HavingDinner类

package SpringAop;

public class HavingDinner{

    public void eat(){
        System.out.println("吃晚饭!");
    }
}

AOP类

package SpringAop;
import org.aspectj.lang.ProceedingJoinPoint;
public class AOP {

    public void havingEat(ProceedingJoinPoint joinPoint){
        System.out.println("购买食材!");
        System.out.println("将食材进行烹饪!");
        try{
            joinPoint.proceed();
        } catch (Throwable e ){
            e.printStackTrace();
        }
        System.out.println("吃完后洗碗!");

    }

    public void BuyIngredients(){
        System.out.println("购买食材!");
    }
    public void Cook(){
        System.out.println("将食材进行烹饪!");
    }
    public void WashDishes(){
        System.out.println("吃完后洗碗!");
    }

    public void exce(){
        System.out.println("方法调用出现异常");
    }
}

spring.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"
       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.xsd">

    <bean id="havingDinner" class="SpringAop.HavingDinner"/>
    <bean id="aop" class="SpringAop.AOP" />
    <!--启用AspectJ自动代理 相当于注解@EnableAspectJAutoProxy-->
    <aop:aspectj-autoproxy />

    <aop:config>
        <!--where 在哪些地方做增加 -->
        <aop:pointcut id="eat" expression="execution(* SpringAop.HavingDinner.eat())"/>

        <!-- what 做什么增加-->
        <aop:aspect id="eatAspect" ref="aop">
            <!-- when:在什么时机(方法前/后/前后) -->
            <aop:around pointcut-ref="eat" method="havingEat"/>
            <aop:before method="BuyIngredients" pointcut-ref="eat"/>
            <aop:before method="Cook" pointcut-ref="eat"/>
            <aop:after method="WashDishes" pointcut-ref="eat"/>
            <aop:after-throwing method="exce" pointcut-ref="eat" />
        </aop:aspect>
    </aop:config>
</beans>

运行结果

参考资料

  • 《Java实战(第四版)》
  • 谷歌&&百度

 

 

 

 

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值