Spring系列之AOP实现的两种方式

AOP常用的实现方式有两种,一种是采用声明的方式来实现(基于XML),一种是采用注解的方式来实现(基于AspectJ)

首先复习下AOP中一些比较重要的概念:

Joinpoint(连接点):程序执行时的某个特定的点,在Spring中就是某一个方法的执行 。
Pointcut(切点):说的通俗点,spring中AOP的切点就是指一些方法的集合,而这些方法是需要被增强、被代理的。一般都是按照一定的约定规则来表示的,如正则表达式等。切点是由一类连接点组成。 
Advice(通知):还是说的通俗点,就是在指定切点上要干些什么。 
Advisor(通知器):其实就是切点和通知的结合 。

一、基于XML配置的Spring AOP

采用声明的方式实现(在XML文件中配置),大致步骤为:配置文件中配置pointcut, 在java中用编写实际的aspect 类, 针对对切入点进行相关的业务处理。

业务接口:

package com.spring.service;

public interface IUserManagerService {
    //查找用户
    public String findUser();

    //添加用户
    public void addUser();
}

业务实现:

package com.spring.service.impl;

import com.spring.service.IUserManagerService;

public class UserManagerServiceImpl implements IUserManagerService{

private String name;

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

    public String getName(){
        return this.name;
    }

    public String findUser(){
        System.out.println("============执行业务方法findUser,查找的用户是:"+name+"=============");
        return name;
    }

    public void addUser(){
        System.out.println("============执行业务方法addUser=============");
        //throw new RuntimeException();
    }
}

切面类:

package com.spring.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

public class AopAspect {

    /**
     * 前置通知:目标方法调用之前执行的代码
      * @param jp
     */
    public void doBefore(JoinPoint jp){
        System.out.println("===========执行前置通知============");
    }

    /**
     * 后置返回通知:目标方法正常结束后执行的代码
      * 返回通知是可以访问到目标方法的返回值的
      * @param jp
     * @param result
     */
    public void doAfterReturning(JoinPoint jp,String result){
        System.out.println("===========执行后置通知============");
        System.out.println("返回值result==================="+result);
    }

    /**
     * 最终通知:目标方法调用之后执行的代码(无论目标方法是否出现异常均执行)
      * 因为方法可能会出现异常,所以不能返回方法的返回值
      * @param jp
     */
    public void doAfter(JoinPoint jp){
        System.out.println("===========执行最终通知============");
    }

    /**
     *
     * 异常通知:目标方法抛出异常时执行的代码
      * 可以访问到异常对象
      * @param jp
     * @param ex
     */
    public void doAfterThrowing(JoinPoint jp,Exception ex){
        System.out.println("===========执行异常通知============");
    }

    /**
     * 环绕通知:目标方法调用前后执行的代码,可以在方法调用前后完成自定义的行为。
      * 包围一个连接点(join point)的通知。它会在切入点方法执行前执行同时方法结束也会执行对应的部分。
      * 主要是调用proceed()方法来执行切入点方法,来作为环绕通知前后方法的分水岭。
      *
     * 环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法。
      * 而且环绕通知必须有返回值,返回值即为目标方法的返回值
      * @param pjp
     * @return
     * @throws Throwable
     */
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("======执行环绕通知开始=========");
         // 调用方法的参数
        Object[] args = pjp.getArgs();
        // 调用的方法名
        String method = pjp.getSignature().getName();
        // 获取目标对象
        Object target = pjp.getTarget();
        // 执行完方法的返回值
        // 调用proceed()方法,就会触发切入点方法执行
        Object result=pjp.proceed();
        System.out.println("输出,方法名:" + method + ";目标对象:" + target + ";返回值:" + result);
        System.out.println("======执行环绕通知结束=========");
        return result;
    }
}

Spring配置:

<?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:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <!-- 声明一个业务类 -->
    <bean id="userManager" class="com.spring.service.impl.UserManagerServiceImpl">
        <property name="name" value="lixiaoxi"></property>
    </bean>  

     <!-- 声明通知类 -->
    <bean id="aspectBean" class="com.spring.aop.AopAspect" />

    <aop:config>
     <aop:aspect ref="aspectBean">
        <aop:pointcut id="pointcut" expression="execution(* com.spring.service.impl.UserManagerServiceImpl..*(..))"/>

        <aop:before method="doBefore" pointcut-ref="pointcut"/>
        <aop:after-returning method="doAfterReturning" pointcut-ref="pointcut" returning="result"/>
        <aop:after method="doAfter" pointcut-ref="pointcut" />
        <aop:around method="doAround" pointcut-ref="pointcut"/>
        <aop:after-throwing method="doAfterThrowing" pointcut-ref="pointcut" throwing="ex"/>
      </aop:aspect>
   </aop:config>
</beans>

测试类:

package com.spring.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.spring.service.IUserManagerService;

public class TestAop {

