前几天在做项目的时候同事说,在使用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 {@Autowiredprivate 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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.2.xsdhttp://www.springframework.org/schema/aophttp://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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc-3.2.xsdhttp://www.springframework.org/schema/contexthttp://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@PostConstructpublic 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

Spring AOP中自我调用的问题相关推荐

  1. Spring AOP中定义切点(PointCut)和通知(Advice)

    本文讨论一下Spring AOP编程中的两个关键问题,定义切点和定义通知,理解这两个问题能应付大部分AOP场景. 如果你还不熟悉AOP,请先看AOP基本原理,本文的例子也沿用了AOP基本原理中的例子. ...

  2. 正确理解Spring AOP中的Around advice

    Spring AOP中,有Before advice和After advice,这两个advice从字面上就可以很容易理解,但是Around advice就有点麻烦了. 乍一看好像是Before ad ...

  3. Spring :Spring AOP 中的一些术语

    1.美图 2.概述 2.1 连接点(Jointpoint) 连接点(Jointpoint):表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化.方法执行.方法调用.字段调用或处理异常等等,S ...

  4. java @around_正确理解Spring AOP中的Around advice

    Spring AOP中,有Before advice和After advice,这两个advice从字面上就可以很容易理解,但是Around advice就有点麻烦了. 乍一看好像是Before ad ...

  5. Spring AOP 中的切点是什么?如何定义切点?

    Spring AOP 中的切点是什么?如何定义切点? 什么是切点? 在 Spring AOP 中,切点(Pointcut)是指一组连接点(Join Point)的集合.连接点是程序执行过程中的某个特定 ...

  6. Spring AOP中定义切点PointCut详解

    1.AOP是什么? 软件工程有一个基本原则叫做"关注点分离"(Concern Separation),通俗的理解就是不同的问题交给不同的部分去解决,每部分专注于解决自己的问题.这年 ...

  7. Spring AOP中Pointcut,dvice 和 Advisor三个概念

    Spring  AOP中Pointcut,dvice 和 Advisor三个概念介绍 在理解了Spring的AOP后,需要重点理解的三个概念是:Pointcut    Advice   Advisor ...

  8. 聊聊spring aop中的advisor组件

    1.Advisor的组成结构 在spring aop中Advisor是一个接口,代表被拦截方法需要增强的逻辑. Advisor通常由另两个组件组成--Advice接口和Pointcut接口,其中Adv ...

  9. Spring AOP中是如何注册Advisor的?

    前置博文: Spring AOP中如何为Bean创建代理? Spring AOP中是如何注册Advisor的? Spring AOP如何为目标方法创建拦截器链? Spring AOP拦截器调用的实现 ...

最新文章

  1. System.Threading.Timer类的TimerCallback 委托
  2. php时间类函数吗,关于PHP 内置时间类函数的一个小问题
  3. 基于windows使用fabric将gitlab的文件远程同步到服务器(本地)
  4. 【Flink】Flink kafka Spark 如何实现数据有序性
  5. React学习笔记2017-12-31
  6. {知道力} = {怎么做} + {为什么} 并且 {为什么} {怎么做}
  7. 若何设定Linux把持系统的启动暗码
  8. Idea中 webservice 的调用
  9. 微信HOOK 1.获取二维码
  10. 【新手必看】渗透测试学习书籍推荐
  11. SQL连接MYSQL出现对象名无效_在SQL数据库中创建视图为什么执行时显示对象名无效?...
  12. SpringCloud禁用Eureka自我保护模式
  13. 【金猿产品展】智能媒体审校系统:内容安全生产的守护者
  14. Node.js 环境搭建过程中可能遇到的问题解决方案
  15. 基于关键链的项目风险管理
  16. unity3d android存储文件,Unity3d资源写入Android内置存储卡
  17. Quad Industries、Agfa等公司合作推出塑料12位RFID标签和带有丝印印刷电路的读出
  18. 系统设置——亮度调节
  19. 关于孩子如何进行艺术教育的思考(一)
  20. ASP.NET + MVC5 入门完整教程七 -—-- MVC基本工具(上)

热门文章

  1. 苹果手机没插耳机为什么显示耳机模式_苹果耳机不能用在国产手机上?
  2. e3mall Day05
  3. 升级maven编译插件_maven进阶插件
  4. 浏览器兼容性问题整理
  5. 对python彻底绝望_彻底绝望伤心的句子
  6. 解决文件夹正在使用问题
  7. 男人必读雄文【看后请雄起】
  8. 几点告诉你为什么《和平精英》之类的 FPS射击游戏不适合手机操作?
  9. 元宇宙的6层架构、需求及展望
  10. Weex框架开发 基于windows系统的环境搭建