Spring AOP中自我调用的问题

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 前几天在做项目的时候同事说,在使用AOP进行拦截的时候发现有些方法有时候能输出拦截的日志有时候不输出拦截的日志。发现在单独调用这些方法的时候是有日志输出,在被同一个类中的方法调用的时候没有日志输出。

前几天在做项目的时候同事说,在使用AOP进行拦截的时候发现有些方法有时候能输出拦截的日志有时候不输出拦截的日志。发现在单独调用这些方法的时候是有日志输出,在被同一个类中的方法调用的时候没有日志输出。我记得之前看过一篇文章是讲Spring事务自我调用不起作用的问题,应该是同样的问题(如果要观看那篇文章请点击这里http://jinnianshilongnian.iteye.com/blog/1487235)。这里先说一下AOP拦截不到自我调用方法的原因:假设我们有一个类是ServiceA,这个类中有一个A方法,A方法中又调用了B方法。当我们使用AOP进行拦截的时候,首先会创建一个ServiceA的代理类,其实在我们的系统中是存在两个ServiceA的对象的,一个是目标ServiceA对象,一个是生成的代理ServiceA对象,如果在代理类的A方法中调用代理类的B方法,这个时候AOP拦截是可以生效的,但是如果在代理类的A方法中调用目标类的B方法,这个时候AOP拦截是不生效的,大多数情况下我们都是在代理类的A方法中直接调用目标类的B方法。那么这种情况我们怎么来解决呢?这里我们简单的写了三种解决方法,但是我们首先先做一些准备动作。

先前准备:

Service类:

package com.zkn.spring.miscellaneous.service;

/**
 *  自我调用的服务类
 * @author zkn
 */
public interface SelfCallService {
    /**
     * 方法A
     */
    void selfCallA();

    /**
     * 方法B
     */
    void selfCallB();
}

Service的实现类:

package com.zkn.spring.miscellaneous.service.impl;

import com.zkn.spring.miscellaneous.service.SelfCallService;
import org.springframework.aop.support.AopUtils;
import org.springframework.stereotype.Service;

/**
 * @author zkn
 */
@Service
public class SelfCallServiceImpl implements SelfCallService{
    /**
     * 方法A
     */
    public void selfCallA() {

        System.out.println("我是方法A");
        System.out.println("是否是AOP拦截:" + AopUtils.isAopProxy(this));
        this.selfCallB();
    }

    /**
     * 方法B
     */
    public void selfCallB() {
        System.out.println("我是方法B");
    }
}

AOP配置类:

package com.zkn.spring.miscellaneous.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * @author zkn
 */
@Component
@Aspect
public class SelfCallAOP {

    @Pointcut("execution(* com.zkn.spring.miscellaneous.service.SelfCallService.*(..))")
    public void pointCut(){

    }
    @Around("pointCut()")
    public void aroundAdvice(ProceedingJoinPoint pjp){
        //获取签名的信息
        Signature signature = pjp.getSignature();
        System.out.println("被拦截的方法名为:"+signature.getName());
        try {
            pjp.proceed();
            System.out.println("方法执行完成:"+signature.getName());
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
}

Controller类:

package com.zkn.spring.miscellaneous.controller;

import com.zkn.spring.miscellaneous.service.SelfCallService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 *
 * @author zkn
 * @date 2017/05/16
 */
@RestController
public class ProcessSelfCallController {
    @Autowired
    private SelfCallService selfCallService;

    @RequestMapping("processSelfCallA")
    public String processSelfCallA() {

        selfCallService.selfCallA();
        return "处理自我调用!";
    }

    @RequestMapping("processSelfCallB")
    public String processSelfCallB() {
        selfCallService.selfCallB();
        return "直接调用方法B!";
    }
}

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: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/context
       http://www.springframework.org/schema/context/spring-context-3.2.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.2.xsd"
       default-autowire="byName">
    <!--开启注解-->
    <context:annotation-config/>
    <!--扫描基础包 这里要注意的是如果SpringMVC和Spring扫描的包是一样的话,AOP的配置可能会失效-->
    <context:component-scan base-package="com.zkn.spring.miscellaneous.service"/>
    <context:component-scan base-package="com.zkn.spring.miscellaneous.aop"/>
    <!--配置AOP proxy-target-class为true的时候是用Cglib动态代理,false的时候启用JDK动态代理-->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>

SpringMVC的配置文件:

<?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:mvc="http://www.springframework.org/schema/mvc"
       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/mvc
		http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
        http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.2.xsd"
       default-autowire="byName">
    <!--请求解析器-->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="messageConverters">
            <list>
                <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
                <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                    <property name="supportedMediaTypes">
                        <list>
                            <value>text/plain;charset=utf-8</value>
                            <value>text/html;charset=UTF-8</value>
                        </list>
                    </property>
                </bean>
            </list>
        </property>
    </bean>
    <!--先开启MVC的注解扫描-->
    <mvc:annotation-driven/>
    <!--开启注解扫描-->
    <context:component-scan base-package="com.zkn.spring.miscellaneous.controller"/>
</beans>
这里需要注意的是:网上有很多人说自己在SpringMVC配置的AOP不起作用。原因是在SpringMVC的配置文件中开启自动扫描的包和在Spring的配置文件中开启自动扫描的包一样,而SpringMVC的自动扫描覆盖了Spring的自动扫描(子父容器)。所以这里最好SpringMVC只扫描Controller这一层的包,其他的包交给Spring来扫描。

OK我们的准备动作已经完成了下面看我们的解决办法:

通过ThreadLocal暴露代理对象

第一种解决方式是通过ThreadLocal暴露代理对象的方式。我们只需要做这两步就行了。第一步:Spring的配置文件中将<aop:aspectj-autoproxy proxy-target-class="true" />改为:
 <aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
第二步:修改Service的实现类,修改为如下:
@Service
public class SelfCallServiceImpl implements SelfCallService {
    /**
     * 方法A
     */
    public void selfCallA() {
        //通过暴露ThreadLocal的方式获取代理对象
        ((SelfCallService)AopContext.currentProxy()).selfCallB();
    }
    /**
     * 方法B
     */
    public void selfCallB() {
        System.out.println("我是方法B");
    }
}
下面我们访问以下看看效果如何:http://localhost:8080/processSelfCallA,结果如下所示:


从上图中可以看到selfCallA和selfCallB两个方法都被拦截到了,说明我们的配置生效了。

通过初始化方法的方式:

如果我们使用这一种方式的话,那么我们需要做的是需要注入ApplicationContext对象,然后从ApplicationContext对象中获取被代理的类。注意:配置文件不做变动。具体做法如下:

@Service
public class SelfCallServiceImpl implements SelfCallService{
    //注入ApplicationContext对象
    @Autowired
    //(1)
    private ApplicationContext applicationContext;
    //(2)
    private SelfCallService selfCallService;
    //(3)
    //在所有属性被设置完值之后被调用(在Spring容器的声明周期中也只会被调用一次)
    //也可以通过实现InitializingBean接口,实现afterPropertiesSet方法 如果是使用XML配置的话,也可以通过指定init-method的方式
    //执行顺序PostConstruct-->afterPropertiesSet-->init-method
    @PostConstruct
    public void setSelfCallService(){
        selfCallService = applicationContext.getBean(SelfCallServiceImpl.class);
    }
    /**
     * 方法A
     */
    public void selfCallA() {
        //第二种方式 从上下文中获取被代理的对象 标号为(1)、(2)、(3)、(4)的就是第二种实现自我调用的方式
        //这种方式的缺点是:不能解决scope为prototype的bean。
        //(4)
        selfCallService.selfCallB();
    }
    /**
     * 方法B
     */
    public void selfCallB() {
        System.out.println("我是方法B");
    }
}
在指定初始化方法这里我们使用了注解的方式,即指定了@PostConstruct这个注解,注意这个注解是JDK提供的,不是Spring提供的。PS:指定初始化方法我们最少有这样三种方式可以达到这样的效果。一:使用@PostConstruct注解;二:实现InitializingBean接口,实现afterPropertiesSet方法(不推荐,对代码的侵入性较强);三:通过xml配置文件指定init-method的方式。这部分的内容属于Spring Bean生命周期的范围,会在下一篇文章中详细介绍。

效果和上面使用ThreadLocal暴露代理对象是一样的。

通过BeanPostProcessor的方式:

这一种方式需要我们定义一个接口,这个接口用来区分和设置被代理对象。具体的做法如下:

1、定义一个专门用来处理自我调用的Service:

public interface SelfCallWrapperService {
    /**
     * 设置自我调用的对象
     * @param obj
     */
    void setSelfObj(Object obj);
}

2、定义一个类用实现BeanPostProcessor接口:

public class BeanPostProcessorSelfCall implements BeanPostProcessor {

    public Object postProcessBeforeInitialization(Object bean, String s) throws BeansException {

        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String s) throws BeansException {
        if (bean instanceof SelfCallWrapperService) {
            ((SelfCallWrapperService)bean).setSelfObj(bean);
        }
        return bean;
    }
}

3、让自我调用的Service实现1中的接口:

@Service
public class SelfCallServiceImpl implements SelfCallService, SelfCallWrapperService {

    private SelfCallService selfCall;
    /**
     * 方法A
     */
    public void selfCallA() {
        selfCall.selfCallB();
    }
    /**
     * 方法B
     */
    public void selfCallB() {
        System.out.println("我是方法B");
    }
    /**
     * 设置自我调用的对象
     *
     * @param obj
     */
    public void setSelfObj(Object obj) {
        selfCall = (SelfCallService)obj;
    }
}
BeanPostProcessor也是Spring Bean生命周期中的内容。同样在下一章会做介绍。
它的效果也是和是用ThreadLocal的方式一样的。

这三种方式各有特点:使用ThreadLocal的方式最为简单,并且各种情况都能适用;使用初始化方法的方式不能解决Bean为prototype的情景(或许配合lookup-method可能解决这个问题),对于循环依赖的支持也可能会有问题;使用BeanPostProcessor的方式对循环依赖注入的支持会有问题。不过我们在项目中配置循环依赖的情况可能会比较少一些,Bean配置为prototype的情景可能会更少(我只在电票的项目中用过。。。。。)。正常情况下这三种方式我们都是可以正常使用的。

参考:

http://jinnianshilongnian.iteye.com/blog/1487235


相关实践学习
通过日志服务实现云资源OSS的安全审计
本实验介绍如何通过日志服务实现云资源OSS的安全审计。
相关文章
|
1月前
|
XML 安全 Java
使用 Spring 的 @Aspect 和 @Pointcut 注解简化面向方面的编程 (AOP)
面向方面编程(AOP)通过分离横切关注点,如日志、安全和事务,提升代码模块化与可维护性。Spring 提供了对 AOP 的强大支持,核心注解 `@Aspect` 和 `@Pointcut` 使得定义切面与切入点变得简洁直观。`@Aspect` 标记切面类,集中处理通用逻辑;`@Pointcut` 则通过表达式定义通知的应用位置,提高代码可读性与复用性。二者结合,使开发者能清晰划分业务逻辑与辅助功能,简化维护并提升系统灵活性。Spring AOP 借助代理机制实现运行时织入,与 Spring 容器无缝集成,支持依赖注入与声明式配置,是构建清晰、高内聚应用的理想选择。
300 0
|
5月前
|
监控 安全 Java
Spring AOP实现原理
本内容主要介绍了Spring AOP的核心概念、实现机制及代理生成流程。涵盖切面(Aspect)、连接点(Join Point)、通知(Advice)、切点(Pointcut)等关键概念,解析了JDK动态代理与CGLIB代理的原理及对比,并深入探讨了通知执行链路和责任链模式的应用。同时,详细分析了AspectJ注解驱动的AOP解析过程,包括切面识别、切点表达式匹配及通知适配为Advice的机制,帮助理解Spring AOP的工作原理与实现细节。
|
2月前
|
人工智能 监控 安全
Spring AOP切面编程颠覆传统!3大核心注解+5种通知类型,让业务代码纯净如初
本文介绍了AOP(面向切面编程)的基本概念、优势及其在Spring Boot中的使用。AOP作为OOP的补充,通过将横切关注点(如日志、安全、事务等)与业务逻辑分离,实现代码解耦,提升模块化程度、可维护性和灵活性。文章详细讲解了Spring AOP的核心概念,包括切面、切点、通知等,并提供了在Spring Boot中实现AOP的具体步骤和代码示例。此外,还列举了AOP在日志记录、性能监控、事务管理和安全控制等场景中的实际应用。通过本文,开发者可以快速掌握AOP编程思想及其实践技巧。
|
2月前
|
人工智能 监控 安全
如何快速上手【Spring AOP】?核心应用实战(上篇)
哈喽大家好吖~欢迎来到Spring AOP系列教程的上篇 - 应用篇。在本篇,我们将专注于Spring AOP的实际应用,通过具体的代码示例和场景分析,帮助大家掌握AOP的使用方法和技巧。而在后续的下篇中,我们将深入探讨Spring AOP的实现原理和底层机制。 AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架中的核心特性之一,它能够帮助我们解决横切关注点(如日志记录、性能统计、安全控制、事务管理等)的问题,提高代码的模块化程度和复用性。
|
2月前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
|
9月前
|
XML Java 开发者
Spring Boot中的AOP实现
Spring AOP(面向切面编程)允许开发者在不修改原有业务逻辑的情况下增强功能,基于代理模式拦截和增强方法调用。Spring Boot通过集成Spring AOP和AspectJ简化了AOP的使用,只需添加依赖并定义切面类。关键概念包括切面、通知和切点。切面类使用`@Aspect`和`@Component`注解标注,通知定义切面行为,切点定义应用位置。Spring Boot自动检测并创建代理对象,支持JDK动态代理和CGLIB代理。通过源码分析可深入了解其实现细节,优化应用功能。
434 6
|
8月前
|
XML Java 测试技术
Spring AOP—通知类型 和 切入点表达式 万字详解(通俗易懂)
Spring 第五节 AOP——切入点表达式 万字详解!
390 25
|
8月前
|
XML 安全 Java
Spring AOP—深入动态代理 万字详解(通俗易懂)
Spring 第四节 AOP——动态代理 万字详解!
305 24
|
7月前
|
Java API 微服务
微服务——SpringBoot使用归纳——Spring Boot中的切面AOP处理——Spring Boot 中的 AOP 处理
本文详细讲解了Spring Boot中的AOP(面向切面编程)处理方法。首先介绍如何引入AOP依赖,通过添加`spring-boot-starter-aop`实现。接着阐述了如何定义和实现AOP切面,包括常用注解如`@Aspect`、`@Pointcut`、`@Before`、`@After`、`@AfterReturning`和`@AfterThrowing`的使用场景与示例代码。通过这些注解,可以分别在方法执行前、后、返回时或抛出异常时插入自定义逻辑,从而实现功能增强或日志记录等操作。最后总结了AOP在实际项目中的重要作用,并提供了课程源码下载链接供进一步学习。
778 0
|
7月前
|
Java 开发者 微服务
微服务——SpringBoot使用归纳——Spring Boot中的切面AOP处理——什么是AOP
本文介绍了Spring Boot中的切面AOP处理。AOP(Aspect Oriented Programming)即面向切面编程,其核心思想是分离关注点。通过AOP,程序可以将与业务逻辑无关的代码(如日志记录、事务管理等)从主要逻辑中抽离,交由专门的“仆人”处理,从而让开发者专注于核心任务。这种机制实现了模块间的灵活组合,使程序结构更加可配置、可扩展。文中以生活化比喻生动阐释了AOP的工作原理及其优势。
386 0