Spring之AOP

本文深入探讨Spring框架中的AOP(面向切面编程)功能,通过实例演示如何使用Spring AOP解决业务统计和安全验证等场景,同时介绍AOP的基本概念、切点表达式及Advice类型。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

     上两周写了两篇Spring IOC相关的文章:Spring使用之IOCSpring之IOC补充主要讲Spring的核心功能IOC。Spring作为一个轻量级容器,其核心功能就是IOC。但也不仅仅是IOC,它提供的另一个核心功能就是AOP(Aspect Oriented Programming)即面向切面编程。

一、Spring AOP概述

(一)、描述

    面向对象编程已经给我们提供了对现实业务很好的抽象,也实现数据的封装,既然我们已经有OOP,那么我们为什么还需要AOP呢??你想一想这样一个业务场景,假如我们银行有500个业务,假如有一天有这么一个需求,要统计每天所有业务被操作的总数??你怎么实现??每个业务逻辑方法加上一个统计??那如果我们有成千上万的业务呢??你每个业务方法都去修改,这样的工作量会有多大,况且统计好像不是业务,不应该写在业务方法里面,还有你修改了所有业务方法,那是不是所有的业务都得重新测试,有了AOP这个问题就迎刃而解。

Spring AOP就如同在选定逻辑方法上统一织入一段逻辑,我们可织入统计的逻辑,也可做安全验证。


(二)、切面编程相关的概念

1、 Joinpoint 连接点(《Spring 实战》中的翻译),可被织入操作的系统点即为Joinpoint,即可被加入逻辑代码的点,常见Joinpoint类型:

(1)、方法调用(方法调用处,调用对象上的执行点)。

(2)、方法调用执行(方法内,被调用方法逻辑执行时点,Spring仅支持该类JoinPoint)。

(3)、构造方法调用。

(4)、类初始化(静态类型,静态块初始化时点)

2、PointCut(切点) 用来描述一组Joinpoint,指定系统中符合条件的一组Joinpoint。spring通常通过AspectJ的切点表达式来描述。

在Spring中所有的方法方法都可以作为连接点,但是我们不需要统计所有方法的调用次数,我们可能只需要统计用户业务的总数,这是我们就得通过表达式来指定我们需要统计的业务方法,通过表达式指定的的连接点就是切点,我们要织入的统计代码就应用在这些切点上。

3、Advice(通知) :织入PointCut的逻辑内容 。

显然我们上面的业务需求中需要做的是统计,Advice根据织入内容在PointCut执行位置又可分为一下几种

(1)、Before Advice 指定PointCut方法执行之前执行。

(2)、After Advice 指定位置之后执行,After Advice包括后面三种,执行位置如下方法展示(After Advice、After Returning Advice 、After throwing Advice)。

执行位置分别对应如下:

public void mockMethod(){
	//before Advice 织入逻辑执行点
	try{
            ...........
	    retrun;
	    //after Returning Advice 织入逻辑执行点
	}catch(Exception e){
	    //After throwing Advice 织入逻辑执行点
	}finally{
	    //After Advice 织入逻辑执行点
	}
}复制代码

 (3)AroundAdvice ,方法执行前后,这是比较特殊的通知,之前的两种通知都不能阻止调用业务方法,但这个通知可阻止对业务方法的调用,比如,我们可使用AroudAdvice在逻辑方法前加一道安全验证,验证不通过则不让调相应的逻辑方法。(这里不理解不要紧,后面会给例子)。

二、Spring切面编程初印象

接下来,我们就上面说的银行的例子来写个demo,见识见识AOP。现在我们需要统计业务办理数,我们可直接在业务方法执行前做一个统计,所以我们通知使用Before Advice,Spring使用切面也又两种方式:XML配置、注解。

(一)、使用XML配置方式

使用AOP除了之前第一篇文章里说的需要引进Spring核心包外,还需要引进新的两个包

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aop</artifactId>
  <version>${org.springframework.version}</version>
</dependency>

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.6.8</version>
</dependency>
复制代码

项目结构如下


我们通过一个静态的int变量来统计所有业务的使用数,CountBusiness如下:

package cn.springstudy.inspectadvice;

import org.springframework.aop.BeforeAdvice;

public class CountBusiness{

    public static int count;

    public void beforeBusinessCount(){
        count++;
    }
}
复制代码

我们建所有银行业务都放在cn.springstudy.bank下,我们现在加上两个银行业务处理类:

package cn.springstudy.bank;

//账号业务
public class AccountBusiness {

    //更改用户账号绑定电话
    public void chgAccountPhone(){
        System.out.println("Change Account Phone");
    }
    //开通新账号
    public void createNewAccount(){
        System.out.println("Create New Account");
    }
    //更改用户信息
    public void chgAccountInfo(){
        System.out.println("Change Account Info Success");
    }
    //销户
    public void delAccount(){
        System.out.println("Delete Account");
    }
}复制代码

