10- Spring AOP

13-Spring AOP

13.1 AOP基本概念

13.1.1 AOP概述

#AOP(Aspect Oriented Programming),即面向切面编程。
	通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
	AOP是OOP的延续,是软件系统开发中的一个热点,也是spring框架的一个重点。
	利用AOP可以实现业务逻辑各个部分的隔离,从而使得业务逻辑各个部分的耦合性降低,提高程序的可重用性,同时提高开发效率
	
#简单理解:
	aop是面向切面编程,使用动态代理技术,实现在不修改java源代码的情况下,运行时实现方法功能的增强。

13.1.2 AOP作用

#作用:
	使用动态代理技术,在程序运行期间,不修改java源代码对已有方法功能进行增强

#优势:
	1.减少重复代码,提高开发效率
	2.统一管理统一调用,方便维护

13.1.3 AOP常用术语

#Joinpoint(连接点):
	在spring中,连接点指的都是方法(指的是那些要被增强功能的候选方法)
	
#Pointcut(切入点):
	在运行时已经被spring 的AOP实现了增强的方法
	
#Advice(通知):
	通知指的是拦截到Joinpoint之后要做的事情。即增强的功能
	通知类型:前置通知、后置通知、异常通知、最终通知、环绕通知
	
#Target(目标对象):
	被代理的对象。比如动态代理案例中的演员
	
#Proxy(代理):
	一个类被AOP织入增强后,即产生一个结果代理类。比如动态代理案例中的经纪人
	
#Weaving(织入):
	织入指的是把增强用于目标对象。创建代理对象的过程

#Aspect(切面):
	切面指的是切入点和通知的结合

13.2 完全xml配置AOP

13.2.1 项目目录

13.2.2 配置pom.xml

<properties>
    <!--spring版本-->
    <spring.version>5.0.2.RELEASE</spring.version>
    <!--aopalliance版本-->
    <aopalliance.version>1.0</aopalliance.version>
</properties>

<dependencies>
    <!-- spring aspects依赖 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <!--aopalliance依赖-->
    <dependency>
        <groupId>aopalliance</groupId>
        <artifactId>aopalliance</artifactId>
        <version>${aopalliance.version}</version>
    </dependency>
</dependencies>

13.2.3 编写业务功能

  • 客户service接口
package cn.guardwhy.service;
/**
 * 客户service接口
 */
public interface CustomerService {
    
    
    /**
     * 保存客户
     */
    void saveCustomer();

    /**
     * 根据客户id查询客户
     */
    void findCustomerById(Integer id);
}
  • 客户service实现类
package cn.guardwhy.service.impl;

import cn.guardwhy.service.CustomerService;
/**
 * 客户service实现类
 */
public class CustomerServiceImpl implements CustomerService {
    
    
    @Override
    public void saveCustomer() {
    
    
        System.out.println("保存客户操作");
    }

    @Override
    public void findCustomerById(Integer id) {
    
    
        // 根据客户id查询客户
        System.out.println("根据客户id查询客户,客户id: " + id);
    }
}

13.2.4 编写通知

package cn.guardwhy.advice;
/**
 * 日志通知
 */
public class LogAdvice {
    
    
    /**
     * 记录用户操作日志
     */
    public void printLog(){
    
    
        System.out.println("记录用户操作日志");
    }
}