    public static void main(String[] args) throws Exception{

        ApplicationContext act =  new ClassPathXmlApplicationContext("applicationContext3.xml");
         IUserManagerService userManager = (IUserManagerService)act.getBean("userManager");
         userManager.findUser();
         System.out.println("\n");
         userManager.addUser();
    }
}

测试结果:

 二、使用注解配置AOP

采用注解来做aop, 主要是将写在spring 配置文件中的连接点写到注解里面。

业务接口和业务实现与上边一样,具体切面类如下:

package com.spring.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class AopAspectJ {

    /**
     * 必须为final String类型的,注解里要使用的变量只能是静态常量类型的
     */
    public static final String EDP="execution(* com.spring.service.impl.UserManagerServiceImpl..*(..))";

    /**
     * 切面的前置方法 即方法执行前拦截到的方法
      * 在目标方法执行之前的通知
      * @param jp
     */
    @Before(EDP)
    public void doBefore(JoinPoint jp){

        System.out.println("=========执行前置通知==========");
    }

    /**
     * 在方法正常执行通过之后执行的通知叫做返回通知
      * 可以返回到方法的返回值 在注解后加入returning
     * @param jp
     * @param result
     */
    @AfterReturning(value=EDP,returning="result")
    public void doAfterReturning(JoinPoint jp,String result){
        System.out.println("===========执行后置通知============");
    }

    /**
     * 最终通知:目标方法调用之后执行的通知(无论目标方法是否出现异常均执行)
      * @param jp
     */
    @After(value=EDP)
    public void doAfter(JoinPoint jp){
        System.out.println("===========执行最终通知============");
    }

    /**
     * 环绕通知:目标方法调用前后执行的通知,可以在方法调用前后完成自定义的行为。
      * @param pjp
     * @return
     * @throws Throwable
     */
    @Around(EDP)
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable{

        System.out.println("======执行环绕通知开始=========");
        // 调用方法的参数
        Object[] args = pjp.getArgs();
        // 调用的方法名
        String method = pjp.getSignature().getName();
        // 获取目标对象
        Object target = pjp.getTarget();
        // 执行完方法的返回值
        // 调用proceed()方法,就会触发切入点方法执行
        Object result=pjp.proceed();
        System.out.println("输出,方法名:" + method + ";目标对象:" + target + ";返回值:" + result);
        System.out.println("======执行环绕通知结束=========");
        return result;
    }

    /**
     * 在目标方法非正常执行完成, 抛出异常的时候会走此方法
      * @param jp
     * @param ex
     */
    @AfterThrowing(value=EDP,throwing="ex")
    public void doAfterThrowing(JoinPoint jp,Exception ex) {
        System.out.println("===========执行异常通知============");
    }
}

spring的配置:

<?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:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <!-- 声明spring对@AspectJ的支持 -->
    <aop:aspectj-autoproxy/>    

    <!-- 声明一个业务类 -->
    <bean id="userManager" class="com.spring.service.impl.UserManagerServiceImpl">
        <property name="name" value="lixiaoxi"></property>
    </bean>   

     <!-- 声明通知类 -->
    <bean id="aspectBean" class="com.spring.aop.AopAspectJ" />

</beans>

测试类:

package com.spring.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.spring.service.IUserManagerService;

public class TestAop1 {
    public static void main(String[] args) throws Exception{

        ApplicationContext act =  new ClassPathXmlApplicationContext("applicationContext4.xml");
         IUserManagerService userManager = (IUserManagerService)act.getBean("userManager");
         userManager.findUser();
         System.out.println("\n");
         userManager.addUser();
    }
}

测试结果与上面相同。

注意:
1.环绕方法通知,环绕方法通知要注意必须给出调用之后的返回值,否则被代理的方法会停止调用并返回null,除非你真的打算这么做。           
2.只有环绕通知才可以使用JoinPoint的子类ProceedingJoinPoint,各连接点类型可以调用代理的方法,并获取、改变返回值。

补充:
1.<aop:pointcut>如果位于<aop:aspect>元素中,则命名切点只能被当前<aop:aspect>内定义的元素访问到,为了能被整个<aop:config>元素中定义的所有增强访问,则必须在<aop:config>下定义切点。
2.如果在<aop:config>元素下直接定义<aop:pointcut>,必须保证<aop:pointcut>在<aop:aspect>之前定义。<aop:config>下还可以定义<aop:advisor>,三者在<aop:config>中的配置有先后顺序的要求:首先必须是<aop:pointcut>,然后是<aop:advisor>,最后是<aop:aspect>。而在<aop:aspect>中定义的<aop:pointcut>则没有先后顺序的要求,可以在任何位置定义。
.<aop:pointcut>:用来定义切入点,该切入点可以重用;
.<aop:advisor>:用来定义只有一个通知和一个切入点的切面;
.<aop:aspect>:用来定义切面,该切面可以包含多个切入点和通知,而且标签内部的通知和切入点定义是无序的;和advisor的区别就在此,advisor只包含一个通知和一个切入点。
3.在使用spring框架配置AOP的时候,不管是通过XML配置文件还是注解的方式都需要定义pointcut"切入点"
例如定义切入点表达式 execution(* com.sample.service.impl..*.*(..))
execution()是最常用的切点函数,其语法如下所示:
整个表达式可以分为五个部分:
(1)、execution(): 表达式主体。
(2)、第一个*号:表示返回类型,*号表示所有的类型。
(3)、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
(4)、第二个*号:表示类名,*号表示所有的类。
(5)、*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。

