Spring(4)--AOP(面向切面编程)

本文详细介绍了Spring AOP的概念,包括连接点、切入点、通知、目标、织入、代理等核心概念,并展示了如何基于XML配置和注解实现AOP的前置、后置、环绕和异常通知。通过具体的代码示例,阐述了如何在系统中实现日志管理、事务控制等功能,帮助开发者更好地理解和应用面向切面编程。

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

1. Spring AOP 简介

Spring AOP 是基于 AOP 编程模式的一个框架,它的使用有效减少了系统间的重复代码,达到了模块间的耦合目的。

AOP 的全称是“Aspect Oriented Programming”,即面向切面编程,它将业务逻辑的各个部分进行隔离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率。

程序需要------1.业务需求------需要实现程序的具体核心功能----添加用户

                       2.系统需求------实现程序的辅助功能----记录系统运行日志

AOP 采取横向抽取机制,取代了传统纵向继承体系的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。
       目前最流行的 AOP 框架有两个,分别为 Spring AOP 和 AspectJ。Spring AOP包含了AspectJ,因此本文只演示Spring AOP。

AOP 的相关术语

名称

说明

Joinpoint(连接点)

指那些被拦截到的点,在 Spring 中,可以被动态代理拦截目标类的方法。

Pointcut(切入点)

指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。

Advice(通知)

指拦截到 Joinpoint 之后要做的事情,即对切入点增强的内容。

Target(目标)

指代理的目标对象。

Weaving(植入)

指把增强代码应用到目标上,生成代理对象的过程。

Proxy(代理)

指生成的代理对象。

Aspect(切面)

切入点和通知的结合。

       简单来说就是,一个类中有很多的方法,比作连接点,需要拦截的连接点(需要对这个方法进行配置),就是切入点,拦截后需要增强内容,就是通知,切入点和通知结合就叫切面。

       举例说明,添加、修改、删除、查询每个方法里面,以往可能需要每个方法里面需要日志管理的代码,现在全部剥离出来,单独创建一个类,日志管理的代码,然后通过AOP配置,进入到需要的方法中。

 

Spring 通知的 5 种类型:

名称

说明

org.springframework.aop.MethodBeforeAdvice(前置通知)

在方法之前自动执行的通知称为前置通知,可以应用于权限管理等功能。

org.springframework.aop.AfterReturningAdvice(后置通知)

在方法之后自动执行的通知称为后置通知,可以应用于关闭流、上传文件、删除临时文件等功能。

org.aopalliance.intercept.MethodInterceptor(环绕通知)

在方法前后自动执行的通知称为环绕通知,可以应用于日志、事务管理等功能。

org.springframework.aop.ThrowsAdvice(异常通知)

在方法抛出异常时自动执行的通知称为异常通知,可以应用于处理异常记录日志等功能。

org.springframework.aop.IntroductionInterceptor(引介通知)

在目标类中添加一些新的方法和属性,可以应用于修改旧版本程序(增强类)。


2. Spring AOP:基于XML

(1)依赖包:

<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->

    <dependency>

      <groupId>org.springframework</groupId>

      <artifactId>spring-context</artifactId>

      <version>5.1.5.RELEASE</version>

    </dependency>

    <dependency>

      <groupId>org.aspectj</groupId>

      <artifactId>aspectjweaver</artifactId>

      <version>1.9.4</version>

    </dependency>

  </dependencies>

(2)AOP配置

在xml中对切点和切面进行配置

目标对象(通知需要切入的类)和切面(通知类)注册bean

通过<aop:pointcut>配置切入点,使用expression中的execution()方法来实现对切入点的范围控制

通过<aop:aspect ref="切面id">来实现切面类中的切面方法需要注入到哪一个切入点中

最重要的地方就是第二步,使用expression中的execution()方法

这个方法是为了找到切面需要织入的那个地方,可以是单个/多个方法,可以是整个类的所有方法

execution表达式的具体写法要求如下:

execution(

modifiers-pattern? —修饰符,比如public

ret-type-pattern —标识方法的返回值

declaring-type-pattern? —声明类型模式

name-pattern/param-pattern —指定方法名/指定方法参数的路径

throws-pattern? —抛出模式

)0

ret-type-pattern,name-pattern(param-pattern)是必须的.

ret-type-pattern:标识方法的返回值,需要使用全路径的类名如java.lang.String,也可以为*表示任何返回值;

name-pattern:指定方法名,*代表所有,例如set*,代表以set开头的所有方法.

param-pattern:指定方法参数(声明的类型):

(..)代表所有参数

(*)代表一个参数

(*,String)代表第一个参数为任何值,第二个为String类型.

表达式例子如下:

任意公共方法的执行:

    execution(public * *(..))

任何一个以“set”开始的方法的执行:

    execution(* set*(..))

UserService接口的任意方法的执行:

    execution(* com.cheng.aop.service.UserService.*(..))

定义在service包里的任意类和任意方法的执行:

    execution(* com.cheng.aop.service.*.*(..))

定义在service包和所有子包里的任意类的任意方法的执行(比上面多了个点):

    execution(* com.cheng.aop.service..*.*(..))