13.2.5 编写bean.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">
    <!--配置客户service-->
    <bean id="customerService" class="cn.guardwhy.service.impl.CustomerServiceImpl"></bean>

    <!--配置日志通知-->
    <bean id="logAdvice" class="cn.guardwhy.advice.LogAdvice"></bean>

    <!--第一步:通过aop:config声明aop的配置-->
    <aop:config>
        <!--第二步:通过aop:aspect配置切面,说明:
            id:唯一标识名称
            ref:指定通知bean对象的引用
        -->
        <aop:aspect id="logAspect" ref="logAdvice">
            <!--第三步:通过aop:before配置前置通知,说明:
                method:指定通知方法名称
                pointcut-ref:指定切入点表达式
            -->
            <aop:before method="printLog" pointcut-ref="pt1"></aop:before>
            <!--第四步:通过aop:pointcut配置切入点表达式,说明:
                id:唯一标识名称
                expression:指定切入点表达式
                切入点表达式组成:
                    访问修饰符  返回值  包名称  类名称  方法名称  (参数列表)
             -->
            <aop:pointcut id="pt1" expression="execution(public void
             cn.guardwhy.service.impl.CustomerServiceImpl.saveCustomer())"/>
        </aop:aspect>
    </aop:config>
</beans>

13.2.6 执行结果

13.3 切入点表达式

13.3.1 项目目录

13.3.2 bean.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">
    <!--配置客户service-->
    <bean id="customerService" class="cn.guardwhy.service.impl.CustomerServiceImpl"></bean>

    <!--配置日志通知-->
    <bean id="logAdvice" class="cn.guardwhy.advice.LogAdvice"></bean>

<!--配置aop:四个步骤-->
    <aop:config>
        <aop:aspect id="logAspect" ref="logAdvice">
            <aop:before method="printLog" pointcut-ref="pt1"></aop:before>

            <!--切入点表达式演化
                表达式组成:
                    访问修饰符  返回值  包名称 类名称  方法名称 (参数列表)
                演化过程:
                    全匹配模式:
                        public void com.itheima.service.impl.CustomerServiceImpl.saveCustomer()
                    访问修饰符可以省略:
                        void com.itheima.service.impl.CustomerServiceImpl.saveCustomer()
                    返回值可以使用通配符:*
                        * com.itheima.service.impl.CustomerServiceImpl.saveCustomer()
                    包名称可以使用通配符:*(有多少个包,就需要多少个*)
                        * *.*.*.*.CustomerServiceImpl.saveCustomer()
                    类名称可以使用通配符:*
                        * *.*.*.*.*.saveCustomer()
                    方法名称可以使用通配符:*
                        * *.*.*.*.*.*()
                    参数列表可以使用通配符:*(此时必须要有参数)
                        * *.*.*.*.*.*(*)
                    参数列表可以使用:..(有无参数都可以)
                        * *.*.*.*.*.*(..)
            -->
            <aop:pointcut id="pt1" expression="execution(* cn.guardwhy.service..*.*(..))">
            	</aop:pointcut>
        </aop:aspect>
    </aop:config>
</beans>

13.3.3 常用标签说明

#<aop:config>:
	作用:声明aop配置

#<aop:aspect>:
	作用:配置切面
	属性:
		id:唯一标识切面的名称
		ref:引用通知类bean的id
		
#<aop:pointcut>:
	作用:配置切入点表达式
	属性:
		id:唯一标识切入点表达式名称
		expression:定义切入点表达式
	
#<aop:before>:
	作用:配置前置通知
	属性:
		method:指定通知方法名称
		pointcut:定义切入点表达式
		pointcut-ref:引用切入点表达式的id
		
#<aop:after-returning>:
	作用:配置后置通知
	属性:
		method:指定通知方法名称
		pointcut:定义切入点表达式
		pointcut-ref:引用切入点表达式的id
		
#<aop:after-throwing>:
	作用:配置异常通知
	属性:
		method:指定通知方法名称
		pointcut:定义切入点表达式
		pointcut-ref:引用切入点表达式的id
		
#<aop:after>:
	作用:配置最终通知
	属性:
		method:指定通知方法名称
		pointcut:定义切入点表达式
		pointcut-ref:引用切入点表达式的id
	
#<aop:around>:
	作用:配置环绕通知
	属性:
		method:指定通知方法名称
		pointcut:定义切入点表达式
		pointcut-ref:引用切入点表达式的id

13.4 通知类型案例

13.4.1 通知类型

#前置通知:
	在目标方法执行前执行
	
