Java - Spring中HttpServletResponse的注入原理

前言

我们在每次接口调用的时候,其实都可以通过注入一个HttpServletResponse或者HttpServletRequest对象实例来获得当前的请求/返回。本文主要来讲解Spring中是如何将这两个对象注入并且获取的。主要围绕着HttpServletResponse来讲,其中也会穿插HttpServletRequest的细节部分。

一. HttpServletResponse的自动注入

首先我们准备一个非常基础的Controller代码:

@RestController
public class MyController {
    @Autowired
    private HttpServletResponse servletResponse;

    @PostMapping("/hello")
    public Student hello(@Validated @RequestBody Student student) {
        System.out.println(servletResponse.getHeaderNames());
        return student;
    }
}

最终程序启动起来,当加载MyController 这个Bean的时候,肯定是需要经过三个步骤的:(详细的可以看Spring源码系列:Bean的加载这篇文章)

  1. 实例化阶段。
  2. 属性注入。
  3. 初始化阶段。

那么我们在Controller中注入的HttpServletResponse对象实例肯定是阶段二来完成的。Spring容器中Bean的属性注入入口在于AbstractAutowireCapableBeanFactory.doCreateBean()中的下面这段代码。

populateBean(beanName, mbd, instanceWrapper);

根据调用链关系:

在这里插入图片描述
我们最终定位到DefaultListableBeanFactory类中。

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
		implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
	protected Map<String, Object> findAutowireCandidates(
			@Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {

		String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
				this, requiredType, true, descriptor.isEager());
		Map<String, Object> result = new LinkedHashMap<>(candidateNames.length);
		for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {
			Class<?> autowiringType = classObjectEntry.getKey();
			if (autowiringType.isAssignableFrom(requiredType)) {
			    // 根据依赖注入源找到对应类型的Bean
				Object autowiringValue = classObjectEntry.getValue();
				// 为它创建一个代理对象,如果有必要的话
				autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
				if (requiredType.isInstance(autowiringValue)) {
					result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
					break;
				}
			}
		}
		// ...
		return result;
	}
}

由于我们项目中注入的是HttpServletResponse对象,因此我们找到我们想要的:
在这里插入图片描述
也就是说,我们注入的HttpServletResponse它的值,就是源码中的autowiringValue对象(当然后面可能被包装),也就是来自于这行代码:

Object autowiringValue = classObjectEntry.getValue();

同时我们注意到autowiringValue的对象实例类型是ResponseObjectFactory,它是WebApplicationContextUtils下的一个静态内部类,我们来看下:

public abstract class WebApplicationContextUtils {
	// 获取ServletRequest的
	private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {
		@Override
		public ServletRequest getObject() {
			return currentRequestAttributes().getRequest();
		}

		@Override
		public String toString() {
			return "Current HttpServletRequest";
		}
	}
	// 获取ServletResponse的
	private static class ResponseObjectFactory implements ObjectFactory<ServletResponse>, Serializable {
		@Override
		public ServletResponse getObject() {
			ServletResponse response = currentRequestAttributes().getResponse();
			if (response == null) {
				throw new IllegalStateException("Current servlet response not available - " +
						"consider using RequestContextFilter instead of RequestContextListener");
			}
			return response;
		}

		@Overrid
		public String toString() {
			return "Current HttpServletResponse";
		}
	}
	↓↓↓↓↓↓
	private static ServletRequestAttributes currentRequestAttributes() {
		RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes();
		if (!(requestAttr instanceof ServletRequestAttributes)) {
			throw new IllegalStateException("Current request is not a servlet request");
		}
		return (ServletRequestAttributes) requestAttr;
	}
}

那么根据源码,我们继续跟踪,可见最终注入的对象来源于RequestContextHolder。无论是Request还是Response。都跟他有关。

RequestContextHolder.currentRequestAttributes()

1.1 用ThreadLocal保存当前的请求和返回

我们来看下RequestContextHolder的一个大致结构:

public abstract class RequestContextHolder  {
	private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
		new NamedThreadLocal<>("Request attributes");

	private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
		new NamedInheritableThreadLocal<>("Request context");

	public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
		RequestAttributes attributes = getRequestAttributes();
		if (attributes == null) {
			if (jsfPresent) {
				attributes = FacesRequestAttributesFactory.getFacesRequestAttributes();
			}
			if (attributes == null) {
				// throw
			}
		}
		return attributes;
	}
	↓↓↓↓↓↓
	@Nullable
	public static RequestAttributes getRequestAttributes() {
		RequestAttributes attributes = requestAttributesHolder.get();
		if (attributes == null) {
			attributes = inheritableRequestAttributesHolder.get();
		}
		return attributes;
	}
}

