spring AOP对父类方法加强分析

本文探讨了Spring AOP在子类调用父类方法时的增强机制问题,揭示了为何直接调用父类增强方法无效,以及如何通过配置和特殊调用方式实现方法增强。

spring AOP可以对方法进行加强,就是在方法前执行一些想要的事情,执行方法后想执行一些信息,原理就是利用动态代理,具体不在阐述
今天要讨论的是一个springBean继承了父类,在父类里进行了方法加强,子类调用父类的方法,父类方法会加强吗?
注解@CommonLogger是我利用AOP Aspect写的一个打印方法参数的注解,就是在方法上加了这个注解,这个方法的参数就会打印出来,并且对日志进行了脱敏处理

现在有以下父类

public  class LogFilterServiceParent {
		private Logger logger = LoggerFactory.getLogger(getClass());
	
		@CommonLogger(positions={Position.PARAMTER})
		public void log2(OrderDetailDTO dto,String str) {
		logger.info("--------------------------------LogFilterServiceParent---------------------------");
		}
}

子类

@Service
public class LogFilterServiceImpl extends LogFilterServiceParent implements LogFilterService {
	private Logger logger = LoggerFactory.getLogger(getClass());
	
	@Override
	public void log(OrderDetailDTO dto,String str) {
		this.log2(dto, str);
		logger.info("--------------------------------LogFilterServiceImpl---------------------------");
	}

}

public interface LogFilterService {
	void log(OrderDetailDTO dto,String str);
}

测试类

@RunWith(SpringJUnit4ClassRunner.class)//其他相关配置省去
public class LogFilterServiceTest extends BaseSARTest {
	@Autowired
	private LogFilterService logFilterService;
	
	private Logger logger = LoggerFactory.getLogger(getClass());
	
	@Test
	public void testLogFilter() {
		OrderDetailDTO dto = new OrderDetailDTO();
		
		NbiCustomerInfoDTO custom = new NbiCustomerInfoDTO();
		custom.setBirthday("2016-02-25");
		custom.setDetailAddress("广东生深圳市罗湖区大剧院京基100大厦");
		custom.setEmail("12355@qq.com");
		custom.setCardId("440515199212237208");
		custom.setMobileNo("13698545691");
		custom.setName("张山疯");
		
		
		dto.setApplyPolicyNo("12365998");
		dto.setCurrentSysDate(new Date());
		dto.setOrderNo("AA201807130849120300010000001");
		dto.setPolicyHolder(custom);
		
		List<NbiCustomerInfoDTO> list = new ArrayList<NbiCustomerInfoDTO>();
		list.add(custom);
		list.add(custom1);
		dto.setCustomerInfoList(list);
		dto.setOrderStatus(2);
		logFilterService.log(dto,"440515199212237208");
	}
	
	
	
	
}

我想达到的效果是,LogFilterServiceImpl调用父类的log2方法,log2方法能够打印入参,但实际上没有打印,并且报了以下错误

[14:30:23.235] [DEBUG] [<T=H0MXGCAmY01yBPqI>] AnnotationAwareAspectJAutoProxyCreator: Creating implicit proxy for bean 'logFilterServiceImpl' with 0 common interceptors and 2 specific interceptors
[14:30:23.235] [DEBUG] [<T=H0MXGCAmY01yBPqI>] CglibAopProxy: Creating CGLIB proxy: target source is SingletonTargetSource for target object [com.shux.trade.online.service.LogFilterServiceImpl@456ce9]
[14:30:23.235] [DEBUG] [<T=H0MXGCAmY01yBPqI>] CglibAopProxy: Unable to apply any optimisations to advised method: public void com.shux.trade.online.service.LogFilterServiceImpl.log(com.shux.trade.online.dto.OrderDetailDTO,java.lang.String)
[14:30:23.235] [DEBUG] [<T=H0MXGCAmY01yBPqI>] CglibAopProxy: Unable to apply any optimisations to advised method: public void com.shux.trade.online.service.LogFilterServiceParent.log2(com.shux.trade.online.dto.OrderDetailDTO,java.lang.String)
[14:30:23.235] [DEBUG] [<T=H0MXGCAmY01yBPqI>] CglibAopProxy: Found 'hashCode' method: public native int java.lang.Object.hashCode()
[14:30:23.235] [DEBUG] [<T=H0MXGCAmY01yBPqI>] CglibAopProxy: Found 'equals' method: public boolean java.lang.Object.equals(java.lang.Object)
[14:30:23.235] [DEBUG] [<T=H0MXGCAmY01yBPqI>] CglibAopProxy: Unable to apply any optimisations to advised method: protected native java.lang.Object java.lang.Object.clone() throws java.lang.CloneNotSupportedException
[14:30:23.235] [DEBUG] [<T=H0MXGCAmY01yBPqI>] CglibAopProxy: Unable to apply any optimisations to advised method: public java.lang.String java.lang.Object.toString()
[14:30:23.235] [DEBUG] [<T=H0MXGCAmY01yBPqI>] CglibAopProxy: Found finalize() method - using NO_OVERRIDE
[14:30:23.235] [DEBUG] [<T=H0MXGCAmY01yBPqI>] CglibAopProxy: Method is declared on Advised interface: public abstract void org.springframework.aop.framework.Advised.addAdvice(int,org.aopalliance.aop.Advice) throws org.springframework.aop.framework.AopConfigException

