第三章 spring aop

1 AOP概念

AOP(Aspect-Oriented Programming,面向切面编程),简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或业务封装起来,再把封装功能整合到业务中。 AOP的核心思想就是“将应用程序中的业务逻辑同对其提供支持的通用服务进行分离"好处:
1,便于减少系统的重复代码
2,降低模块间的耦合度
3,有利于未来的可操作性和可维护性。
例如:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

2 AOP入门示例

需求:在服务层用AOP给所有方法(增删改)添加统一日志服务(谁什么时间操作了哪个对象的哪个方法。。。。)
1,添加依赖

 <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <spring.version>5.3.8</spring.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency><!--aop包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.7</version>
        </dependency>
    </dependencies>

2,编写服务接口和实现类

package com.xii.service.Impl;


import com.xii.entity.Dept;
import com.xii.service.DeptService;
import org.springframework.stereotype.Service;

@Service
public class DeptServiceImpl implements DeptService {
    /**
     * 添加
     * @param dept
     */
    @Override
    public void add(Dept dept) {
    	int a = 1/0;
        System.out.println("添加操作");
    }

    /**
     * 通过id删除
     * @param id
     */
    @Override
    public void deleteById(Integer id) {
        System.out.println("删除操作");
    }

    /**
     * 更新
     * @param dept
     */
    @Override
    public void updateDept(Dept dept) {
        System.out.println("更新操作");
    }

    /**
     * 查询操作
     */
    @Override
    public void queryDept() {
        System.out.println("查询操作");
    }
}

3,编写日志记录工具类

package com.xii.utils;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 通知类 又叫增强类
 */
@Component    //三层之外的注解
public class LogRecordUtil {
    /**
     * 统一记录日志的方法
     *
     * @param joinPoint
     */
    public void logRecord(JoinPoint joinPoint) {
        //获取目标对象
        String targetName = joinPoint.getTarget().getClass().getName();
        //获取方法名称
        String methodName = joinPoint.getSignature().getName();
        //获取执行方法参数
        Object[] args = joinPoint.getArgs();

        String argsType = null;
        if (args != null && args.length > 0) {
            //获得参数类型  反射
            argsType = args[0].getClass().getName();
        }
        System.out.println("admin在" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) +
                "执行类" + targetName + "中的方法" + methodName + ",该方法参数类型为:" + argsType + "----------模拟日志记录-------------");

    }

    /**
     * 前置通知
     *
     * @param joinPoint
     */
    public void beforeDoSome(JoinPoint joinPoint) {
        System.out.println("-------------------------前置通知方法-------------------------");
    }


    /**
     * 环绕通知具体实现方法   相当于前置和后置通知的整合 但是也有区别:
     * 1,前后置通知无法决定是否调用业务方法  而环绕通知可以决定是否调用业务方法
     * 2,前后置通知无法获取业务方法的返回值,而环绕通知可以获取执行业务方法的返回值
     *@param proceedingJoinPoint
     * @return
     */
    public Object aroundHandler(ProceedingJoinPoint proceedingJoinPoint) {
        /* proceedingJoinPoint.getTarget();
        proceedingJoinPoint.getSignature().getName();
        proceedingJoinPoint.getArgs();*/
        System.out.println("-------环绕通知,在执行业务之前干什么---------");
        Object result = null;
        try {
            //调用业务方法
            result = proceedingJoinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("业务方法的返回值为:" + result);
        System.out.println("-------环绕通知,在执行业务之后干什么---------");
        return result;
    }

    /**
     * 异常处理方法
     * @param joinPoint
     * @param ex
     */
    public void exceptionHandle(JoinPoint joinPoint,Exception ex){
        System.out.println("-------------------异常业务----------------------");
        System.out.println("在执行"+joinPoint.getSignature().getName()+"方法时:出现:"+ex.getClass().getName()+"信息:"+ex.getMessage());
    }
    /**
     * 最终通知
     */
    public void finallyHandler(JoinPoint joinPoint){
        System.out.println("最终通知-----------无论有没有异常都会执行的方法");
    }
}