我们从源码中可以看出两点:

  1. 我们每次请求的ServletRequestServletResponse是放在了ThreadLocal当中的。
  2. ThreadLocal里面指定的泛型则是RequestAttributes

因为RequestAttributes是一个接口,因此我们需要看他的对应实现子类,但是呢,乍一看其子类还挺多的,那到底是哪个?
在这里插入图片描述

我们结合上面的代码,关于ServletResponse的获取:

ServletResponse response = currentRequestAttributes().getResponse();
↓↓↓↓↓↓
public class ServletRequestAttributes extends AbstractRequestAttributes {
	private final HttpServletRequest request;
	@Nullable
	private HttpServletResponse response;
	@Nullable
	private volatile HttpSession session;
	@Nullable
	public final HttpServletResponse getResponse() {
		return this.response;
	}
	public final HttpServletRequest getRequest() {
		return this.request;
	}
}

那么在这一小节我们就可以得出结论:

  1. 和当次调用有关的请求和返回对象,都保存在ServletRequestAttributes这个实例当中。
  2. ServletRequestAttributes则又存储于ThreadLocal,和当前线程绑定。

只不过细心的同学就会发现这么几个问题:

  1. 首先,我们调用10次请求,就应该对应着10个RequestResponse那么Spring也就应该做到能够实时的获取当前请求对应的实例。 那么是如何实现这样的动态加载的呢?
  2. 我们从源码发现,还有一段代码没有讲解。也就是创建代理对象的步骤。

我们一个个来解决。

1.2 创建请求/返回的代理对象

我们回到代码中:

for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {
	Class<?> autowiringType = classObjectEntry.getKey();
	if (autowiringType.isAssignableFrom(requiredType)) {
		// 从ThreadLocal中拿到我们的ResponseObjectFactory 或者RequestObjectFactory 
		Object autowiringValue = classObjectEntry.getValue();
		// 完成一个代理
		autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
		// ...
	}
}

第一个部分,我们在1.1小节有已经讲了,这里就说代理部分:

abstract class AutowireUtils {
	public static Object resolveAutowiringValue(Object autowiringValue, Class<?> requiredType) {
		if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) {
			// ResponseObjectFactory 
			ObjectFactory<?> factory = (ObjectFactory<?>) autowiringValue;
			// 此时requiredType的类型是interface javax.servlet.http.HttpServletResponse
			if (autowiringValue instanceof Serializable && requiredType.isInterface()) {
				// 通过JDK动态代理拿到我们的一个代理对象
				autowiringValue = Proxy.newProxyInstance(requiredType.getClassLoader(),
						new Class<?>[] {requiredType}, new ObjectFactoryDelegatingInvocationHandler(factory));
			}
			else {
				return factory.getObject();
			}
		}
		return autowiringValue;
	}
}

这里主要就是通过JDK动态代理来创建出一个代理对象,同样的,既然是动态代理,我们就应该将注意力转移到代理的内容上,我们看传入的ObjectFactoryDelegatingInvocationHandler它做了什么事情:(Java - JDK动态代理原理

private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable {
	// 被代理对象
	private final ObjectFactory<?> objectFactory;

	public ObjectFactoryDelegatingInvocationHandler(ObjectFactory<?> objectFactory) {
		this.objectFactory = objectFactory;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		String methodName = method.getName();
		if (methodName.equals("equals")) {
			// Only consider equal when proxies are identical.
			return (proxy == args[0]);
		}
		else if (methodName.equals("hashCode")) {
			// Use hashCode of proxy.
			return System.identityHashCode(proxy);
		}
		else if (methodName.equals("toString")) {
			return this.objectFactory.toString();
		}
		try {
			return method.invoke(this.objectFactory.getObject(), args);
		}
		catch (InvocationTargetException ex) {
			throw ex.getTargetException();
		}
	}
}

也就是说,当我们拿到HttpServletResponse / HttpServletRequest对象实例的时候,当调用相关的方法时,实际上是代理对象通过反射来调用原始函数的。只不过代理对象对equals、hashCode、toString这三个方法做了一点特殊处理罢了。

那么就到了最后一个问题:Spring如何做到动态加载当前的Request/Response

1.3 请求和返回的动态加载

这个问题,我们可以从两个角度来思考:

  1. 创建ResponseRequest的对应工厂是如何注入的?
  2. Spring是如何动态改变Request的?

1.3.1 RequestObjectFactory的注入

我们以RequestObjectFactory为例,还记得上面的源码吗?

private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {
}

RequestObjectFactory实现了ObjectFactory接口。这里再来复习一下ObjectFactory是干啥用的:

  1. 简而言之就是一个对象工厂,用来创建Bean对象。
  2. 实现了这个接口的Bean,会通过对应的getObject工厂方法来获得对象。

那么RequestObjectFactory实现这个接口,有什么目的?好巧不巧,我在Spring源码系列 - ApplicationContext容器的功能扩展这篇文章有一个章节就是讲关于容器启动阶段的后处理功能的。

我们这里拿到里面的入口函数AbstractApplicationContext.refresh来开始分析:

public abstract class AbstractApplicationContext extends DefaultResourceLoader
		implements ConfigurableApplicationContext {
	@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// ...
			try {
				postProcessBeanFactory(beanFactory);
				// ...
			}
			// ...
		}
	}
	protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
	}
}