没懂这个报错的意思

按照继承的规则,子类自动拥有父类的方法,按理应该spring会给加了注解的方法加强,但实际上没有
查阅spring aop相关的内容,发现spring AOP不支持同类方法调用,意思就是同一个spring Bean中,如果多个方法中都用了方法加强,一个方法调用了另外一个方法,另外一个方法(被调用方法)是无法加强的,于是我改了一下
在子类的接口中增加父类的方法

public interface LogFilterService {
	void log(OrderDetailDTO dto,String str);
 void log2(OrderDetailDTO dto,String str);
}

测试类直接调用父类的这个方法log2,测试方法改成如下

@Test
	public void testLogFilter() {
		OrderDetailDTO dto = new OrderDetailDTO();
		
		NbiCustomerInfoDTO custom = new NbiCustomerInfoDTO();
		custom.setBirthday("2016-02-25");
		custom.setDetailAddress("广东生深圳市罗湖区大剧院京基100大厦");
		custom.setEmail("12355@qq.com");
		custom.setCardId("440515199212237208");
		custom.setMobileNo("13698545691");
		custom.setName("张山疯");
		
		
		dto.setApplyPolicyNo("12365998");
		dto.setCurrentSysDate(new Date());
		dto.setOrderNo("AA201807130849120300010000001");
		dto.setPolicyHolder(custom);
		
		List<NbiCustomerInfoDTO> list = new ArrayList<NbiCustomerInfoDTO>();
		list.add(custom);
		list.add(custom1);
		dto.setCustomerInfoList(list);
		dto.setOrderStatus(2);
		logFilterService.log2(dto,"440515199212237208");//这里有变化

	}

发现日志打印出来了,意思就是父类的方法加强起效了,然我又把带接口的方式放到方法log中,如下

@Service
public class LogFilterServiceImpl extends LogFilterServiceParent implements LogFilterService {
	private Logger logger = LoggerFactory.getLogger(getClass());
	
	@Override
	public void log(OrderDetailDTO dto,String str) {
		this.log2(dto, str);
		logger.info("--------------------------------LogFilterServiceImpl---------------------------");
	}

}

测试类

@Test
	public void testLogFilter() {
		OrderDetailDTO dto = new OrderDetailDTO();
		
		NbiCustomerInfoDTO custom = new NbiCustomerInfoDTO();
		custom.setBirthday("2016-02-25");
		custom.setDetailAddress("广东生深圳市罗湖区大剧院京基100大厦");
		custom.setEmail("12355@qq.com");
		custom.setCardId("440515199212237208");
		custom.setMobileNo("13698545691");
		custom.setName("张山疯");
		
		
		dto.setApplyPolicyNo("12365998");
		dto.setCurrentSysDate(new Date());
		dto.setOrderNo("AA201807130849120300010000001");
		dto.setPolicyHolder(custom);
		
		List<NbiCustomerInfoDTO> list = new ArrayList<NbiCustomerInfoDTO>();
		list.add(custom);
		list.add(custom1);
		dto.setCustomerInfoList(list);
		dto.setOrderStatus(2);
		logFilterService.log(dto,"440515199212237208");//这里有变化

	}

发现还是不能打印日志,正对这种情况我又百度了一下,要使调用本类的方法并且是方法加强有效,则必须使用以下方式
在spring配置文件中,aop:aspectj-autoproxy 标签增加expose-proxy="true"属性,如下配置
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
 

public  class LogFilterServiceParent {
	private Logger logger = LoggerFactory.getLogger(getClass());
	
	@CommonLogger(positions={Position.PARAMTER})
	public void log2(OrderDetailDTO dto,String str) {
		//logger.debug(sensitivityMessageFilter.doSensitivityFilter(dto));
		logger.info("--------------------------------LogFilterServiceParent---------------------------");
	}
}

然后子类调用父类的log2方法用以下方式调用

public void log(OrderDetailDTO dto,String str) {
		((LogFilterServiceImpl)AopContext.currentProxy()).log2(dto, str);
		logger.info("--------------------------------LogFilterServiceImpl---------------------------");
}

然后在调用测试类