定义在pointcutexp包和所有子包里的UserService的的任意方法的执行:

    execution(* com.cheng.aop.service..UserService.*(..))

【注意】在java中默认使用的动态代理是JDK proxy基于接口的代理,在xml文件中配置如下代码:<aop:aspectj-autoproxy proxy-target-class="true"/>,测试类中getBean()括号中可以为类可以为接口,否则必须注入到接口上。 不然报错:

was actually of type 'com.sun.proxy.$Proxy3'

 

(3)前置通知

<!-- 引入系统需求功能实现类-->

        <aop:aspect ref="myAspect">

            <!-- 配置切入点 -->

            <!--id:切入点名称-->

            <!--expression:切入点表达式-->

            <aop:pointcut id="point1" expression="execution(* com.weiwei.springAOPdemo1.service.impl.StudentServiceImpl.insertStudent())"/>

            <!--配置前置通知 -->

            <aop:before method="saveLog" pointcut-ref="point1"/>

        </aop:aspect>

(4)后置通知

<aop:aspect ref="myAspect">

            <aop:pointcut id="point2"  expression="execution(* com.weiwei.springAOPdemo1.service.impl.StudentServiceImpl.updateStudent())"/>

            <!--配置后置通知 -->

            <aop:after method="saveLog" pointcut-ref="point2"/>

        </aop:aspect>

(5)环绕通知

//实现环绕通知的具体方法

    public Object myAround(ProceedingJoinPoint proceedingJoinPoint)

            throws Throwable {

        xxxxx(); // 开始

        Object obj = proceedingJoinPoint.proceed(); // 执行当前目标方法

        xxxxx(); // 结束

        return obj;

    }

(6)异常通知

    //异常通知实现方法

    public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {

        System.out.println("异常通知" + "出错了" + e.getMessage());

}

  <aop:aspect ref="myAspect">

            <aop:pointcut id="point4"  expression="execution(* com.weiwei.springAOPdemo1.service.impl.StudentServiceImpl.selectStudent())"/>

            <!--配置后置通知 -->

           <aop:after-throwing method="myAfterThrowing" pointcut-ref="point4" throwing="e"/>

        </aop:aspect>

(7)完整代码演示:

//系统功能



public class MyAspect {

    public void saveLog(){

        System.out.println("记录用户操作的日志");

    }



    //实现环绕通知的具体方法

    public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        saveLog(); // 开始

        Object obj = proceedingJoinPoint.proceed(); // 执行当前目标方法

        saveLog(); // 结束

        return obj;

    }

    //异常通知实现方法

    public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {

        System.out.println("异常通知" + "出错了" + e.getMessage());

    }

    //异常通知实现方法

    public void myAfterThrowing1(JoinPoint joinPoint) {

        System.out.println("异常通知" + "出错了");

    }

}

package com.weiwei.springAOPdemo1.service.impl;



import com.weiwei.springAOPdemo1.service.StudentService;



//Target(目标)

//业务功能

public class StudentServiceImpl implements StudentService {



    //Joinpoint(连接点)

    @Override

    public void insertStudent() {

        System.out.println("实现添加学生的业务方法----insertStudent");

    }

    //Joinpoint(连接点)

    @Override

    public void updateStudent() {

        System.out.println("实现修改学生的业务方法----updateStudent");

    }

   // Joinpoint(连接点)

    @Override

    public void deleteStudent() {

        System.out.println("实现删除学生的业务方法----deleteStudent");

    }

    //Joinpoint(连接点)

    @Override

    public void selectStudent() {

        System.out.println("实现查询学生的业务方法----selectStudent");

        int a=10/0;

    }

}

package com.weiwei.springAOPdemo1.service;

public interface StudentService {

    void insertStudent();

    void updateStudent();

    void deleteStudent();

    void selectStudent();

}

<?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="studentService" class="com.weiwei.springAOPdemo1.service.impl.StudentServiceImpl"/>

<!-- 创建系统功能实现类对象   -->

    <bean id="myAspect" class="com.weiwei.springAOPdemo1.aspect.MyAspect"/>

<!--  AOP配置  -->

<!--    <aop:config proxy-target-class="true">-->

    <aop:config>

        <!-- 引入系统需求功能实现类-->

        <aop:aspect ref="myAspect">

            <!-- 配置切入点 -->

            <!--id:切入点名称-->

            <!--expression:切入点表达式-->

            <aop:pointcut id="point1" expression="execution(* com.weiwei.springAOPdemo1.service.impl.StudentServiceImpl.insertStudent())"/>

            <!--配置前置通知 -->

            <aop:before method="saveLog" pointcut-ref="point1"/>

        </aop:aspect>



        <aop:aspect ref="myAspect">

            <aop:pointcut id="point2"  expression="execution(* com.weiwei.springAOPdemo1.service.impl.StudentServiceImpl.updateStudent())"/>

            <!--配置后置通知 -->

            <aop:after method="saveLog" pointcut-ref="point2"/>

        </aop:aspect>



        <aop:aspect ref="myAspect">

            <aop:pointcut id="point3" expression="execution(* com.weiwei.springAOPdemo1.service.impl.StudentServiceImpl.deleteStudent())" />

            <!--配置环绕通知 -->

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

        </aop:aspect>



        <aop:aspect ref="myAspect">

            <aop:pointcut id="point4"  expression="execution(* com.weiwei.springAOPdemo1.service.impl.StudentServiceImpl.selectStudent())"/>

            <!--配置异常通知 -->

           <aop:after-throwing method="myAfterThrowing" pointcut-ref="point4" throwing="e"/>

        </aop:aspect>



        <aop:aspect ref="myAspect">

            <aop:pointcut id="point5"  expression="execution(* com.weiwei.springAOPdemo1.service.impl.StudentServiceImpl.selectStudent())"/>

            <!--配置异常通知 -->

            <aop:after-throwing method="myAfterThrowing1" pointcut-ref="point5"/>

        </aop:aspect>



    </aop:config>