postProcessBeanFactory这个函数是一个抽象类模板,具体实现则在于子类,Spring中有这么一个AbstractApplicationContext的子类AnnotationConfigReactiveWebServerApplicationContext就有对应的实现:

@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
	super.postProcessBeanFactory(beanFactory);
	// ...
}
↓↓↓↓↓
public class ServletWebServerApplicationContext extends GenericWebApplicationContext
		implements ConfigurableWebServerApplicationContext {
	@Override
	protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		beanFactory.addBeanPostProcessor(new WebApplicationContextServletContextAwareProcessor(this));
		beanFactory.ignoreDependencyInterface(ServletContextAware.class);
		registerWebApplicationScopes();
	}
	↓↓↓↓↓
	private void registerWebApplicationScopes() {
		ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(getBeanFactory());
		// 调用WebApplicationContextUtils的注册
		WebApplicationContextUtils.registerWebApplicationScopes(getBeanFactory());
		existingScopes.restore();
	}
}

到这里,仿佛一切又关联上了。

  • 我们的ServletRequestResponse就和WebApplicationContextUtils息息相关。
  • Spring在容器启动阶段的后处理过程中,又会调用到WebApplicationContextUtils中的registerWebApplicationScopes函数。

我们来看下做了什么:

// WebApplicationContextUtils
public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory) {
	registerWebApplicationScopes(beanFactory, null);
}

public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory,
			@Nullable ServletContext sc) {

	// ...
	// 添加依赖注入的来源Bean
	beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory());
	beanFactory.registerResolvableDependency(ServletResponse.class, new ResponseObjectFactory());
	beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory());
	beanFactory.registerResolvableDependency(WebRequest.class, new WebRequestObjectFactory());
	// ...
}

可见,就是注册相关的依赖,类型有:ServletRequest、ServletResponse、HttpSession、WebRequest,这里的beanFactory类型是DefaultListableBeanFactory

// DefaultListableBeanFactory
@Override
public void registerResolvableDependency(Class<?> dependencyType, @Nullable Object autowiredValue) {
	Assert.notNull(dependencyType, "Dependency type must not be null");
	if (autowiredValue != null) {
		if (!(autowiredValue instanceof ObjectFactory || dependencyType.isInstance(autowiredValue))) {
			throw new IllegalArgumentException("Value [" + autowiredValue +
					"] does not implement specified dependency type [" + dependencyType.getName() + "]");
		}
		this.resolvableDependencies.put(dependencyType, autowiredValue);
	}
}

简单点,就是将实现了ObjectFactory接口的对象添加到依赖注入源中。

1.3.2 RequestAttributes中请求和返回的赋值

Spring中有这么一个过滤器:RequestContextFilter,它的代码如下:

public class RequestContextFilter extends OncePerRequestFilter {
	@Override
	protected void doFilterInternal(
			HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		// 创建一个ServletRequestAttributes类型的实例
		ServletRequestAttributes attributes = new ServletRequestAttributes(request, response);
		// 初始化RequestContextHolder,这里就将ServletRequestAttributes塞到ThreadLocal里面了
		initContextHolders(request, attributes);

		try {
			// 继续执行后续逻辑
			filterChain.doFilter(request, response);
		}
		finally {
			resetContextHolders();
			if (logger.isTraceEnabled()) {
				logger.trace("Cleared thread-bound request context: " + request);
			}
			attributes.requestCompleted();
		}
	}

