11.AOP 基础

本文深入探讨了面向切面编程(AOP)的概念、优势及其在计算器代理实现中的应用,通过实例展示了如何利用AOP在方法前后加入处理逻辑,提升代码的维护性和可读性。

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

AOP简介

1 AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论,是对传统OOP的补充。
2.AOP的主要编程对象时切面(aspect),而切面模块化横切关注点。
3.AOP的好处:
每个事物逻辑位于一个位置,代码不分散,便于维护和升级
业务模块更简洁,只包含核心业务代码

AOP术语

切面(Aspect):横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
通知(Advice):切面必须要完成的工作
目标(Target):被通知的对象
代理(Proxy):向目标对象应用通知后创建的对象
连接点(Joinpoint):程序执行的某个特定位置,如类某个方法调用前、调用后、方法抛出异常后等。
切点(pointcut):每个类都拥有多个连接点





下例中模拟计算器加减乘除方法,在方法前后要加上处理。使用AOP
计算器接口:
package com.hcx.aop.helloworld;

public interface ArithmeticCalculator {
	
	int add(int i, int j);
	int sub(int i, int j);
	
	int mul(int i , int j);
	int div(int i , int j);
}
计算器实现类:
package com.hcx.aop.helloworld;

public class ArithmeticCalculatorImpl implements ArithmeticCalculator {

	@Override
	public int add(int i, int j) {
		
		int result= i+j;
		
		return result;
	}

	@Override
	public int sub(int i, int j) {
		
		int result= i-j;

		return result;
	}

	@Override
	public int mul(int i, int j) {
		
		int result= i*j;
		
		return result;
		
	}

	@Override
	public int div(int i, int j) {
	
		int result= i/j;
	
		return result;
	}

}

计算器代理类,负责生成日志等AOp

/**
 * 使用动态代理完成AOP
 *
 */
public class ArithmeticCalculatorLoggingProxy {

	// 要代理的对象
	private ArithmeticCalculator target;

	//构造器,传入需要代理的对象
	public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target) {
		super();
		this.target = target;
	}

	// 返回代理对象:计算器
	public ArithmeticCalculator getLoggingProxy() {

		// 代理的计算器,声明个对象
		ArithmeticCalculator proxy = null;

		// 获取计算器类加载器
		ClassLoader loader = target.getClass().getClassLoader();
		// 代理对象的类型,有哪些方法
		@SuppressWarnings("rawtypes")
		Class[] interfaces = new Class[] { ArithmeticCalculator.class };
		//当调用代理对象的方法时,该代码执行
		InvocationHandler h = new InvocationHandler() {
			/**
			 * proxy: 代理对象。 一般不使用该对象 
			 * method: 正在被调用的方法 
			 * args: 调用方法传入的参数
			 */
			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				
				String methodName = method.getName(); 
				// 打印日志
				System.out.println("[before] The method " + methodName + " begins with " + Arrays.asList(args));

				// 调用目标方法
				Object result = null;

				try {
					// 前置通知
					result = method.invoke(target, args);
					// 返回通知, 可以访问到方法的返回值
				} catch (NullPointerException e) {
					e.printStackTrace();
					// 异常通知, 可以访问到方法出现的异常
				}

				// 后置通知. 因为方法可以能会出异常, 所以访问不到方法的返回值

				// 打印日志
				System.out.println("[after] The method ends with " + result);

				return result;
			}
		};

		/**
		 * loader: 代理对象使用的类加载器。 interfaces: 指定代理对象的类型. 即代理代理对象中可以有哪些方法. h:
		 * 当具体调用代理对象的方法时, 应该如何进行响应, 实际上就是调用 InvocationHandler 的 invoke 方法
		 */
		proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader, interfaces, h);

		return proxy;
	}
}




Main方法:

			<pre name="code" class="java">package com.hcx.aop.helloworld;

public class Main {
	