#后置通知:
	在目标方法正常返回后执行。它和异常通知只能执行一个
	
#异常通知:
	在目标方法发生异常后执行。它和后置通知只能执行一个
	
#最终通知:
	无论目标方法正常返回,还是发生异常都会执行
	
#环绕通知: 
	综合了前面四类通知,可以手动控制通知的执行时间点和顺序

13.4.2 项目目录

13.4.3 日志通知类

package cn.guardwhy.advice;

import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 日志通知
 */
public class LogAdvice {
    
    
    /**
     * 前置通知
     */
    public void beforeLog(){
    
    
        System.out.println("[前置通知]记录用户操作日志");
    }

    /**
     * 后置通知
     */
    public  void afterReturningLog(){
    
    
        System.out.println("【后置通知】记录用户操作日志");
    }

    /**
     * 异常通知
     */
    public  void afterThrowingLog(){
    
    
        System.out.println("【异常通知】记录用户操作日志");
    }

    /**
     * 最终通知
     */
    public  void afterLog(){
    
    
        System.out.println("【最终通知】记录用户操作日志");
    }

    /**
     * 环绕通知:
     *      1.它是spring框架为我们提供了手动控制通知执行时间点和顺序的一种特殊通知类型
     * 原理分析:
     *      2.spring框架提供了ProceedingJoinPoint接口,作为环绕通知的参数。在环绕通知
     *      执行的时候,spring框架会提供实例化对象,我们直接使用即可。该接口中提供了
     *      两个方法:
     *          getArgs:获取参数列表
     *          proceed:相当于反射中的invoke方法
     *
     */
    public void aroundLog(ProceedingJoinPoint pjp){
    
    
        // 前置通知
        System.out.println("[环绕通知-前置通知]记录用户操作日志");

        try {
    
    
            // 获取参数列表
            Object[] args = pjp.getArgs();
            // 反射调用目标方法
            Object retv = pjp.proceed(args);
            // 后置通知
            System.out.println("[环绕通知-后置通知]记录用户操作日志");
        } catch (Throwable throwable) {
    
    
            throwable.printStackTrace();
            // 异常通知
            System.out.println("[环绕通知-异常通知]记录用户操作日志");
        }
        // 最终通知
        System.out.println("[环绕通知-最终通知]记录用户操作日志");
    }
}

13.4.4 配置bean.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">
    <!--配置客户service-->
    <bean id="customerService" class="cn.guardwhy.service.impl.CustomerServiceImpl"/>

    <!--配置日志通知-->
    <bean id="logAdvice" class="cn.guardwhy.advice.LogAdvice"/>

    <!--配置aop -->
    <aop:config>
        <aop:aspect id="logAspect" ref="logAdvice">
            <!--前置通知-->
            <aop:before method="beforeLog" pointcut-ref="pt1"/>
            <!--后置通知-->
            <aop:after-returning method="afterReturningLog" pointcut-ref="pt1"/>
            <!--异常通知-->
            <aop:after-throwing method="afterThrowingLog" pointcut-ref="pt1"/>
            <!--最终通知-->
            <aop:after method="afterLog" pointcut-ref="pt1"/>
            
            <!--环绕通知-->
            <aop:around method="aroundLog" pointcut-ref="pt1"/>
            <!--切入点表达式-->
            <aop:pointcut id="pt1" expression="execution(* cn.guardwhy.service..*.*(..))"/>
        </aop:aspect>
    </aop:config>
</beans>

13.4.5 客户表现层(Controller)

package cn.guardwhy.controller;

import cn.guardwhy.service.CustomerService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 客户表现层
 */
public class CustomerController {
    
    
    public static void main(String[] args) {
    
    
        // 1.加载spring配置文件,创建spring容器
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:bean.xml");
        // 2.获取客户service
        CustomerService customerService = (CustomerService) context.getBean("customerService");
        // 3.保存客户
        customerService.saveCustomer();
    }
}