	private void initContextHolders(HttpServletRequest request, ServletRequestAttributes requestAttributes) {
		LocaleContextHolder.setLocale(request.getLocale(), this.threadContextInheritable);
		RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
		if (logger.isTraceEnabled()) {
			logger.trace("Bound request context to thread: " + request);
		}
	}
}

因此我们每次请求的时候,都会被这个过滤器捕获到,然后过滤器就会把当前的HttpRequestResponse都塞到当前线程的ThreadLocal中。 至此,就完成了一个闭环。

二. 总结

首先是ThreadLocal的线程绑定问题:

  1. 我们每次请求,都会被拦截器RequestContextFilter拦截。主要就是将当前的HttpServletResponseHttpServletRequest存储在ServletRequestAttributes中。并初始化RequestContextHolder
  2. 初始化操作就是将ServletRequestAttributes则存储在ThreadLocal中。这样就保证请求和当前线程的绑定。

接下来就是注入的问题:

  1. Spring容器在启动的时候,有个后处理过程会将ServletRequest、ServletResponse、HttpSession、WebRequest注册到依赖源中。完成了RequestObjectFactoryResponseObjectFactory的注入。
  2. RequestObjectFactory又实现了ObjectFactory接口,因此获取实例的时候,调用的是对应的getObject函数。
  3. 那么每次调用接口的时候,通过getObject函数,拿到当前的ServletRequestAttributes。然后通过JDK动态代理,将RequestResponse的代理对象注入到对应的类中。
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,下面是一个更具体的Java-Spring Boot集成银联充值的案例。 首先,您需要添加银联支付SDK的依赖项到您的项目。您可以在Maven或Gradle添加以下依赖项: ```xml <dependency> <groupId>com.unionpay</groupId> <artifactId>acp-sdk</artifactId> <version>1.0.0</version> </dependency> ``` 接下来,您需要创建一个Controller类来处理充值请求和回调通知: ```java @RestController @RequestMapping("/payment") public class PaymentController { @Autowired private UnionPayService unionPayService; @PostMapping("/recharge") public String recharge(@RequestParam("orderId") String orderId, @RequestParam("amount") String amount) { // 构建支付请求参数 Map<String, String> requestData = new HashMap<>(); requestData.put("orderId", orderId); requestData.put("txnAmt", amount); // 其他参数... // 发送支付请求 Map<String, String> responseData = unionPayService.createPayment(requestData); // 处理支付结果 String respCode = responseData.get("respCode"); if ("00".equals(respCode)) { // 支付成功逻辑 return "Payment success"; } else { // 支付失败逻辑 return "Payment failed"; } } @PostMapping("/notify") public void notifyCallback(HttpServletRequest request, HttpServletResponse response) throws IOException { // 解析回调通知参数 Map<String, String> responseData = unionPayService.handleNotifyCallback(request.getParameterMap()); // 处理回调通知结果 String respCode = responseData.get("respCode"); if ("00".equals(respCode)) { // 回调处理成功逻辑 response.getWriter().write("success"); } else { // 回调处理失败逻辑 response.getWriter().write("fail"); } } } ``` 在上述代码,使用`@Autowired`注解将`UnionPayService`注入到`PaymentController`。`UnionPayService`是一个自定义的服务类,用于处理银联支付相关的操作。 在`UnionPayService`,您可以实现以下方法: ```java @Service public class UnionPayService { // 银联支付请求URL private static final String PAYMENT_URL = "https://xxxxx"; // 银联支付回调通知URL private static final String NOTIFY_URL = "https://xxxxx"; public Map<String, String> createPayment(Map<String, String> requestData) { // 构建支付请求参数 // 发送支付请求 // 处理支付结果 return responseData; } public Map<String, String> handleNotifyCallback(Map<String, String[]> requestParams) { // 解析回调通知参数 // 处理回调通知结果 return responseData; } } ``` 在`createPayment`方法,您需要构建支付请求参数,并发送支付请求到银联支付URL。然后,根据支付结果进行逻辑处理,并返回响应数据。 在`handleNotifyCallback`方法,您需要解析回调通知参数,并根据结果进行逻辑处理,并返回响应数据。 最后,您需要在启动类(例如`Application.java`)上添加`@EnableWebMvc`注解,以确保Spring Boot应用程序可以处理HTTP请求。 这个案例只是一个简单的示例,实际的集成可能需要更多的逻辑和安全性处理。希望这个例子对您有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zong_0915

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值