package cn.springstudy.bank;

//转账业务
public class TransferBusiness {

    //转账
    public void transfer(){
        System.out.println("Transfer A Account To B Account");
    }
}
复制代码

接下来,需要在配置文件中配置

<?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">
    <!-- 使用Spring AOP -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    <bean id="accountBusiness" class="cn.springstudy.bank.AccountBusiness"></bean>
    <bean id="transferBusiness" class="cn.springstudy.bank.TransferBusiness"></bean>
    <!--Advice类  -->
    <bean id="countBusiness" class="cn.springstudy.inspectadvice.CountBusiness"></bean>
    <!--AOP配置 -->
    <aop:config>
        <!-- 配置切面 -->
        <aop:aspect ref="countBusiness">
<!--切点,表示执行cn.springstudy.bank下的任意类的任意方法,expression中的表单
       是即为AspectJ表达式,指定需要被织入逻辑的切点,这里表达式可先不深究,后面会讲解 -->
            <aop:pointcut id="bankbusinesspointcut" 
                 expression="execution(* cn.springstudy.bank.*.*(..))">
            </aop:pointcut>
<!--配置BeforeAdvice 执行织入逻辑为bean 
      countBusiness的beforeBusinessCount方法,应用在bankbusinesspointcut切点上-->
            <aop:before method="beforeBusinessCount" 
                   pointcut-ref="bankbusinesspointcut" >
            </aop:before>
        </aop:aspect>
    </aop:config>
</beans>复制代码

接下来就可以做测试了

package cn.springstudy.spring;

import cn.springstudy.bank.AccountBusiness;
import cn.springstudy.bank.TransferBusiness;
import cn.springstudy.inspectadvice.CountBusiness;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringStudy {

    public static void main(String arg[]){
        //方式一
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");;
        AccountBusiness accountBusiness = (AccountBusiness) classPathXmlApplicationContext.getBean("accountBusiness");
        //办理修改账号信息业务
        accountBusiness.chgAccountInfo();
        //办理修改绑定电话业务
        accountBusiness.chgAccountPhone();
        TransferBusiness transferBusiness = (TransferBusiness) classPathXmlApplicationContext.getBean("transferBusiness");
         //办理转账业务
        transferBusiness.transfer();
        System.out.println(CountBusiness.count);
    }
}复制代码

OK,执行结构如我们想的一样,统计出来的业务办理量是3:


可以看到整个AOP功能的使用还是很清爽的,我们只是增加了一个统计的类CountBusiness,还有增加了点配置,可以看出Spring的理念之一:非侵入式。有一天你不想用Spring了,那些业务类、CountBusiness依旧是Java类,还是可以使用。

(二)、使用注解。

我们只需建CountBusiness类做下修改

package cn.springstudy.inspectadvice;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.aop.BeforeAdvice;
//这是一个切面类
@Aspect
public class CountBusiness implements BeforeAdvice {
    public static int count;
    //定义切点
    @Pointcut("execution(* cn.springstudy.bank.*.*(..))")
    public void declarePointCut(){}
    //before Advice 执行逻辑
    @Before("declarePointCut()")
    public void beforeBusinessCount(){
        count++;
    }
}
复制代码

此时我们的XML配置就不在需要AOP配置了,只需将CountBusiness配置成一个普通Bean即可,配置文件改为一下,即可执行测试方法,执行结果同上。

<?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:c="http://www.springframework.org/schema/c"
       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/c
       http://www.springframework.org/schema/c/spring-c.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    <bean id="accountBusiness" class="cn.springstudy.bank.AccountBusiness"></bean>
    <bean id="transferBusiness" class="cn.springstudy.bank.TransferBusiness"></bean>
    <!--切面类-->
    <bean id="countBusiness" class="cn.springstudy.inspectadvice.CountBusiness"></bean>
</beans>
复制代码

三、更多Spring AOP知识

(一)、Around Advice

Around Advice:前面我们说过使用Around Advice可以可阻止对业务方法的调用。接下来我们就来体验一下。我们在操作用户业务前面加个验证。

我们增加一个用户类:

package cn.springstudy.vo;

public class User {

    private String name;
    private String passWord;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassWord() {
        return passWord;
    }

    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }
}复制代码

再更改下账号业务的类,办理业务,顺便把要办理的用户传过来

package cn.springstudy.bank;

import cn.springstudy.vo.User;
//账号业务
public class AccountBusiness {

    //更改用户账号绑定电话
    public void chgAccountPhone(User user){
        System.out.println("Change Account Phone");
    }

    //开通新账号
    public void createNewAccount(User user){
        System.out.println("Create New Account");
    }

