spring中expose-proxy的作用与原理

先看一下几个问题,下面是段代码:

public interface UserService{
	public void a();
	public void a();
}

public class UserServiceImpl implements UserService{
	@Transactional(propagation = Propagation.REQUIRED)
	public void a(){
		this.b();
	}
	@Transactional(propagation = Propagation.REQUIRED_NEW)
	public void b(){
		System.out.println("b has been called");
	}
}

ps:代码参照《Spring源码解析》p173页。
Q1:b中的事务会不会生效?
A1:不会,a的事务会生效,b中不会有事务,因为a中调用b属于内部调用,没有通过代理,所以不会有事务产生。
Q2:如果想要b中有事务存在,要如何做?
A2:<aop:aspectj-autoproxy expose-proxy=“true”> ,设置expose-proxy属性为true,将代理暴露出来,使用AopContext.currentProxy()获取当前代理,将this.b()改为((UserService)AopContext.currentProxy()).b()

下面看下expose-proxy在spring中的作用:

在AopNamespaceUtils中的方法

public static void registerAutoProxyCreatorIfNecessary(ParserContext parserContext, Element sourceElement) {
	BeanDefinition beanDefinition = AopConfigUtils.registerAutoProxyCreatorIfNecessary(
				parserContext.getRegistry(), parserContext.extractSource(sourceElement));
		//处理proxy-target-class与expose-proxy属性
		useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
		registerComponentIfNecessary(beanDefinition, parserContext);
	}

在这里插入图片描述
在这里插入图片描述
跟踪代码至此,发现蓝色部分代码这里将该参数设置到了BeanDefinition中,那么这个参数在哪里使用的呢。跳过aop生成代理的过程,直接看代理的调用的地方,以JdkDynamicAopProxy为例,cglib方式的地方与jdk一致。对JDK方式的动态代理,都是通过invoke方法执行目标对象的方法,那么来看一下spring中JdkDynamicAopProxy的invoke方法源码。

/**
	 * Implementation of {@code InvocationHandler.invoke}.
	 * <p>Callers will see exactly the exception thrown by the target,
	 * unless a hook method throws an exception.
	 */
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		MethodInvocation invocation;
		Object oldProxy = null;
		boolean setProxyContext = false;

		TargetSource targetSource = this.advised.targetSource;
		Class<?> targetClass = null;
		Object target = null;

		try {
			if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
				// The target does not implement the equals(Object) method itself.
				return equals(args[0]);
			}
			if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
				// The target does not implement the hashCode() method itself.
				return hashCode();
			}
			if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
					method.getDeclaringClass().isAssignableFrom(Advised.class)) {
				// Service invocations on ProxyConfig with the proxy config...
				return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
			}

			Object retVal;

			if (this.advised.exposeProxy) {
				// Make invocation available if necessary.
				oldProxy = AopContext.setCurrentProxy(proxy);
				setProxyContext = true;
			}

			// May be null. Get as late as possible to minimize the time we "own" the target,
			// in case it comes from a pool.
			target = targetSource.getTarget();
			if (target != null) {
				targetClass = target.getClass();
			}

			// Get the interception chain for this method.
			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

			// Check whether we have any advice. If we don't, we can fallback on direct
			// reflective invocation of the target, and avoid creating a MethodInvocation.
			if (chain.isEmpty()) {
				// We can skip creating a MethodInvocation: just invoke the target directly
				// Note that the final invoker must be an InvokerInterceptor so we know it does
				// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
				retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
			}
			else {
				// We need to create a method invocation...
				invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
				// Proceed to the joinpoint through the interceptor chain.
				retVal = invocation.proceed();
			}

			// Massage return value if necessary.
			Class<?> returnType = method.getReturnType();
			if (retVal != null && retVal == target && returnType.isInstance(proxy) &&
					!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
				// Special case: it returned "this" and the return type of the method
				// is type-compatible. Note that we can't help if the target sets
				// a reference to itself in another returned object.
				retVal = proxy;
			}
			else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
				throw new AopInvocationException(
						"Null return value from advice does not match primitive return type for: " + method);
			}
			return retVal;
		}
		finally {
			if (target != null && !targetSource.isStatic()) {
				// Must have come from TargetSource.
				targetSource.releaseTarget(target);
			}
			if (setProxyContext) {
				// Restore old proxy.
				AopContext.setCurrentProxy(oldProxy);
			}
		}
	}

在这里插入图片描述
在这里插入图片描述
主要关注这两段代码,发现如果expose-proxy属性值为true的话会调用
oldProxy = AopContext.setCurrentProxy(proxy);
那么看一下这个方法
在这里插入图片描述
在这里插入图片描述
通过英文注释应该就大概明白了,这里是通过一个ThreadLocal来保存代理的,在每次调用代理的时候会判断一下exposeProxy是否为true,如果是的话,就通过ThreadLocal保存代理,可通过以下方法获取到代理。
在这里插入图片描述
然后invoke方法的finally块里又把oldProxy放回了ThreadLocal中,这里要注意一下,就是每次调用都会set一下当前的代理,最后再把之前保存的放回去。

