我的 Spring 启动应用程序需要一个简单的 API 代理,它根据来自数据库的值创建代理映射。我发现这个API代理 https://github.com/mitre/HTTP-Proxy-Servlet 它很简单,可以达到我的目的
我发现创建动态代理 servlet 的唯一方法如下。
@ManagedBean
public final class ExecutorListener implements ServletContextInitializer {
private static final String TARGET_URI = "targetUri";
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
WebApplicationContext springContext = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
EndpointsRepo endpointrepo = springContext.autowireBean(EndpointsRepo.class);
List<Endpoint> endpoints = endpointrepo.findAll();
for(Endpoint endpoint: endpoints){
ServletRegistration.Dynamic dispatcher = servletContext.addServlet(endpoint.getEndpointCode(), new ProxyServlet());
dispatcher.setLoadOnStartup(1);
dispatcher.setInitParameter(TARGET_URI, endpoint.getEndpointUrl());
dispatcher.addMapping(endpoint.getUrlPattern());
}
}
代理现在按预期工作正常,除非我更新数据库中的映射值,我需要将它们反映在动态代理中。
有没有办法在不重新启动 Spring 引导应用程序的情况下刷新 servlet 映射?我尝试使用 applicationcontext.refresh(),但它是由数据库数据源给出一条无法关闭并导致内存泄漏的消息引起的。
如果我尝试使用 SpringServletTRegistration 在运行时添加 bean,则创建的映射不起作用。 似乎只有当我在启动之前配置 servlet 时才起作用,正如 Spring servlet 文档所说。
在初始化阶段之后,您不能添加额外的 servlet,因为这是 Servlet API 规范所禁止的(参见 Java Servlet 规范,版本 4.0)。在第 4.4 节中,在谈论ServletContext#addServlet
方法时,它说:
这些方法只能在应用程序初始化期间调用 从
ServletContextListener
的contexInitialized
方法 实现或从onStartup
方法的ServletContainerInitializer
实施。
您仍然可以重用ProxyServlet
类,但您需要编写自己的servlet(映射到/*
),这将创建许多ProxyServlet
实例,配置它们并将请求路由到正确的实例。
编辑:基本上你需要这样的类:
public class MetaProxyServlet extends HttpServlet {
/**
* To configure a {@link ProxyServlet} without modifying its code.
*/
private static class ProxyServletConfig implements ServletConfig {
private static final String P_TARGET_URI = "targetUri";
private final String targetUri;
@Override
public String getInitParameter(String name) {
return P_TARGET_URI.equals(name) ? targetUri : null;
}
// Other methods
}
/**
* Serves to modify the pathInfo of the request.
*/
private static class RewrittenServletRequest extends HttpServletRequestWrapper {
private final String pathInfo;
@Override
public String getPathInfo() {
return pathInfo;
}
// Other methods
}
private TreeMap<String, ProxyServlet> proxyServlets = new TreeMap<>();
/**
* Adds a reverse proxy between {@code pathPrefix} of this servlets namespace
* and {@code targetUri}.
*
* @param pathPrefix
* must start with '/'
* @param targetUri
* URI to be proxied
* @throws ServletException
*/
public void addRedirect(String pathPrefix, String targetUri) throws ServletException {
final ProxyServlet servlet = new ProxyServlet();
servlet.init(new ProxyServletConfig(pathPrefix, getServletContext(), targetUri));
proxyServlets.put(pathPrefix, servlet);
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String pathInfo = req.getPathInfo();
Entry<String, ProxyServlet> entry = proxyServlets.floorEntry(pathInfo);
// entry.getKey() precedes, but not necessarily is a prefix for pathInfo
if (entry != null && pathInfo.startsWith(entry.getKey())) {
String rewrittenPathInfo = pathInfo.substring(entry.getKey().length());
entry.getValue()//
.service(new RewrittenServletRequest(req, rewrittenPathInfo), resp);
} else {
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
}
完整的代码在要点上。