4,编写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: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 https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.xii"></context:component-scan>
    <!--被代理者-->
    <!--<bean id="logRecordUtil" class="com.xii.utils.LogRecordUtil"></bean>-->
    <!--aop的配置-->
    <aop:config>
        <!--配置切入点-->
        <aop:pointcut id="pointCutA" expression="execution(* com.xii.service.Impl.*.add(..))" />
        <aop:pointcut id="pointCutB" expression="execution(* com.xii.service.Impl.*.*(..))" />

        <!--切面配置-->
        <aop:aspect ref="logRecordUtil">
            <!--后置通知-->
            <aop:after-returning pointcut-ref="pointCutA" method="logRecord"></aop:after-returning>
            <!--前置通知-->
            <aop:before pointcut-ref="pointCutA" method="beforeDoSome"></aop:before>
            <!--环绕通知-->
            <aop:around pointcut-ref="pointCutA" method="aroundHandler"></aop:around>
            <!--异常通知-->
            <aop:after-throwing pointcut-ref="pointCutA" method="exceptionHandle" throwing="ex"></aop:after-throwing>
            <!--最终通知-->
            <aop:after method="finallyHandler" pointcut-ref="pointCutA"></aop:after>
        </aop:aspect>
    </aop:config>
</beans>

5,编写测试类

 /**
     * 原生
     */
    @Test
    public void aopTest() {
        ClassPathXmlApplicationContext aopContext = new ClassPathXmlApplicationContext("spring_aop.xml");
        DeptService deptServiceImpl = aopContext.getBean("deptServiceImpl", DeptService.class);
        deptServiceImpl.add(new Dept());
        deptServiceImpl.deleteById(1);
        deptServiceImpl.queryDept();
        deptServiceImpl.updateDept(new Dept());
    }

3 aop术语简介

1,切面(Aspect)

切点和增强组成,被抽取的公共模块 如:将日志记录,性能统计,安全控制,事务处理,异常处理等等

2,增强(Advice)又叫通知

通知是切面的具体实现,是放置“切面代码”的类。在不修改原有代码的前提下,为某一个对象增加新的功能,增加的新功能就是增强。切面在某个具体的连接点采取的行为或行动称为通知。

3,切入点(Pointcut)

多个连接点组成一个切入点,可以使用切入点表达式来表示,AOP通过切点定位到特定的连接点。
切入点表达式:

在这里插入图片描述

4,连接点(Join point)

程序执行过程中某个特定的点,连接点总是代表某个方法执行的特定位置。类开始初始化前、类初始化后、类的某个方法调用前、类的某个方法调用后、方法抛出异常后等。

5,织入(Weaving)

织入是指将切面代码插入到目标对象的过程(或者将切入点和通知结合的过程称为织入)。

6,目标对象(Target)

包含主业务逻辑的类的对象,包含一个连接点的对象,即被拦截的对象,被代理的对象。

4 通知类型

Before:在目标方法被调用之前调用(前置通知)
AfterReturning:在目标方法被调用之后调用(后置通知)
Around:拦截对目标对象方法的调用(环绕通知)
After:无论目标方法是否正常运行,都一定要执行的通知(最终通知)
AfterThrowing:当目标方法抛出异常时调用(异常通知)

环绕通知和前置通知,后置通知有着很大的区别,主要有两个重要的区别:

1) 目标方法的调用由环绕通知决定,即你可以决定是否调用目标方法,而前置和后置通知 是不能决定的,他们只是在方法的调用前后执行通知而已,即目标方法肯定是要执行的。

2) 环绕通知可以控制返回对象,即你可以返回一个与目标对象完全不同的返回值,虽然这很危险,但是你却可以办到。而后置方法是无法办到的,因为他是在目标方法返回值后调用