@Test
	public void testLogFilter() {
		OrderDetailDTO dto = new OrderDetailDTO();
		
		NbiCustomerInfoDTO custom = new NbiCustomerInfoDTO();
		custom.setBirthday("2016-02-25");
		custom.setDetailAddress("广东生深圳市罗湖区大剧院京基100大厦");
		custom.setEmail("12355@qq.com");
		custom.setCardId("440515199212237208");
		custom.setMobileNo("13698545691");
		custom.setName("张山疯");
		
		
		dto.setApplyPolicyNo("12365998");
		dto.setCurrentSysDate(new Date());
		dto.setOrderNo("AA201807130849120300010000001");
		dto.setPolicyHolder(custom);
		
		List<NbiCustomerInfoDTO> list = new ArrayList<NbiCustomerInfoDTO>();
		list.add(custom);
		list.add(custom1);
		dto.setCustomerInfoList(list);
		dto.setOrderStatus(2);
		logFilterService.log(dto,"440515199212237208");//这里有变化

	}

发现可以打印日志了
the params of com.shux.trade.online.service.LogFilterServiceParent.log2 is :dto:{"orderNo":"IC201807130849120300010000001","currentSysDate":1547541805585,"policyHolder":{"cardId":"4****************8","birthday":"******","email":"******@qq.com","name":"*山疯","mobileNo":"1369****691","sortNumber":0,"detailAddress":"广东生深圳市罗湖区******"},"orderStatus":2,"customerInfoList":[{"cardId":"4****************8","birthday":"******","email":"******@qq.com","name":"*山疯","sortNumber":0,"mobileNo":"1369****691","detailAddress":"广东生深圳市罗湖区******"},{"cardId":"4****************8","birthday":"******","email":"******@qq.com","name":"*山疯","sortNumber":0,"mobileNo":"1332****424","detailAddress":"广东生深圳市罗湖区******"}],"applyPolicyNo":"12365998"},str:"4****************8"

为什么会这样?为什么直接掉没有用
其实这个也很简单,大家都知道spring的AOP其实是动态代理,没有走AOP加强说明了没有走代理,根据spring的springBean自动注入和动态代理
自动注入的都是其他类,自己是不会注入到自己本类的,自然就没有自动注入代理类了,自己掉自己没有走代理,自然就不会有加强了
为了解决这种问题,spring提供了AopContext类。
到时这样做会比较麻烦,大师们有更好的办法吗?