时间: 2024-09-30 00:06:25

Spring系列之AOP实现的两种方式的相关文章

Spring加载properties文件的两种方式

在项目中如果有些参数经常需要修改,或者后期可能需要修改,那我们最好把这些参数放到properties文件中,源代码中读取properties里面的配置,这样后期只需要改动properties文件即可,不需要修改源代码,这样更加方便.在Spring中也可以这么做,而且Spring有两种加载properties文件的方式:基于xml方式和基于注解方式.下面分别讨论下这两种方式. 1. 通过xml方式加载properties文件 我们以Spring实例化dataSource为例,我们一般会在beans

(01)Spring MVC之处理异常的两种方式及优先级

项目开发中异常需要统一处理,总的来说有两种方式,一种是实现HandlerExceptionResolver接口,一种是使用@ExceptionHandler注解的方式.其中Spring已经为我们提供了一个实现了HandlerExceptionResolver接口的类SimpleMappingExceptionResolver,有人把它单独列为一种方式,不过我认为方式越少越好,哈哈哈哈哈,下面记录一下Spring MVC处理异常的这两种方式. 1.实现HandlerExceptionResolve

Spring Boot 中实现定时任务的两种方式

在 Spring + SpringMVC 环境中,一般来说,要实现定时任务,我们有两中方案,一种是使用 Spring 自带的定时任务处理器 @Scheduled 注解,另一种就是使用第三方框架 Quartz ,Spring Boot 源自 Spring+SpringMVC ,因此天然具备这两个 Spring 中的定时任务实现策略,当然也支持 Quartz,本文我们就来看下 Spring Boot 中两种定时任务的实现方式. @Scheduled 使用 @Scheduled 非常容易,直接创建一个

spring之级联属性赋值的两种方式

Car.java package com.gong.spring.beans; public class Car { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Car [name=" + name + &qu

基于aspectj实现AOP操作的两种方式——xml配置

1. 要导入的 jar 包: 常用的aspectj表达式: 权限修饰符可以省略,以下表示:返回值类型为任意,com.chy.service包以及其子包下的.任意类的.参数任意的.任意方法 execution(* com.chy.service..*(..) 2. 在spring的核心配置文件中: 总结: 1. 配置切入点 2. 配置切面:把哪个增强类的哪个方法,前置增强到哪个切入点上 原文地址:https://www.cnblogs.com/cn-chy-com/p/9256048.html

spring boot中读取配置文件的两种方式

application.properties test.name=测试 test.url=www.test.com [email protected]注解 在controller里可以这样直接调用 @Value("${test.name}") private String name; @Value("${test.url}") private String url; [email protected](prefix="test") 新建一个Con

java的动态代理的两种方式和spring的aop面向切面编程的对比

java动态代理的两种方式 使用动态代理的好处:可以进行类的功能的加强,同时减少耦合和代码的冗余,耦合的意思是不用吧加强的部分写到各个实现类里面,冗余的意思是如果对每个实现类加强的部分是一样的,通过一个代理类即可实现 基于jdk的动态代理 通过jdk中自带的Proxy类进行动态的代理,Proxy创建动态代理对象的时候要传入三个参数,第一个是classloader,第二个是interfaces,第三个是代理类实现的方法 要求:代理的是一个接口,必须至少有一个实现类 创建接口的代码: /** * a

Spring Boot2 系列教程(十五)定义系统启动任务的两种方式

在 Servlet/Jsp 项目中,如果涉及到系统任务,例如在项目启动阶段要做一些数据初始化操作,这些操作有一个共同的特点,只在项目启动时进行,以后都不再执行,这里,容易想到web基础中的三大组件( Servlet.Filter.Listener )之一 Listener ,这种情况下,一般定义一个 ServletContextListener,然后就可以监听到项目启动和销毁,进而做出相应的数据初始化和销毁操作,例如下面这样: public class MyListener implements

Spring定义Bean的两种方式:和@Bean

前言:    Spring中最重要的概念IOC和AOP,实际围绕的就是Bean的生成与使用. 什么叫做Bean呢?我们可以理解成对象,每一个你想交给Spring去托管的对象都可以称之为Bean. 今天通过Spring官方文档来了解下,如何生成bean,如何使用呢? 1.通过XML的方式来生成一个bean    最简单也是最原始的一种方式,通过XML来定义一个bean,我们来看下其过程 1)创建entity,命名为Student @Data@AllArgsConstructor@NoArgsCon