13.4.6 执行结果

13.5 xml和注解配置AOP

13.5.1 项目目录

13.5.2 客户service实现类

package cn.guardwhy.service.impl;

import cn.guardwhy.service.CustomerService;
import org.springframework.stereotype.Service;

/**
 * 客户service实现类
 */
@Service("customerService")
public class CustomerServiceImpl implements CustomerService {
    
    
    @Override
    public void saveCustomer() {
    
    
        System.out.println("保存客户操作");

    }

    @Override
    public void findCustomerById(Integer id) {
    
    
        // 根据客户id查询客户
        System.out.println("根据客户id查询客户,客户id: " + id);
    }
}

13.5.3 日志通知类

package cn.guardwhy.advice;

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

/**
 * 日志通知类
 * 注解说明:
 *      @Aspect:
 *          作用:声明当前类是一个切面类,相当于xml中aop:aspect标签
 *      @Before:
 *          作用:配置前置通知
 *      @AfterReturning:
 *          作用:配置后置通知
 *      @AfterThrowing:
 *          作用:配置异常通知
 *      @After:
 *          作用:配置最终通知
 *      @Pointcut:
 *          作用:配置切入点表达式
 */
@Component("logAdvice")
@Aspect
public class LogAdvice {
    
    

    /**
     * 切入点表达式
     */
    @Pointcut("execution(* cn.guardwhy.service..*.*(..))")
    public void pt1(){
    
    }

    /**
     * 前置通知
     */
    @Before("pt1()")
    public void beforeLog(){
    
    
        System.out.println("[前置通知]记录用户操作日志");
    }

    /**
     * 后置通知
     */
    @AfterReturning("pt1()")
    public  void afterReturningLog(){
    
    
        System.out.println("【后置通知】记录用户操作日志");
    }

    /**
     * 异常通知
     */
    @AfterThrowing("pt1()")
    public  void afterThrowingLog(){
    
    
        System.out.println("【异常通知】记录用户操作日志");
    }

    /**
     * 最终通知
     */
    @After("pt1()")
    public  void afterLog(){
    
    
        System.out.println("【最终通知】记录用户操作日志");
    }

    /**
     * 环绕通知:
     *      1.它是spring框架为我们提供了手动控制通知执行时间点和顺序的一种特殊通知类型
     * 原理分析:
     *      2.spring框架提供了ProceedingJoinPoint接口,作为环绕通知的参数。在环绕通知
     *      执行的时候,spring框架会提供实例化对象,我们直接使用即可。该接口中提供了
     *      两个方法:
     *          getArgs:获取参数列表
     *          proceed:相当于反射中的invoke方法
     *
     */
     @Around("pt1()")
    public void aroundLog(ProceedingJoinPoint pjp){
    
    
        // 前置通知
        System.out.println("[环绕通知-前置通知]记录用户操作日志");

        try {
    
    
            // 获取参数列表
            Object[] args = pjp.getArgs();
            // 反射调用目标方法
            Object retv = pjp.proceed(args);
            // 后置通知
            System.out.println("[环绕通知-后置通知]记录用户操作日志");
        } catch (Throwable throwable) {
    
    
            throwable.printStackTrace();
            // 异常通知
            System.out.println("[环绕通知-异常通知]记录用户操作日志");
        }
        // 最终通知
        System.out.println("[环绕通知-最终通知]记录用户操作日志");
    }
}

13.5.4 配置bean.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"
       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
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

    <!--配置包扫描advice/service,说明:
        第一步:导入context名称空间和约束
        第二步:通过<context:component-scan>标签配置包扫描
    -->
    <context:component-scan base-package="cn.guardwhy"/>

    <!--关键步骤:开启spring对注解aop支持-->
    <aop:aspectj-autoproxy/>
</beans>

13.5.5 执行结果

猜你喜欢

转载自blog.csdn.net/hxy1625309592/article/details/113556943