那么之前在问题中,a中调用b的时候通过从ThreadLocal中获取到当前代理再执行b方法就可以达到b中也有事务的要求了。简单来说,处理的方法就是设置expose-proxy属性为true暴露代理。

<think>好的,我现在需要帮用户寻找Spring Boot项目的集群化部署方案。首先,用户已经提供了一些相关的引用内容,我需要仔细看看这些引用,看看有没有提到集群化部署的内容。 引用1提到Spring Boot的starter机制,但主要是关于配置和依赖管理的,可能和集群化部署关系不大。引用2和3讲的是项目创建和CI/CD流程,可能涉及部署流程自动化,但还不是直接的集群方案。引用4是关于使用Kubernetes部署Spring Boot应用,这应该和集群有关,因为Kubernetes是容器编排工具,常用于管理集群。引用5提到了自动化部署脚本和Tomcat配置,可能涉及到传统方式的部署,但用户问的是集群化,所以可能需要结合容器化方案。 接下来,我需要整理集群化部署的常见方案。一般来说,集群化部署需要考虑负载均衡、服务发现、配置同步、会话管理等问题。常见的方案可能包括: 1. 容器化部署结合Kubernetes,如引用4提到的Docker和K8s,这应该是主流方案。 2. 使用服务注册发现组件,比如Eureka、Consul等,实现服务自动注册和发现。 3. 配置中心,如Spring Cloud Config,保证集群中各节点的配置一致。 4. 状态管理,比如使用Redis来存储会话,实现无状态服务,方便水平扩展。 5. 传统的反向代理负载均衡,比如Nginx或HAProxy,将流量分发到多个Spring Boot实例。 然后,根据用户提供的引用,特别是引用4中的Kubernetes部署,可能需要详细说明如何利用K8s进行集群部署。另外,引用5中的Tomcat部署可能适用于传统的服务器集群,但可能不够现代化。 还需要考虑是否需要结合CI/CD流程,如引用3提到的,自动化构建和部署到集群环境中。此外,配置管理方面,引用1提到的starter和默认配置可能需要在集群中保持一致,可能需要配置中心的支持。 用户问的是最佳实践,所以可能需要综合容器化、Kubernetes、服务发现、配置中心、无状态设计等要素。同时,需要确保每个步骤有依据,比如引用4的Dockerfile示例,引用3的CI/CD流程,引用5的部署脚本等。 可能还需要提到监控和日志聚合,比如使用Prometheus和ELK栈,但用户提供的引用中没有提到这些,不过作为最佳实践,可能需要简要提及。 最后,生成相关问题时要围绕集群部署的扩展,比如如何实现服务发现、配置管理、监控等。</think>### Spring Boot项目集群化部署最佳实践方案 Spring Boot项目的集群化部署需要结合容器化技术、负载均衡和服务发现机制,以下是分步实施方案: #### 一、容器化封装 1. **创建Docker镜像** 使用Dockerfile将应用封装为独立容器,参考引用4的示例: ```dockerfile FROM openjdk:11-jre-slim COPY target/my-spring-boot-app.jar /app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "/app.jar"] ``` 通过CI/CD工具(如Jenkins)自动构建镜像并推送到镜像仓库[^3]。 #### 二、Kubernetes集群编排(主流方案) 1. **部署到K8s集群** 使用Kubernetes的Deployment定义Pod副本数量,实现自动扩缩容: ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: springboot-app spec: replicas: 3 # 集群节点数量 template: spec: containers: - name: app image: registry.example.com/my-spring-boot-app:latest ports: - containerPort: 8080 ``` 2. **服务暴露负载均衡** 通过Service组件实现内部负载均衡: ```yaml apiVersion: v1 kind: Service metadata: name: springboot-service spec: selector: app: springboot-app ports: - protocol: TCP port: 80 targetPort: 8080 type: LoadBalancer # 云平台自动创建外部负载均衡器 ``` 此方案可支撑千级节点规模,适合高并发场景[^4]。 #### 三、传统服务器集群方案 1. **反向代理配置** 使用Nginx实现流量分发(适合中小规模): ```nginx upstream springboot_cluster { server 192.168.1.101:8080; server 192.168.1.102:8080; server 192.168.1.103:8080; } server { listen 80; location / { proxy_pass http://springboot_cluster; } } ``` 需配合自动化部署脚本同步多节点(参考引用5的deploy.sh)[^5]。 #### 四、关键配套组件 1. **配置中心** 使用Spring Cloud Config或Nacos实现集群配置统一管理,避免单点配置修改[^1]。 2. **会话共享** 采用Redis存储会话数据: ```properties spring.session.store-type=redis spring.redis.host=redis-cluster.example.com ``` 3. **服务发现** 集成Eureka或Consul实现节点自动注册: ```java @EnableEurekaClient public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` #### 五、监控保障体系 1. 集成Prometheus收集容器指标 2. 通过ELK栈实现日志聚合分析 3. 配置K8s HPA自动伸缩策略
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值