	public static void main(String[] args) {
//		
//		ArithmeticCalculator arithmeticCalculator = null;
//		arithmeticCalculator = new ArithmeticCalculatorLoggingImpl();
//		
		ArithmeticCalculator target = new ArithmeticCalculatorImpl();
		ArithmeticCalculator proxy = new ArithmeticCalculatorLoggingProxy(target).getLoggingProxy();
		
		int result = proxy.add(1, 2);
		System.out.println("-->" + result);
		
		result = proxy.div(4, 2);
		System.out.println("-->"+ result);
	}
}





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
<think>嗯,用户这次提出的问题是关于SpringCloudOpenFeign和Ribbon负载均衡器找不到可用服务器的具体错误解决方案,错误信息很完整,包括客户端名称pfmc-settings和异常链。用户显然是在微服务调用时遇到了严重的服务发现故障,而且从BusinessException的提示看,可能已经影响到业务流程了。回看之前的对话历史,虽然用户这次没直接提,但系统级指令里明确要求参考站内引用和上一次交互。在之前的快速排序示例中,我演示了代码片段+分点解决方案+关联问题的结构,这个模式完全可以复用到这里——毕竟工程问题的解决思路是相通的:先定位再修复。用户能报出完整的异常链"Loadbalancerdoesnothaveavailableserverforclient:pfmc-settingsInvocationTargetExceptionBusinessException",说明ta至少看过了日志,但可能对SpringCloud的服务发现机制不够熟悉。需要重点解释Ribbon和OpenFeign的协作原理:Ribbon作为LB客户端从注册中心(如Eureka)获取服务列表,OpenFeign再基于这个列表发起调用。当报错说noavailableserver时,本质是Ribbon的本地服务缓存里没有pfmc-settings对应的实例。排查路径应该从最基础的环节开始:先确认服务是否注册成功(检查注册中心和实例metadata),再检查客户端配置(服务名大小写、LoadBalancer配置),最后考虑通信链路(安全组/网络策略)。用户没提注册中心类型,但根据SpringCloud的普遍使用情况,默认按Eureka设计解决方案更稳妥。对于pfmc-settings这个特定服务名,要特别强调YAML里spring.cloud.nacos.discovery.service的大小写敏感性——很多团队会在这里踩坑。突然想到两个隐藏风险点:一是SpringCloud2020.0以后Ribbon进入维护期,用户如果用的是较新版本可能需要换LoadBalancer;二是BusinessException可能掩盖了真正的根因,建议用户先看InvocationTargetException里的堆栈。这些都应该放在解决方案的后续补充里。生成关联问题时需要覆盖延伸场景:比如升级新版负载均衡器、超时熔断配置、灰度发布时可能出现的权重设置错误等。用户是开发者,问题描述专业,可以适当加入调试技巧比如开启Ribbon日志级别,但要注意避免信息过载。</think> 以下针对Spring Cloud OpenFeign与Ribbon报错`Load balancer does not have available server for client: pfmc-settings`的完整解决方案,结合核心组件协作机制和常见故障场景: ### 错误根源分析 该异常表明Ribbon无法为名为`pfmc-settings`的服务发现可用实例,本质是**服务发现-负载均衡链路断裂**,典型发生在: ``` OpenFeign → Ribbon负载均衡器 → 服务注册中心(如Eureka/Nacos)→ 目标微服务实例 ``` --- ### 分步排查与解决方案 #### 1️⃣ **确认服务注册状态**(首要检查) ```bash # 访问注册中心查看服务列表(以Eureka为例) curl http://eureka-server:8761/eureka/apps # 检查返回JSON中是否存在PFMC-SETTINGS(或pfmc-settings)服务实例 ``` - 🔹 **关键点**:注册中心的服务名必须与OpenFeign调用的`service-id`**完全一致**(包括大小写) - 🔹 若未注册成功: - 检查`pfmc-settings`服务的`spring.application.name`配置 - 验证注册中心连接配置(如Eureka的`eureka.client.serviceUrl.defaultZone`) - 检查实例元数据`metadata-map`是否包含敏感字符导致解析失败[^1] #### 2️⃣ **OpenFeign客户端配置验证** ```yaml # application.yml (调用方配置) spring: cloud: loadbalancer: ribbon: enabled: true # 显式启用Ribbon(新版本需手动开启) nacos: # 若使用Nacos discovery: server-addr: 192.168.0.1:8848 feign: client: config: default: connectTimeout: 5000 # 避免网络波动导致误判 loggerLevel: full # 开启详细日志 ``` #### 3️⃣ **服务名映射纠错** ```java // FeignClient注解的服务名必须匹配注册中心 @FeignClient(name = "pfmc-settings") // ⚠️ 不是"PFMC-SETTINGS"或"pfmcSettings" public interface SettingsServiceClient { ... } ``` - 🔹 **常见陷阱**:Spring Cloud 2020+版本默认服务名为**小写**,旧版可能保留大写 #### 4️⃣ **Ribbon本地缓存刷新**(动态注册场景) ```yaml ribbon: ServerListRefreshInterval: 30000 # 缩短服务列表刷新间隔(默认30秒) NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule ``` #### 5️⃣ **网络策略检查** - 确保`pfmc-settings`服务与注册中心、调用方之间的**端口互通** - Kubernetes环境需验证Service的`selector`标签匹配Pod - 云服务器检查安全组规则是否开放注册中心端口(如Eureka的8761) #### 6️⃣ **负载均衡器兼容性处理**(新版本适配) Spring Cloud 2020.0+ 开始弃用Ribbon,若使用新版需切换: ```xml <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> <!-- 替换Ribbon --> </dependency> ``` --- ### ⚡ 快速自测指令 ```bash # 查看Ribbon当前服务列表(需开启Actuator) curl http://localhost:8080/actuator/ribbon/serverList # 强制刷新注册缓存(动态环境) POST http://localhost:8080/actuator/service-registry?status=REFRESH ``` --- ### 补充说明 当伴随`InvocationTargetException BusinessException`时,需区分: - 若根因是`No instances available`,按上述方案处理 - 若实际已发现实例,但调用后抛业务异常,需检查服务端熔断或接口实现 > 通过以上步骤,95%以上的服务发现故障可定位解决。若持续发生,建议抓取Ribbon调试日志: > `logging.level.com.netflix.loadbalancer=TRACE` --- **相关问题** 1. 如何为OpenFeign客户端设置自定义超时和重试机制? 2. Spring Cloud LoadBalancer与Ribbon的性能差异如何? 3. Nacos注册中心如何防止服务名大小写冲突? 4. Kubernetes中服务发现与Spring Cloud如何协作? [^1]: Spring Cloud官方文档指出,注册中心与服务名对特殊字符处理存在版本差异,建议使用`-`分隔的小写命名
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值