<think>我们正在讨论SpringAOP的原理和使用场景。根据引用内容,我们可以总结出以下关键点:1.**AOP概念**:AOP(面向切面编程)是一种通过预编译方式和运行期间动态代理实现程序功能统一维护的技术(引用[1])。2.**实现技术**:SpringAOPAOP的一种实现,它借用了AspectJ的注解(如@Aspect,@Before等),但底层使用动态代理(引用[1][3])。3.**动态代理方式**:-如果目标类实现了接口,SpringAOP默认使用JDK动态代理。-如果目标类没有实现接口,则使用CGLIB生成代理类(引用[3])。4.**使用场景**:日志记录、事务管理(如@Transactional)、安全检查等(引用[2]示例中提到了@Transactional和安全检查)。5.**注意事项**:-自调用问题:同一个类中的方法调用不会经过代理(引用[2])。-切面顺序:使用@Order注解控制多个切面的执行顺序(引用[2])。现在,我将详细解释SpringAOP的原理和使用场景。###SpringAOP原理SpringAOP的核心是**动态代理**。在运行时,Spring会为被代理的目标对象生成一个代理对象,这个代理对象会拦截对目标方法的调用,并在方法执行前后插入切面逻辑(如前置通知、后置通知等)。具体实现有两种方式:1.**JDK动态代理**:基于接口。当目标类实现了至少一个接口时,Spring会使用`java.lang.reflect.Proxy`创建代理对象。代理对象会实现相同的接口,并将方法调用转发给`InvocationHandler`,由它负责执行切面逻辑和原方法。2.**CGLIB代理**:基于子类。当目标类没有实现接口时,Spring使用CGLIB库生成目标类的子类作为代理。代理类重写父类方法,并在方法中插入切面逻辑。###使用场景SpringAOP适用于以下场景:1.**日志记录**:在方法执行前后记录日志,便于调试和监控。2.**事务管理**:如Spring的`@Transactional`注解,通过AOP实现声明式事务。3.**安全检查**:在方法执行前进行权限验证。4.**性能监控**:统计方法执行时间。5.**异常处理**:统一捕获并处理异常。###示例代码下面是一个简单的日志切面示例:```java@Aspect@ComponentpublicclassLoggingAspect{//定义切入点:所有Service包下的方法@Pointcut("execution(*com.example.service.*.*(..))")publicvoidserviceMethods(){}//前置通知:在方法执行前记录日志@Before("serviceMethods()")publicvoidlogBefore(JoinPointjoinPoint){System.out.println("Beforemethod:"+joinPoint.getSignature().getName());}//后置通知:在方法执行后记录日志(无论是否异常)@After("serviceMethods()")publicvoidlogAfter(JoinPointjoinPoint){System.out.println("Aftermethod:"+joinPoint.getSignature().getName());}}```###注意事项-**自调用问题**:同一个类中A方法调用B方法(B方法有切面逻辑),B方法的切面不会生效,因为调用的是目标对象的方法,而不是代理对象的方法。解决方法:从AOP上下文中获取代理对象,通过代理对象调用B方法。-**代理方式选择**:可以通过配置强制使用CGLIB代理(设置`spring.aop.proxy-target-class=true`)。§§相关问题§§1.SpringAOP中JDK动态代理和CGLIB代理有什么区别?2.如何解决SpringAOP中的自调用问题?3.SpringAOPAspectJ有什么区别和联系?4.如何配置Spring以强制使用CGLIB代理?5.SpringAOP中的通知类型有哪些?分别用在什么场景?</think>### Spring AOP原理及使用场景详解 #### 一、核心原理 Spring AOP(面向切面编程)通过**动态代理**技术实现,主要包含两种实现方式: 1. **JDK动态代理** - 适用于实现了接口的类 - 运行时生成接口的代理实例 - 示例:`UserService` 实现 `IUserService` 接口时使用 2. **CGLIB代理** - 适用于未实现接口的类 - 通过字节码增强生成目标类的子类代理 - 示例:`OrderService` 无接口时默认使用 > 动态代理过程: > $$ \text{目标对象} \xrightarrow{\text{代理工厂}} \text{代理对象} \xrightarrow{\text{拦截}} \text{增强逻辑} + \text{原方法} $$ #### 二、核心概念解析 1. **切面(Aspect)** - 使用 `@Aspect` 注解的类,包含通知和切入点 - 示例:日志切面、事务切面 2. **连接点(Join Point)** - 程序执行过程中的可插入点(如方法调用、异常抛出) 3. **通知(Advice)类型** ```java @Before("pointcut()") // 方法执行前 @AfterReturning // 方法正常返回后 @AfterThrowing // 方法抛出异常后 @After("pointcut()") // 方法结束后(finally) @Around("pointcut()") // 包裹目标方法(最强大) ``` 4. **切入点(Pointcut)** - 通过表达式定义哪些连接点会被拦截: ```java @Pointcut("execution(* com.service.*.*(..))") public void serviceMethods() {} ``` #### 三、典型使用场景 1. **声明式事务管理** ```java @Transactional // 基于AOP实现事务控制[^2] public void updateUser(User user) { ... } ``` 2. **统一日志记录** ```java @Around("serviceMethods()") public Object logMethod(ProceedingJoinPoint pjp) { System.out.println("调用方法: " + pjp.getSignature()); return pjp.proceed(); } ``` 3. **权限校验** ```java @Before("@annotation(RequireAdmin)") public void checkAdmin() { if(!currentUser.isAdmin()) throw new SecurityException(); } ``` 4. **性能监控** ```java @Around("serviceMethods()") public Object monitor(ProceedingJoinPoint pjp) { long start = System.currentTimeMillis(); Object result = pjp.proceed(); System.out.println("执行耗时: " + (System.currentTimeMillis()-start)); return result; } ``` #### 四、关键注意事项 1. **自调用问题** 同类内部方法调用不会触发代理[^2]: ```java public void methodA() { methodB(); // 不会触发AOP增强 } @Transactional public void methodB() { ... } ``` **解决方案**:通过AopContext获取代理对象: ```java ((UserService) AopContext.currentProxy()).methodB(); ``` 2. **切面执行顺序** 使用 `@Order` 控制多个切面的优先级[^2]: ```java @Aspect @Component @Order(1) // 先执行 public class SecurityAspect { ... } @Aspect @Component @Order(2) // 后执行 public class LoggingAspect { ... } ``` 3. **代理方式选择** - 默认策略:有接口用JDK代理,无接口用CGLIB - 强制CGLIB:设置 `spring.aop.proxy-target-class=true` #### 五、与AspectJ的关系 - **Spring AOP**:运行时动态代理,无需特殊编译(轻量级) - **AspectJ**:编译时/类加载时织入(功能更强大) - **混合使用**:Spring可整合AspectJ注解(如 `@Aspect`)[^1] > 适用场景对比: > | 特性 | Spring AOP | AspectJ | > |--------------|------------|---------| > | 织入时机 | 运行时 | 编译/类加载时 | > | 性能 | 中等 | 更高 | > | 切入点支持 | 方法级别 | 字段/构造器等 | > | 依赖 | 仅Spring | 独立 |
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值