springboot中如何注册servlet和filter
法1
通过@web*开头的注解
@WebServlet
public class HalloServlet extends HttpServlet{}
---
@WebFilter("/hello/*")
public class HelloFilter implements Filter {}
---
//启动类中需要通过@ServletComponentScan扫描这些注解
@SpringBootApplication
@ServletComponentScan
public class SpringBootServletApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootServletApplication.class, args);
}
}
法2
通过RegistrationBean
@Bean
public ServletRegistrationBean helloServlet() {
ServletRegistrationBean helloServlet = new ServletRegistrationBean();
helloServlet.addUrlMappings("/hello");
helloServlet.setServlet(new HelloServlet());
return helloServlet;
}
@Bean
public FilterRegistrationBean helloFilter() {
FilterRegistrationBean helloFilter = new FilterRegistrationBean();
helloFilter.addUrlPatterns("/hello/*");
helloFilter.setFilter(new HelloFilter());
return helloWorldFilter;
}
springboot如何加载DispatcherServlet?
我们只讨论springboot环境下使用内嵌tomcat容器的情况。
springboot版本 2.0.2.RELEASE
DispatcherServlet初始化的位置
// DispatcherServletAutoConfiguration.class
/*
* The bean name for a DispatcherServlet that will be mapped to the root URL "/"
*/
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(
this.webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(
this.webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(
this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
return dispatcherServlet;
}
同时也需要关注下DispatcherServlet的RigistrationBean
// DispatcherServletAutoConfiguration.class
/*
* The bean name for a DispatcherServlet that will be mapped to the root URL "/"
*/
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
/*
* The bean name for a ServletRegistrationBean for the DispatcherServlet "/"
*/
public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public ServletRegistrationBean<DispatcherServlet> dispatcherServletRegistration(
DispatcherServlet dispatcherServlet) {
ServletRegistrationBean<DispatcherServlet> registration = new ServletRegistrationBean<>(
dispatcherServlet,
this.serverProperties.getServlet().getServletMapping());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(
this.webMvcProperties.getServlet().getLoadOnStartup());
if (this.multipartConfig != null) {
registration.setMultipartConfig(this.multipartConfig);
}
return registration;
}
可以看出,ServletRegistrationBean中的servlet属性即为上面创建的DispatcherServlet,现在 RegistrationBean准备好了,那么是在什么地方被使用的呢?
DispatcherServlet的加载流程
TomcatStarter
首先在tomcatstarter中 会有整体对ServletContextInitializer的onStartup方法的调用。
// TomcatStarter.class
@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext)
throws ServletException {
try {
for (ServletContextInitializer initializer : this.initializers) {
initializer.onStartup(servletContext);
}
}
catch (Exception ex) {
this.startUpException = ex;
// Prevent Tomcat from logging and re-throwing when we know we can
// deal with it in the main thread, but log for information here.
if (logger.isErrorEnabled()) {
logger.error("Error starting Tomcat context. Exception: "
+ ex.getClass().getName() + ". Message: " + ex.getMessage());
}
}
}
这里的initializers 主要有这三个 通过断点信息,并没有在其中发现RegisterBean的相关信息
为了真正理解springboot如何加载servlet,还得研究下ServletWebServerApplicationContext,来看看ServletWebServerApplicationContext的逻辑流程:
//ServletWebServerApplicationContext.class
// 首先是onRefresh方法
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
// onRefresh方法中 调用了createWebServer()
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = getWebServerFactory();
//下一层的入口
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
initPropertySources();
}
// 然后是下一层的入口getSelfInitializer(),我们在TomcatStarter中看到的其实就是这里返回的匿名类
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(
beanFactory);
WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,
getServletContext());
existingScopes.restore();
WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,
getServletContext());
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
// 在selfInitialize()中,转而调用getServletContextInitializerBeans()
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
return new ServletContextInitializerBeans(getBeanFactory());
}
现在流程转到了ServletContextInitializerBeans中,那么ServletContextInitializerBeans做了什么呢
//ServletContextInitializerBeans.class
// 在构造方法中,调用了addServletContextInitializerBeans()方法,看起来离我们的目标不远了
public ServletContextInitializerBeans(ListableBeanFactory beanFactory) {
this.initializers = new LinkedMultiValueMap<>();
addServletContextInitializerBeans(beanFactory);
addAdaptableBeans(beanFactory);
List<ServletContextInitializer> sortedInitializers = new ArrayList<>();
this.initializers.values().forEach((contextInitializers) -> {
AnnotationAwareOrderComparator.sort(contextInitializers);
sortedInitializers.addAll(contextInitializers);
});
this.sortedList = Collections.unmodifiableList(sortedInitializers);
}
//addServletContextInitializerBeans()方法会去容器中寻找注册过的ServletContextInitializer
private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
for (Entry<String, ServletContextInitializer> initializerBean : getOrderedBeansOfType(
beanFactory, ServletContextInitializer.class)) {
addServletContextInitializerBean(initializerBean.getKey(),
initializerBean.getValue(), beanFactory);
}
}
将断点打到addServletContextInitializerBeans()方法中,可以看到getOrderedBeansOfType(beanFactory, ServletContextInitializer.class)获取到的就是我们在最开始注册的dispatcherServletRegistration到此,整个流程就串起来了。
- 首先,以ServletWebServerApplicationContext中的onRefresh()方法为起点触发配置了一个匿名的ServletContextInitializer。
- 这个匿名的ServletContextInitializer的onStart方法会去容器中找到所有的RegisterBean并加载到ServletContext中,ServletContainer 其实就是 servlet filter listener 的汇总。
- 这个匿名的ServletContextInitializer最终传递给TomcatStarter,由TomcatStarter的onStartup方法触发ServletContextInitializer 的 onStartup 方法,完成装配。
后记
是哪里调用的TomcatStarter的onStart方法呢
在TomcatServletWebServerFactory中可以看到TomcatStart是被new出来的
// TomcatServletWebServerFactory.class
protected void configureContext(Context context,
ServletContextInitializer[] initializers) {
TomcatStarter starter = new TomcatStarter(initializers);
if (context instanceof TomcatEmbeddedContext) {
// Should be true
((TomcatEmbeddedContext) context).setStarter(starter);
}
context.addServletContainerInitializer(starter, NO_CLASSES);
......
}
那么 TomcatServletWebServerFactory 又是如何被创建的呢,经过寻找,是在ServletWebServerFactoryConfiguration中
//ServletWebServerFactoryConfiguration.class
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
}
满足上面的条件,就会触发springboot的自动配置。
后后记
ServletRegistrationBean的onStract方法干了什么呢?
可以看到ServletRegistrationBean是继承自RegistartionBean的,在RegistartionBean的onStart方法中,调用了register方法。
// RegistartionBean.class
@Override
public final void onStartup(ServletContext servletContext) throws ServletException {
String description = getDescription();
if (!isEnabled()) {
logger.info(StringUtils.capitalize(description)
+ " was not registered (disabled)");
return;
}
register(description, servletContext);
}
register方法被DynamicRegistrationBean实现,在register方法中,转而调用了addRegistration方法。
// DynamicRegistrationBean.class
@Override
protected final void register(String description, ServletContext servletContext) {
D registration = addRegistration(description, servletContext);
if (registration == null) {
logger.info(StringUtils.capitalize(description) + " was not registered "
+ "(possibly already registered?)");
return;
}
configure(registration);
}
该方法最终被ServletRegistrationBean实现。
//ServletRegistrationBean.class
protected ServletRegistration.Dynamic addRegistration(String description,
ServletContext servletContext) {
String name = getServletName();
logger.info("Servlet " + name + " mapped to " + this.urlMappings);
return servletContext.addServlet(name, this.servlet);
}
可以看到 ,最终其实就是将servlet放入servletContext中。