    //更改用户信息
    public void chgAccountInfo(User user){
        System.out.println("Change Account Info Success");
    }

    //销户
    public void delAccount(User user){
        System.out.println("Delete Account");
    }
}复制代码

接下来是Advice 类:

package cn.springstudy.inspectadvice;

import cn.springstudy.vo.User;
import org.aspectj.lang.ProceedingJoinPoint;

public class Authenticate {

    public void aroundAdvice(ProceedingJoinPoint joinPoint){
        //拿到调用切点方法的参数
        Object[] arg = joinPoint.getArgs();
        if (arg[0] instanceof User){
            //如果用户名是zhangsan,不让调切点方法,直接return
            if ("zhangsan".equals(((User)arg[0]).getName())){
                System.out.println("zhangsan 异常用户,不让办理业务");
                return;
            }
            try {
                joinPoint.proceed();//执行切点方法,即逻辑方法
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        }
    }
}复制代码

配置文件

<?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:c="http://www.springframework.org/schema/c"
       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/c
       http://www.springframework.org/schema/c/spring-c.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    <bean id="accountBusiness" class="cn.springstudy.bank.AccountBusiness"></bean>
    <bean id="transferBusiness" class="cn.springstudy.bank.TransferBusiness"></bean>
    <bean id="authenticate" class="cn.springstudy.inspectadvice.Authenticate"></bean>

    <aop:config>
        <aop:aspect ref="authenticate">
            <aop:around method="aroundAdvice" pointcut-ref="authenticatepointcut"></aop:around>
        </aop:aspect>
    </aop:config>

</beans>
复制代码

接下来就可以测试了

public class SpringStudy {
    public static void main(String arg[]){
        AccountBusiness accountBusiness = (AccountBusiness) classPathXmlApplicationContext.getBean("accountBusiness");
        User user = new User();
        user.setName("zhangsan");
        user.setPassWord("123456");
        //zhangsan 过来办理更改用户信息的业务
        accountBusiness.chgAccountInfo(user);  
    }
}
复制代码

执行结果,可以看到chgAccountInfo方法里东西并没有执行。


如果是李四办理业务呢,没错李四可以正常调用业务。

public class SpringStudy {
    public static void main(String arg[]){
        AccountBusiness accountBusiness = (AccountBusiness) classPathXmlApplicationContext.getBean("accountBusiness");
        User user = new User();
        user.setName("lisi");
        user.setPassWord("123456");
        //zhangsan 过来办理更改用户信息的业务
        accountBusiness.chgAccountInfo(user);  
    }
}复制代码


(二)、ASpectJ表达式

前面说到Apring AOP支持部分SpectJ切点语言来指定要应用切面的切点,那么ASpectJ切点语言究竟是怎样的语法。

支持的部分指示符包括:execution、within、this、target、args

1、execution是最常用的了,用来指定切点方法,接下来就看看之前我们的配置:

execution(* cn.springstudy.bank.*.*(..))复制代码

(1)、其中第一个*表示返回类型可以是任意类型,当然我们也可以直接指定,比如:

execution(int cn.springstudy.bank.*.*(..))        表示匹配的返回类型为int的方法。execution(cn.springstudy.vo.User cn.springstudy.bank.AccountBusiness.*(..))      匹配的是返回类型为User的方法。

(2)、第二个*号表示任意类,第三个*号表示任意方法,括号里面两个点表示参数可以是任意个数、任意类型。

所以execution(* cn.springstudy.bank.*.*(..))这个表达式表示的就是cn.springstudy.bank包下任意类,类中任意方法,返回类型,和参数都不做限定,所以指定的切点就是AccountBusiness、TransferBusiness这两个类中的所有方法。



2、within匹配所以持有指定注解类型内的方法

within(cn.springstudy.bank.*)    效果同   execution(* cn.springstudy.bank.*.*(..))

within(cn.springstudy.bank..*) 其中..表示包括子包。所以这个表示cn.springstudy.bank下,包括子包中的所有类下的方法。

3、args:使用“args(参数类型列表)”匹配当前执行的方法传入的参数为指定类型的执行方法,例:args(cn.springstudy.vo.User) 表示参数为User的方法。

4、target,用于匹配类型,例:target(cn.springstudy.bank.VIPBusiness)实现了VIPBusiness接口的类都会被匹配上

以上的所有指示器还可以通过 and 和 or这些逻辑运算符结合在一起,例:表示匹配cn.springstudy.bank包下类的方法,且方法参数类型为User

within(cn.springstudy.bank.*) and args(cn.springstudy.vo.User)复制代码

5、 this(),这个我测试,没看出跟target有什么区别,暂时不清楚跟target的区别。


未完,待续...............


转载于:https://juejin.im/post/5bf6a8396fb9a049b82a18ce

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值