Java Web プロジェクトの開発では、HttpServletRequest を使用してリクエスト パラメータやリクエスト ヘッダーなどの情報を取得することがよくあります。Spring プロジェクトでは、通常、Spring が提供するアノテーションを使用して @RequestParam、@RequestHeader などのパラメーターを取得します。
ただし、シナリオによっては、リクエスト IP の取得、リクエスト ドメイン名の取得など、HttpServletRequest オブジェクトからさらに多くの機能を取得する必要がある場合があります。この記事では、Spring MVC 環境で HttpServletRequest を取得する方法とその実装方法を学び、その理由を理解します。
コントローラーメソッドのパラメーター
注釈付きの Spring MVC コントローラー メソッドは、リクエストを処理するハンドラーとして使用できます。リクエスト オブジェクトを取得したい場合は、メソッドに ServletRequest または HttpServletRequest タイプのパラメーターを追加するだけです。コードは以下のように表示されます
@RestController
public class UserController {
@GetMapping("/getUser")
public String getUser(HttpServletRequest request) {
return "request ip is : " + request.getRemoteHost();
}
}
拡張: レスポンスを取得する方法は、同じ例のメソッドに ServletResponse または HttpServletResponse タイプのパラメーターを追加するだけです。
コントローラメソッドパラメータの実装原理
上記のコードを通じてこれを簡単に実現できますが、Spring はそれを実現するのにどのように役立つでしょうか?
- Spring mvc では、ブラウザーから開始されたすべてのリクエストは、処理のために DispatcherServlet に渡されます。
- DispatcherServlet は、HandlerMapping を使用して、ユーザーまたはデフォルトの構成に基づいてリクエストを処理できるハンドラーを見つけます。
- DispatcherServlet は、HandlerMapping によって返されたハンドラー チェーン HandlerExecutionChain を取得します。プロセッサ チェーン全体はインターセプタとハンドラで構成されます。
- DispatcherServlet はハンドラーを HandlerAdapter として適応させます。
- DispatcherServlet はリクエストの前処理にインターセプターを使用します。
- DispatcherServlet はリクエスト処理にハンドラーを使用します。
- DispatcherServlet はリクエストの後処理にインターセプターを使用します。
- DispatcherServlet は、インターセプターまたはハンドラーからモデルとビュー ModelAndView を抽出します。
- DispatcherServlet は ViewResolver を使用して、ビューからビューを解決します。
- DispatcherServlet はビューをレンダリングし、リクエストに応じてブラウザに返します。
上記のステップ 6 [DispatcherServlet はリクエスト処理にプロセッサを使用します] では、独自のコントローラー メソッドを呼び出す前に、Spring が
HandlerMethodArgumentResolver を通じて対応するパラメーターをコントローラー メソッドに挿入します。
静的メソッド
Spring では、コントローラー メソッド パラメーターを介して HttpServletRequest オブジェクトを取得するだけでなく、Spring が提供するツール クラスの静的メソッドを介して HttpServletRequest を取得することもできます。例としては以下のようなものがあります。
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
静的メソッドの実装原理
上記の例では、RequestContextHolder はリクエスト コンテキスト ホルダーを表し、各リクエスト コンテキスト情報を内部的に ThreadLocal に格納します。
public abstract class RequestContextHolder {
/**
* 线程上下文 RequestAttributes
*/
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");
/**
* 支持继承的线程上下文 RequestAttributes
*/
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<>("Request context");
}
DispatcherServlet は、リクエストを処理する前にリクエストを ServletRequestAttributes に保存し、それを RequestContextHolder に置きます。
詳細については、DispatcherServlet の親クラス FrameworkServlet.processRequest() を参照してください。
直接噴射
HttpServletRequest を通常の Bean として注入することも可能です。コードは以下のように表示されます
@RestController
public class UserController {
@Autowired
private HttpServletRequest request;
@GetMapping("/getIP")
public String getIP() {
return "request ip is : " + request.getRemoteHost();
}
}
直接噴射解析
@Autowired を介してリクエストを導入するのも非常に簡単ですが、ここで問題が発生すると思いますか? …
コントローラーはシングルトン Bean オブジェクトではないでしょうか? Spring コンテナーにはインスタンスが 1 つだけあり、各リクエストはリクエスト オブジェクトに対応します。Spring は 1 つのリクエストをどのように使用して複数のリクエストを表すのでしょうか?
慎重に分析した結果、Spring は Bean を注入するときに、基礎となる DefaultListableBeanFactory を使用して Bean インスタンスを取得していることがわかります
。
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
// 不依赖关系
private final Map<Class<?>, Object> resolvableDependencies = new ConcurrentHashMap<>(16);
// 查找候选 bean
protected Map<String, Object> findAutowireCandidates(
@Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {
//部分代码省略
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)) {
Object autowiringValue = classObjectEntry.getValue();
// 解析 ObjectFactory
autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
if (requiredType.isInstance(autowiringValue)) {
result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
break;
}
}
}
//部分代码省略
}
}
DefaultListableBeanFactory が候補 Bean を検索するときは、まず resolvableDependency から検索し、見つかった後、AutowireUtils.resolveAutowiringValue メソッドを呼び出して再度解決します。
resolvableDependency 内のオブジェクトは Spring では特殊な存在であり、Spring が管理する Bean には属さないため、手動で
DefaultListableBeanFactory に登録する必要があります。
私たちはソースコードの追跡を続けます。
abstract class AutowireUtils {
public static Object resolveAutowiringValue(Object autowiringValue, Class<?> requiredType) {
if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) {
// ObjectFactory 类型值和所需类型不匹配,创建代理对象
ObjectFactory<?> factory = (ObjectFactory<?>) autowiringValue;
if (autowiringValue instanceof Serializable && requiredType.isInterface()) {
// 创建代理对象,可用于处理 HttpServletRequest 注入等问题
autowiringValue = Proxy.newProxyInstance(requiredType.getClassLoader(),
new Class<?>[]{requiredType}, new ObjectFactoryDelegatingInvocationHandler(factory));
} else {
return factory.getObject();
}
}
return autowiringValue;
}
}
resolvableDependency 内のオブジェクトが ObjectFactory タイプであり、必要なタイプと一致しない場合、Spring は ObjectFactory を使用して 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 {
try {
return method.invoke(this.objectFactory.getObject(), args);
} catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}
エージェントの実装はシンプルで、必要な型のメソッドが呼び出されるたびに、ObjectFactory で取得したインスタンス オブジェクトの対応するメソッドが呼び出されます。
では、それを HttpServletRequest に関連付けるにはどうすればよいでしょうか?
Spring は起動時に Web 環境に関連する依存オブジェクトを登録します。
public abstract class WebApplicationContextUtils {
public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory,
@Nullable ServletContext sc) {
beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope());
if (sc != null) {
ServletContextScope appScope = new ServletContextScope(sc);
beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope);
// Register as ServletContext attribute, for ContextCleanupListener to detect it.
sc.setAttribute(ServletContextScope.class.getName(), appScope);
}
// ServletRequest 类型对应 ObjectFactory 注册
beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory());
beanFactory.registerResolvableDependency(ServletResponse.class, new ResponseObjectFactory());
beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory());
beanFactory.registerResolvableDependency(WebRequest.class, new WebRequestObjectFactory());
if (jsfPresent) {
FacesDependencyRegistrar.registerFacesDependencies(beanFactory);
}
}
}
Spring が RequestObjectFactory 型を ServletRequest に挿入し、その実装を確認することができます。
public abstract class WebApplicationContextUtils {
private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {
@Override
public ServletRequest getObject() {
return currentRequestAttributes().getRequest();
}
/**
* Return the current RequestAttributes instance as ServletRequestAttributes.
* @see RequestContextHolder#currentRequestAttributes()
*/
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;
}
@Override
public String toString() {
return "Current HttpServletRequest";
}
}
}
ご覧のとおり、先ほど紹介した【静的メソッド】と同じ考え方です。
以上、春のシーンでリクエストを取得する方法を3つご紹介しましたが、理解できましたか?