5 AOP注解

1,接口及实现类同上

2,日志工具类

package com.xiiannotation.utils;

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

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 通知类 又叫增强类
 */
@Aspect   //增强类注解
@Component
public class LogRecordUtil {

    /**
     * 配置切入点
     */
    @Pointcut(value = "execution(* com.xiiannotation.service.*.*add(..))")
    public void pointCutA(){

    }

    /**
     * 统一记录日志的方法 后置
     *
     * @param joinPoint
     */
    @AfterReturning(pointcut = "pointCutA()")
    public void logRecord(JoinPoint joinPoint) {
        //获取目标对象
        String targetName = joinPoint.getTarget().getClass().getName();
        //获取方法名称
        String methodName = joinPoint.getSignature().getName();
        //获取执行方法参数
        Object[] args = joinPoint.getArgs();

        String argsType = null;
        if (args != null && args.length > 0) {
            //获得参数类型  反射
            argsType = args[0].getClass().getName();
        }
        System.out.println("admin在" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) +
                "执行类" + targetName + "中的方法" + methodName + ",该方法参数类型为:" + argsType + "----------模拟日志记录-------------");

    }

    /**
     * 前置通知
     *
     * @param joinPoint
     */
    @Before("pointCutA()")
    public void beforeDoSome(JoinPoint joinPoint) {
        System.out.println("-------------------------前置通知方法-------------------------");
    }


    /**
     * 环绕通知具体实现方法   相当于前置和后置通知的整合 但是也有区别:
     * 1,前后置通知无法决定是否调用业务方法  而环绕通知可以决定是否调用业务方法
     * 2,前后置通知无法获取业务方法的返回值,而环绕通知可以获取执行业务方法的返回值
     *@param proceedingJoinPoint
     * @return
     */
    @Around(value = "pointCutA()")
    public Object aroundHandler(ProceedingJoinPoint proceedingJoinPoint) {
        /* proceedingJoinPoint.getTarget();
        proceedingJoinPoint.getSignature().getName();
        proceedingJoinPoint.getArgs();*/
        System.out.println("-------环绕通知,在执行业务之前干什么---------");
        Object result = null;
        try {
            //调用业务方法
            result = proceedingJoinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("业务方法的返回值为:" + result);
        System.out.println("-------环绕通知,在执行业务之后干什么---------");
        return result;
    }

    /**
     * 异常处理方法
     * @param joinPoint
     * @param ex
     */
    @AfterThrowing(pointcut = "pointCutA()",throwing = "ex")
    public void exceptionHandle(JoinPoint joinPoint,Exception ex){
        System.out.println("-------------------异常业务----------------------");
        System.out.println("在执行"+joinPoint.getSignature().getName()+"方法时:出现:"+ex.getClass().getName()+"信息:"+ex.getMessage());
    }
    /**
     * 最终通知
     */
    @After("pointCutA()")
    public void finallyHandler(JoinPoint joinPoint){
        System.out.println("最终通知-----------无论有没有异常都会执行的方法");
    }
}

3,配置

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

    <context:component-scan base-package="com.xiiannotation"></context:component-scan>
    <!--开启@Aspest注解扫描-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

4、测试

 /**
     * 注解测试
     */
    @Test
    public void aopAnnotationTest(){
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring_aopannotation.xml");
        com.xiiannotation.service.DeptService deptServiceImpl = applicationContext.getBean("deptServiceImpl", com.xiiannotation.service.DeptService.class);
        deptServiceImpl.add(new com.xiiannotation.entity.Dept());
        deptServiceImpl.deleteById(1);
        deptServiceImpl.queryDept();
        deptServiceImpl.updateDept(new com.xiiannotation.entity.Dept());
    }

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

杵意

谢谢金主打赏呀!!

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

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

打赏作者

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

抵扣说明:

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

余额充值