AOP Observable

本文探讨了如何在缺少setter方法的Android Bean类中通过AspectJ实现KVO(Key-Value Observing)模式来通知数据变化。介绍了使用AspectJ切入Fieldaccess事件的基本原理及具体实施方法,包括如何避免重复通知的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在公司重构的过程中,希望用KVO的方式传递数据变化的事件。然而,在传统Android的写法中,Bean是没有setter的,就没有时机来notifyObservers了。
值得庆幸的是AspectJ能够切入Field access事件,用AOP就是一个非常好的解决方法了。项目,使用方法见README.md,这里只说原理。跪谢大神的Hugo插件

切入

非常基础的AspectJ,就是我这种半吊子写的PointCut和Advice。主要思路就是切入所有:

  • Observable子类中
  • 没有被标记为IgnoreField的field
  • 在被没有标记IgnoreInovker的类或方法中
  • 被修改的代码

分发

上面的切入有个大bug,对同一个对象的连续修改会触发多次无用的notifyObservers。为了解决这个问题,采用两个方法:

  • 对于有Looper的线程,所有行为都是以代码段(Message)的方式放置到queue中串行执行的,那么可以将一个代码段(Message)中的所有修改集中去重,并在queue的下一个Message统一分发。这个方法无感的解决掉了大部分(因为主线程是LooperThread)修改的去重
  • 对于非Looper线程,需要给修改的方法标记为IgnoreInvoker,并在修改完成后手动调用Dispatchers#notifyDataChanged。这个并不优雅,但是我并没有找到AspectJ这么复杂的PointCut声明方式。目测可以用反射和JointPoint中的内容解决问题,但是对于尽可能轻量化的切面会过重
2025-06-08 11:47:15.380 [start-eval-executor-3] ERROR com.pccw.ihr.base.web.util.SpringAsyncMehodService - Error occurred while invoking exception handler method of async method: startPlan java.lang.reflect.InvocationTargetException: null at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.apache.commons.lang3.reflect.MethodUtils.invokeMethod(MethodUtils.java:230) at org.apache.commons.lang3.reflect.MethodUtils.invokeMethod(MethodUtils.java:256) at org.apache.commons.lang3.reflect.MethodUtils.invokeMethod(MethodUtils.java:148) at com.pccw.ihr.base.web.util.SpringAsyncMehodService.doVoidAsync(SpringAsyncMehodService.java:52) at com.pccw.ihr.base.web.util.SpringAsyncMehodService$$FastClassBySpringCGLIB$$66b60535.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:749) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) at org.springframework.aop.interceptor.AsyncExecutionInterceptor.lambda$invoke$0(AsyncExecutionInterceptor.java:115) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) Caused by: java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: pfmc-settings at org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient.execute(LoadBalancerFeignClient.java:90) at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:108) at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:78) at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103) at com.sun.proxy.$Proxy248.selectFormSetByFormId(Unknown Source) at com.pccw.ihr.pfmc.execution.web.service.PfmcStartPlanService.startPlan(PfmcStartPlanService.java:225) at com.pccw.ihr.pfmc.execution.web.service.PfmcStartPlanService$$FastClassBySpringCGLIB$$d6f2fb4c.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:749) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:295) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) at com.pccw.ihr.pfmc.execution.web.service.PfmcStartPlanService$$EnhancerBySpringCGLIB$$932c812b.startPlan(<generated>) ... 17 common frames omitted Caused by: com.netflix.client.ClientException: Load balancer does not have available server for client: pfmc-settings at com.netflix.loadbalancer.LoadBalancerContext.getServerFromLoadBalancer(LoadBalancerContext.java:483) at com.netflix.loadbalancer.reactive.LoadBalancerCommand$1.call(LoadBalancerCommand.java:184) at com.netflix.loadbalancer.reactive.LoadBalancerCommand$1.call(LoadBalancerCommand.java:180) at rx.Observable.unsafeSubscribe(Observable.java:10327) at rx.internal.operators.OnSubscribeConcatMap.call(OnSubscribeConcatMap.java:94) at rx.internal.operators.OnSubscribeConcatMap.call(OnSubscribeConcatMap.java:42) at rx.Observable.unsafeSubscribe(Observable.java:10327) at rx.internal.operators.OperatorRetryWithPredicate$SourceSubscriber$1.call(OperatorRetryWithPredicate.java:127) at rx.internal.schedulers.TrampolineScheduler$InnerCurrentThreadScheduler.enqueue(TrampolineScheduler.java:73) at rx.internal.schedulers.TrampolineScheduler$InnerCurrentThreadScheduler.schedule(TrampolineScheduler.java:52) at rx.internal.operators.OperatorRetryWithPredicate$SourceSubscriber.onNext(OperatorRetryWithPredicate.java:79) at rx.internal.operators.OperatorRetryWithPredicate$SourceSubscriber.onNext(OperatorRetryWithPredicate.java:45) at rx.internal.util.ScalarSynchronousObservable$WeakSingleProducer.request(ScalarSynchronousObservable.java:276) at rx.Subscriber.setProducer(Subscriber.java:209) at rx.internal.util.ScalarSynchronousObservable$JustOnSubscribe.call(ScalarSynchronousObservable.java:138) at rx.internal.util.ScalarSynchronousObservable$JustOnSubscribe.call(ScalarSynchronousObservable.java:129) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48) at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30) at rx.Observable.subscribe(Observable.java:10423) at rx.Observable.subscribe(Observable.java:10390) at rx.observables.BlockingObservable.blockForSingle(BlockingObservable.java:443) at rx.observables.BlockingObservable.single(BlockingObservable.java:340) at com.netflix.client.AbstractLoadBalancerAwareClient.executeWithLoadBalancer(AbstractLoadBalancerAwareClient.java:112) at org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient.execute(LoadBalancerFeignClient.java:83) ... 31 common frames omitted 2025-06-08 11:47:15.381 [start-eval-executor-3] ERROR c.p.i.b.w.aop.interceptor.IhrAsyncExceptionHandler - Unexpected exception occurred invoking async method: public void com.pccw.ihr.base.web.util.SpringAsyncMehodService.doVoidAsync(java.lang.Object,java.lang.String,java.lang.Object[]) 2025-06-08 11:47:15.381 [start-eval-executor-3] ERROR c.p.i.b.w.aop.interceptor.IhrAsyncExceptionHandler - Params: [com.pccw.ihr.pfmc.execution.web.service.PfmcStartPlanService@cc9ef8d, startPlan, [Ljava.lang.Object;@61d112bd] 2025-06-08 11:47:15.381 [start-eval-executor-3] ERROR c.p.i.b.w.aop.interceptor.IhrAsyncExceptionHandler - Error detail: com.pccw.ihr.base.provider.exception.BusinessException: 异步执行任务出错 at com.pccw.ihr.base.web.util.SpringAsyncMehodService.doVoidAsync(SpringAsyncMehodService.java:55) at com.pccw.ihr.base.web.util.SpringAsyncMehodService$$FastClassBySpringCGLIB$$66b60535.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:749) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) at org.springframework.aop.interceptor.AsyncExecutionInterceptor.lambda$invoke$0(AsyncExecutionInterceptor.java:115) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748)
06-09
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值