</beans>

package com.weiwei.springAOPdemo1;



import com.weiwei.springAOPdemo1.service.StudentService;

import com.weiwei.springAOPdemo1.service.impl.StudentServiceImpl;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;



public class Test {

    public static void main(String[] args) {

        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

        StudentService studentService = applicationContext.getBean("studentService", StudentService.class);

//        StudentServiceImpl serviceimpl = applicationContext.getBean("studentService", StudentServiceImpl.class);

        studentService.insertStudent();

        System.out.println("------------------------------");

        studentService.updateStudent();

        System.out.println("------------------------------");

        studentService.deleteStudent();

        System.out.println("------------------------------");

        studentService.selectStudent();



    }

}

 

3. Spring AOP:基于Annotation

       配置文件中需要AOP命名空间

<?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:context="http://www.springframework.org/schema/context"

       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

       http://www.springframework.org/schema/context

       http://www.springframework.org/schema/context/spring-context.xsd">

    <!--扫描含com.weiwei.springAOP包下的所有注解-->

    <context:component-scan base-package="com.weiwei.springAOP" />

    <!-- 使切面开启自动代理 默认使用的动态代理是JDK proxy基于接口的代理-->

    <aop:aspectj-autoproxy />

</beans>

package com.weiwei.springAOP;

import org.springframework.stereotype.Component;

@Component("stu")

public class StudentServiceImpl {

    public  void  insertStudent(){

        System.out.println("添加学生信息的业务方法---insertStudent");

    }

    public  void  updateStudent(){

        System.out.println("修改学生信息的业务方法---updateStudent");

    }

    public  void  deleteStudent(){

        System.out.println("删除学生信息的业务方法---deleteStudent");

    }

    public  void  selectStudent(){

        System.out.println("查询学生信息的业务方法---selectStudent");

        int a=10/0;

    }

}







package com.weiwei.springAOP;



import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.*;

import org.springframework.stereotype.Component;



@Component

@Aspect

public class myAspect {



    @Pointcut("execution(* com.weiwei.springAOP.StudentServiceImpl.insertStudent())")

    //切点,使用execution表达式设置insertStudent方法为切入点,保存到point1()中;

    public void point1(){}



    @Pointcut("execution(* com.weiwei.springAOP.StudentServiceImpl.deleteStudent())")

    public void point2(){}



    @Pointcut("execution(* com.weiwei.springAOP.StudentServiceImpl.updateStudent())")

    public void point3(){}



    @Pointcut("execution(* com.weiwei.springAOP.StudentServiceImpl.selectStudent())")

    public void point4(){}



    @Before("point1()")

    public  void saveLog1(){

        System.out.println("记录系统运行日志");

    }

    @After("point2()")

    public  void saveLog2(){

        System.out.println("记录系统运行日志");

    }

    //测试环绕通知

    @Around("point3()")

    public Object myAround(ProceedingJoinPoint proceedingJoinPoint)throws Throwable {

        saveLog1(); // 开始

        Object obj = proceedingJoinPoint.proceed(); // 执行当前目标方法

        saveLog1(); // 结束

        return obj;

    }



    @AfterThrowing(value = "point4()",throwing = "e")

    public void myAfterThrowing(Throwable e) {

        System.out.println("异常通知" + "出错了" + e.getMessage());

    }

}





package com.weiwei.springAOP;



import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;



/**

 * Hello world!

 *

 */

public class App {

    public static void main( String[] args ) {

       ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

        StudentServiceImpl stu = applicationContext.getBean("stu", StudentServiceImpl.class);

        stu.insertStudent();

        System.out.println("---------------------------");

        stu.deleteStudent();

        System.out.println("---------------------------");

        stu.updateStudent();

        System.out.println("---------------------------");

        stu.selectStudent();



    }

}

输出:

记录系统运行日志

添加学生信息的业务方法---insertStudent

---------------------------

删除学生信息的业务方法---deleteStudent

记录系统运行日志

---------------------------

记录系统运行日志

修改学生信息的业务方法---updateStudent

记录系统运行日志

---------------------------

查询学生信息的业务方法---selectStudent

异常通知出错了/ by zero

Exception in thread "main" java.lang.ArithmeticException: / by zero

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Java-请多指教

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

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

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

打赏作者

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

抵扣说明:

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

余额充值