赞
踩
现在主流的SpringBoot就是基于Servlet的,所以研究Servlet原理还是很有必要的。
文章较长,而且代码是根据线索逐渐深入的,很多代码会直接忽略不看,所以建议不要死盯代码,多看看前后文我写了什么,跟着思路走。最后有总结,没时间可以直接看。
我们看看HttpServlet注释:
/** * Provides an abstract class to be subclassed to create * an HTTP servlet suitable for a Web site. A subclass of * <code>HttpServlet</code> must override at least * one method, usually one of these: * * <ul> * <li> <code>doGet</code>, if the servlet supports HTTP GET requests * <li> <code>doPost</code>, for HTTP POST requests * <li> <code>doPut</code>, for HTTP PUT requests * <li> <code>doDelete</code>, for HTTP DELETE requests * <li> <code>init</code> and <code>destroy</code>, * to manage resources that are held for the life of the servlet * <li> <code>getServletInfo</code>, which the servlet uses to * provide information about itself * </ul> * * <p>There's almost no reason to override the <code>service</code> * method. <code>service</code> handles standard HTTP * requests by dispatching them to the handler methods * for each HTTP request type (the <code>do</code><i>Method</i> * methods listed above). * * <p>Likewise, there's almost no reason to override the * <code>doOptions</code> and <code>doTrace</code> methods. * * <p>Servlets typically run on multithreaded servers, * so be aware that a servlet must handle concurrent * requests and be careful to synchronize access to shared resources. * Shared resources include in-memory data such as * instance or class variables and external objects * such as files, database connections, and network * connections. * See the * <a href="http://java.sun.com/Series/Tutorial/java/threads/multithreaded.html"> * Java Tutorial on Multithreaded Programming</a> for more * information on handling multiple threads in a Java program. */
意思就是说,这是一个用来创建Http站点的类的抽象。一般要至少重写里边的一些方法,如doGet、doPost、doPut、doDelete等方法。
servlet通常运行在多线程服务器上,所以要知道servlet必须处理并发请求,并注意同步访问共享资源。共享资源包括内存中的数据(如实例或类变量)和外部对象(例如文件、数据库连接和网络连接)。
类声明是这样的:
public abstract class HttpServlet extends GenericServlet
这是一个抽象类!
父类GenericServlet源码:
/** * Defines a generic, protocol-independent servlet. To write an HTTP servlet for * use on the Web, extend {@link javax.servlet.http.HttpServlet} instead. * <p> * <code>GenericServlet</code> implements the <code>Servlet</code> and * <code>ServletConfig</code> interfaces. <code>GenericServlet</code> may be * directly extended by a servlet, although it's more common to extend a * protocol-specific subclass such as <code>HttpServlet</code>. * <p> * <code>GenericServlet</code> makes writing servlets easier. It provides simple * versions of the lifecycle methods <code>init</code> and <code>destroy</code> * and of the methods in the <code>ServletConfig</code> interface. * <code>GenericServlet</code> also implements the <code>log</code> method, * declared in the <code>ServletContext</code> interface. * <p> * To write a generic servlet, you need only override the abstract * <code>service</code> method. */ public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable { private static final long serialVersionUID = 1L; private transient ServletConfig config; /** * Does nothing. All of the servlet initialization is done by one of the * <code>init</code> methods. */ public GenericServlet() { // NOOP } /** * Called by the servlet container to indicate to a servlet that the servlet * is being taken out of service. See {@link Servlet#destroy}. */ @Override public void destroy() { // NOOP by default } /** * Returns a <code>String</code> containing the value of the named * initialization parameter, or <code>null</code> if the parameter does not * exist. See {@link ServletConfig#getInitParameter}. * <p> * This method is supplied for convenience. It gets the value of the named * parameter from the servlet's <code>ServletConfig</code> object. * * @param name * a <code>String</code> specifying the name of the * initialization parameter * @return String a <code>String</code> containing the value of the * initialization parameter */ @Override public String getInitParameter(String name) { return getServletConfig().getInitParameter(name); } /** * Returns the names of the servlet's initialization parameters as an * <code>Enumeration</code> of <code>String</code> objects, or an empty * <code>Enumeration</code> if the servlet has no initialization parameters. * See {@link ServletConfig#getInitParameterNames}. * <p> * This method is supplied for convenience. It gets the parameter names from * the servlet's <code>ServletConfig</code> object. * * @return Enumeration an enumeration of <code>String</code> objects * containing the names of the servlet's initialization parameters */ @Override public Enumeration<String> getInitParameterNames() { return getServletConfig().getInitParameterNames(); } /** * Returns this servlet's {@link ServletConfig} object. * * @return ServletConfig the <code>ServletConfig</code> object that * initialized this servlet */ @Override public ServletConfig getServletConfig() { return config; } /** * Returns a reference to the {@link ServletContext} in which this servlet * is running. See {@link ServletConfig#getServletContext}. * <p> * This method is supplied for convenience. It gets the context from the * servlet's <code>ServletConfig</code> object. * * @return ServletContext the <code>ServletContext</code> object passed to * this servlet by the <code>init</code> method */ @Override public ServletContext getServletContext() { return getServletConfig().getServletContext(); } /** * Returns information about the servlet, such as author, version, and * copyright. By default, this method returns an empty string. Override this * method to have it return a meaningful value. See * {@link Servlet#getServletInfo}. * * @return String information about this servlet, by default an empty string */ @Override public String getServletInfo() { return ""; } /** * Called by the servlet container to indicate to a servlet that the servlet * is being placed into service. See {@link Servlet#init}. * <p> * This implementation stores the {@link ServletConfig} object it receives * from the servlet container for later use. When overriding this form of * the method, call <code>super.init(config)</code>. * * @param config * the <code>ServletConfig</code> object that contains * configuration information for this servlet * @exception ServletException * if an exception occurs that interrupts the servlet's * normal operation * @see UnavailableException */ @Override public void init(ServletConfig config) throws ServletException { this.config = config; this.init(); } /** * A convenience method which can be overridden so that there's no need to * call <code>super.init(config)</code>. * <p> * Instead of overriding {@link #init(ServletConfig)}, simply override this * method and it will be called by * <code>GenericServlet.init(ServletConfig config)</code>. The * <code>ServletConfig</code> object can still be retrieved via * {@link #getServletConfig}. * * @exception ServletException * if an exception occurs that interrupts the servlet's * normal operation */ public void init() throws ServletException { // NOOP by default } /** * Writes the specified message to a servlet log file, prepended by the * servlet's name. See {@link ServletContext#log(String)}. * * @param message * a <code>String</code> specifying the message to be written to * the log file */ public void log(String message) { getServletContext().log(getServletName() + ": " + message); } /** * Writes an explanatory message and a stack trace for a given * <code>Throwable</code> exception to the servlet log file, prepended by * the servlet's name. See {@link ServletContext#log(String, Throwable)}. * * @param message * a <code>String</code> that describes the error or exception * @param t * the <code>java.lang.Throwable</code> error or exception */ public void log(String message, Throwable t) { getServletContext().log(getServletName() + ": " + message, t); } /** * Called by the servlet container to allow the servlet to respond to a * request. See {@link Servlet#service}. * <p> * This method is declared abstract so subclasses, such as * <code>HttpServlet</code>, must override it. * * @param req * the <code>ServletRequest</code> object that contains the * client's request * @param res * the <code>ServletResponse</code> object that will contain the * servlet's response * @exception ServletException * if an exception occurs that interferes with the servlet's * normal operation occurred * @exception IOException * if an input or output exception occurs */ @Override public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; /** * Returns the name of this servlet instance. See * {@link ServletConfig#getServletName}. * * @return the name of this servlet instance */ @Override public String getServletName() { return config.getServletName(); } }
扫一眼,发现这是个抽象类,很多方法都没有实现,是个空壳,亦或者说是模板,所以我们就看看HttpServlet的一些变量:
private static final long serialVersionUID = 1L;
private static final String METHOD_DELETE = "DELETE";
private static final String METHOD_HEAD = "HEAD";
private static final String METHOD_GET = "GET";
private static final String METHOD_OPTIONS = "OPTIONS";
private static final String METHOD_POST = "POST";
private static final String METHOD_PUT = "PUT";
private static final String METHOD_TRACE = "TRACE";
private static final String HEADER_IFMODSINCE = "If-Modified-Since";
private static final String HEADER_LASTMOD = "Last-Modified";
private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings";
private static final ResourceBundle lStrings = ResourceBundle.getBundle(LSTRING_FILE);
可以看出,提供了一些字符串常量,这些是Http协议中比较重要的一些字符串,例如请求方式、请求头等。
比较有意思的是ResourceBundle这个类,是用来做国际化和本地化问题的。
可以参考:https://blog.csdn.net/mupengfei6688/article/details/79060014
看看其中的部分方法:
我们看看其中比较常用的doGet:
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String msg = lStrings.getString("http.method_get_not_supported");
sendMethodNotAllowed(req, resp, msg);
}
sendMethodNotAllowed方法看一看
private void sendMethodNotAllowed(HttpServletRequest req, HttpServletResponse resp, String msg) throws IOException {
String protocol = req.getProtocol();
// Note: Tomcat reports "" for HTTP/0.9 although some implementations
// may report HTTP/0.9
if (protocol.length() == 0 || protocol.endsWith("0.9") || protocol.endsWith("1.0")) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
} else {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
}
}
其实就是HttpServlet的doGet方法并没有具体的功能,甚至说没有功能,虽然它接收了request,但是只做了http协议支持方面的检测,毕竟http也不是一开始就有所有功能的。详情可以看看http协议版本方面的知识。
如果你用的是SpringBoot或者SpringMVC,那我们尝试一下这个例子:
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/resource")
public class ResourceServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.getWriter().print("ResourceServlet do get...");
}
}
配置一下:
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@ServletComponentScan(basePackages = {
"com.micah.demo.servlet"
})
public class WebConfig implements WebMvcConfigurer {
}
测试:
http://localhost:port/resource
但是我们会有这样的疑问,Servlet是怎么接收Http请求的?单纯说doService?
我们一般会认为说Tomcat为我们做这些事了,我们写了Servlet,请求就会被相应的处理器接收处理。
但我们还是继续深究为什么。
传统的JavaWeb,我们写一个项目,里边有很多个Servlet,以及映射的配置文件web.xml,之后打包成war包,放入Tomcat容器。
后来SpringBoot直接一个DispatcherServlet,还内嵌Tomcat,直接打包成jar包运行。
我们要看多个Servlet的,那就要看传统javaweb方式,那SpringBoot有什么可以复现的呢?
那就要谈谈这个SpringBootServletInitializer类了
SpringBootServletInitializer类注释:
/** * An opinionated {@link WebApplicationInitializer} to run a {@link SpringApplication} * from a traditional WAR deployment. Binds {@link Servlet}, {@link Filter} and * {@link ServletContextInitializer} beans from the application context to the server. * <p> * To configure the application either override the * {@link #configure(SpringApplicationBuilder)} method (calling * {@link SpringApplicationBuilder#sources(Class...)}) or make the initializer itself a * {@code @Configuration}. If you are using {@link SpringBootServletInitializer} in * combination with other {@link WebApplicationInitializer WebApplicationInitializers} you * might also want to add an {@code @Ordered} annotation to configure a specific startup * order. * <p> * Note that a WebApplicationInitializer is only needed if you are building a war file and * deploying it. If you prefer to run an embedded web server then you won't need this at * all. * * @author Dave Syer * @author Phillip Webb * @author Andy Wilkinson * @since 2.0.0 * @see #configure(SpringApplicationBuilder) */
翻译:
一个自以为是的{@link WebApplicationInitializer}来运行一个{@link SpringApplication}
从传统的war部署。 绑定{@link Servlet}, {@link Filter}和
{@link ServletContextInitializer}从应用上下文到服务器的bean。
要配置应用程序,可以重写
{@link #configure(SpringApplicationBuilder)}方法(调用
{@link SpringApplicationBuilder#sources(Class…)}))或使初始化器本身为
{@code @ configuration}。 如果你正在使用{@link SpringBootServletInitializer}
@link WebApplicationInitializer WebApplicationInitializer
可能还想添加一个{@code @Ordered}注释来配置一个特定的启动
秩序。
注意,WebApplicationInitializer只在构建war文件时才需要
部署它。 如果你更喜欢运行嵌入式web服务器,那么你根本不需要这个。
参考: #configure(SpringApplicationBuilder)
上文比较重要的信息是:这个类只是用来实现传统JavaWeb开发的!
当这个开启时,会执行onStartup方法:
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.setAttribute(LoggingApplicationListener.REGISTER_SHUTDOWN_HOOK_PROPERTY, false);
// Logger initialization is deferred in case an ordered
// LogServletContextInitializer is being used
this.logger = LogFactory.getLog(getClass());
WebApplicationContext rootApplicationContext = createRootApplicationContext(servletContext);
if (rootApplicationContext != null) {
servletContext.addListener(new SpringBootContextLoaderListener(rootApplicationContext, servletContext));
}
else {
this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not "
+ "return an application context");
}
}
除了日志之外,重要的部分就是createRootApplicationContext方法:
protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) { SpringApplicationBuilder builder = createSpringApplicationBuilder(); builder.main(getClass()); ApplicationContext parent = getExistingRootWebApplicationContext(servletContext); if (parent != null) { this.logger.info("Root context already created (using as parent)."); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null); builder.initializers(new ParentContextApplicationContextInitializer(parent)); } builder.initializers(new ServletContextApplicationContextInitializer(servletContext)); builder.contextFactory((webApplicationType) -> new AnnotationConfigServletWebServerApplicationContext()); builder = configure(builder); builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext)); SpringApplication application = builder.build(); if (application.getAllSources().isEmpty() && MergedAnnotations.from(getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) { application.addPrimarySources(Collections.singleton(getClass())); } Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the " + "configure method or add an @Configuration annotation"); // Ensure error pages are registered if (this.registerErrorPageFilter) { application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class)); } application.setRegisterShutdownHook(false); return run(application); }
比较值得关注的是SpringApplicationBuilder这个类。
builder.main(getClass())方法很有意思,我们进去看看:
/**
* Fixes the main application class that is used to anchor the startup messages.
* @param mainApplicationClass the class to use.
* @return the current builder
*/
public SpringApplicationBuilder main(Class<?> mainApplicationClass) {
this.application.setMainApplicationClass(mainApplicationClass);
return this;
}
注意这个application是:
private final SpringApplication application;
这意味着什么?意味着这个SpringApplicationBuilder就是内嵌了一个SpringApplication,不然为什么叫做Builder,就是一个构造器模式,为了构建SpringApplication而存在。
在设置完上下文环境后,SpringApplication application = builder.build()构建一个Spring应用,最后run(application)真正开启Spring项目;
很简单的run:
/**
* Called to run a fully configured {@link SpringApplication}.
* @param application the application to run
* @return the {@link WebApplicationContext}
*/
protected WebApplicationContext run(SpringApplication application) {
return (WebApplicationContext) application.run();
}
也就是我们熟知的一种开启SpringBoot应用的方法。
看了一圈源码,我们发现,我们还是没有探究出Servlet究竟在什么地方加进来。因为onStartup方法我们并不能看出有什么servletContext传进来。
突然我在SpringApplication中看到了这个属性:
private ApplicationContextFactory applicationContextFactory = ApplicationContextFactory.DEFAULT;
而这个DEFAULT:
/** * A default {@link ApplicationContextFactory} implementation that will create an * appropriate context for the {@link WebApplicationType}. */ ApplicationContextFactory DEFAULT = (webApplicationType) -> { try { switch (webApplicationType) { case SERVLET: return new AnnotationConfigServletWebServerApplicationContext(); case REACTIVE: return new AnnotationConfigReactiveWebServerApplicationContext(); default: return new AnnotationConfigApplicationContext(); } } catch (Exception ex) { throw new IllegalStateException("Unable create a default ApplicationContext instance, " + "you may need a custom ApplicationContextFactory", ex); } };
AnnotationConfigServletWebServerApplicationContext构造器是这样的:
/**
* Create a new {@link AnnotationConfigServletWebServerApplicationContext} that needs
* to be populated through {@link #register} calls and then manually
* {@linkplain #refresh refreshed}.
*/
public AnnotationConfigServletWebServerApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
其定义:
ClassPathBeanDefinitionScanner scanner
AnnotatedBeanDefinitionReader reader
一听名字,就知道一个是基于注解的,一个是基于类路径配置的
而后面这个reader、scanner的操作是这样的:
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
super.postProcessBeanFactory(beanFactory);
if (this.basePackages != null && this.basePackages.length > 0) {
this.scanner.scan(this.basePackages);
}
if (!this.annotatedClasses.isEmpty()) {
this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));
}
}
如何扫描的:
public int scan(String... basePackages) { int beanCountAtScanStart = this.registry.getBeanDefinitionCount(); this.doScan(basePackages); if (this.includeAnnotationConfig) { AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); } return this.registry.getBeanDefinitionCount() - beanCountAtScanStart; } protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet(); String[] var3 = basePackages; int var4 = basePackages.length; for(int var5 = 0; var5 < var4; ++var5) { String basePackage = var3[var5]; Set<BeanDefinition> candidates = this.findCandidateComponents(basePackage); Iterator var8 = candidates.iterator(); while(var8.hasNext()) { BeanDefinition candidate = (BeanDefinition)var8.next(); ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) { this.postProcessBeanDefinition((AbstractBeanDefinition)candidate, beanName); } if (candidate instanceof AnnotatedBeanDefinition) { AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition)candidate); } if (this.checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); this.registerBeanDefinition(definitionHolder, this.registry); } } }
将packages域下的所有包进行扫描,instanceof 判断类型,然后注册bean。
而我们注意到AnnotationConfigServletWebServerApplicationContext其实就是个ServletWebServerApplicationContext
可以瞄一眼这个图:
我们用this.scanner = new ClassPathBeanDefinitionScanner(this) 自己传入为register,这个register将在后面起到很重要的作用。
我们回归问题,doScan后面怎么将bean加进来的,我们可以继续看其中的一行:
this.registerBeanDefinition(definitionHolder, this.registry);
这一行追溯进去:
protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) {
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
}
继续追溯:
public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
String[] var4 = aliases;
int var5 = aliases.length;
for(int var6 = 0; var6 < var5; ++var6) {
String alias = var4[var6];
registry.registerAlias(beanName, alias);
}
}
}
注意到registry.registerAlias(beanName, alias);
追溯:
package org.springframework.core;
public interface AliasRegistry {
void registerAlias(String name, String alias);
void removeAlias(String alias);
boolean isAlias(String name);
String[] getAliases(String name);
}
发现是个接口。我们知道我们一开始将自己this传入给register域,结合上诉的继承图,再结合以下这个接口实现类:
我们可以知道,我们运行的是GenericApplicationContext(多态!)的registerAlias方法
public void registerAlias(String beanName, String alias) {
this.beanFactory.registerAlias(beanName, alias);
}
继续追溯:
public void registerAlias(String name, String alias) { Assert.hasText(name, "'name' must not be empty"); Assert.hasText(alias, "'alias' must not be empty"); synchronized(this.aliasMap) { if (alias.equals(name)) { this.aliasMap.remove(alias); if (this.logger.isDebugEnabled()) { this.logger.debug("Alias definition '" + alias + "' ignored since it points to same name"); } } else { String registeredName = (String)this.aliasMap.get(alias); if (registeredName != null) { if (registeredName.equals(name)) { return; } if (!this.allowAliasOverriding()) { throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" + name + "': It is already registered for name '" + registeredName + "'."); } if (this.logger.isDebugEnabled()) { this.logger.debug("Overriding alias '" + alias + "' definition for registered name '" + registeredName + "' with new target name '" + name + "'"); } } this.checkForAliasCircle(name, alias); this.aliasMap.put(alias, name); if (this.logger.isTraceEnabled()) { this.logger.trace("Alias definition '" + alias + "' registered for name '" + name + "'"); } } } }
synchronized:加锁了,防止多线程破坏
this.aliasMap.put(alias, name); 注册别名的方法其实就是在Map中放置k-v
同样的方法,我们也可以追溯到registerBeanDefinition其实就是GenericApplicationContext的registerBeanDefinition,继续追溯得到:
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { Assert.hasText(beanName, "Bean name must not be empty"); Assert.notNull(beanDefinition, "BeanDefinition must not be null"); if (beanDefinition instanceof AbstractBeanDefinition) { try { ((AbstractBeanDefinition)beanDefinition).validate(); } catch (BeanDefinitionValidationException var8) { throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", var8); } } BeanDefinition existingDefinition = (BeanDefinition)this.beanDefinitionMap.get(beanName); if (existingDefinition != null) { if (!this.isAllowBeanDefinitionOverriding()) { throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition); } if (existingDefinition.getRole() < beanDefinition.getRole()) { if (this.logger.isInfoEnabled()) { this.logger.info("Overriding user-defined bean definition for bean '" + beanName + "' with a framework-generated bean definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } } else if (!beanDefinition.equals(existingDefinition)) { if (this.logger.isDebugEnabled()) { this.logger.debug("Overriding bean definition for bean '" + beanName + "' with a different definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } } else if (this.logger.isTraceEnabled()) { this.logger.trace("Overriding bean definition for bean '" + beanName + "' with an equivalent definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]"); } this.beanDefinitionMap.put(beanName, beanDefinition); } else { if (this.hasBeanCreationStarted()) { synchronized(this.beanDefinitionMap) { this.beanDefinitionMap.put(beanName, beanDefinition); List<String> updatedDefinitions = new ArrayList(this.beanDefinitionNames.size() + 1); updatedDefinitions.addAll(this.beanDefinitionNames); updatedDefinitions.add(beanName); this.beanDefinitionNames = updatedDefinitions; this.removeManualSingletonName(beanName); } } else { this.beanDefinitionMap.put(beanName, beanDefinition); this.beanDefinitionNames.add(beanName); this.removeManualSingletonName(beanName); } this.frozenBeanDefinitionNames = null; } if (existingDefinition == null && !this.containsSingleton(beanName)) { if (this.isConfigurationFrozen()) { this.clearByTypeCache(); } } else { this.resetBeanDefinition(beanName); } }
前面大部分在检查
this.beanDefinitionMap.put(beanName, beanDefinition);这一行就是关键了
beanDefinitionMap的定义是:
private final Map<String, BeanDefinition> beanDefinitionMap;
BeanDefinition是什么?
package org.springframework.beans.factory.config; import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.MutablePropertyValues; import org.springframework.core.AttributeAccessor; import org.springframework.core.ResolvableType; import org.springframework.lang.Nullable; public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { String SCOPE_SINGLETON = "singleton"; String SCOPE_PROTOTYPE = "prototype"; int ROLE_APPLICATION = 0; int ROLE_SUPPORT = 1; int ROLE_INFRASTRUCTURE = 2; void setParentName(@Nullable String var1); @Nullable String getParentName(); void setBeanClassName(@Nullable String var1); @Nullable String getBeanClassName(); void setScope(@Nullable String var1); @Nullable String getScope(); void setLazyInit(boolean var1); boolean isLazyInit(); void setDependsOn(@Nullable String... var1); @Nullable String[] getDependsOn(); void setAutowireCandidate(boolean var1); boolean isAutowireCandidate(); void setPrimary(boolean var1); boolean isPrimary(); void setFactoryBeanName(@Nullable String var1); @Nullable String getFactoryBeanName(); void setFactoryMethodName(@Nullable String var1); @Nullable String getFactoryMethodName(); ConstructorArgumentValues getConstructorArgumentValues(); default boolean hasConstructorArgumentValues() { return !this.getConstructorArgumentValues().isEmpty(); } MutablePropertyValues getPropertyValues(); default boolean hasPropertyValues() { return !this.getPropertyValues().isEmpty(); } void setInitMethodName(@Nullable String var1); @Nullable String getInitMethodName(); void setDestroyMethodName(@Nullable String var1); @Nullable String getDestroyMethodName(); void setRole(int var1); int getRole(); void setDescription(@Nullable String var1); @Nullable String getDescription(); ResolvableType getResolvableType(); boolean isSingleton(); boolean isPrototype(); boolean isAbstract(); @Nullable String getResourceDescription(); @Nullable BeanDefinition getOriginatingBeanDefinition(); }
就是一个接口,定义了bean的基本情况。
一个比较典型的bean实例就是AbstractBeanDefinition,我们可以看到
@Nullable
private volatile Object beanClass;
就是这个bean内部的Object类。
还有一个问题,Tomcat不是内置在SpringBoot中吗?是的,AnnotationConfigServletWebServerApplicationContext的父类ServletWebServerApplicationContext有一个方法:
@Override protected void onRefresh() { super.onRefresh(); try { createWebServer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start web server", ex); } } private void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create"); ServletWebServerFactory factory = getWebServerFactory(); createWebServer.tag("factory", factory.getClass().toString()); this.webServer = factory.getWebServer(getSelfInitializer()); createWebServer.end(); getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.webServer)); getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this, this.webServer)); } else if (servletContext != null) { try { getSelfInitializer().onStartup(servletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context", ex); } } initPropertySources(); }
这里的getWebServer就有可能get到Tomcat服务器(如果getWebServerFactory得到的是TomcatServletWebServerFactory的话)。
总结:
看了那么多,我们知道了,其实WebApplicationInitializer开启应用的时候,就是创建SpringApplicationBuillder来创建SpringApplication实例,SpringApplication里自动初始化一个AnnotationConfigServletWebServerApplicationContext,它有scan(还有reader,原理也差不多)可以扫描basepackages下的各个包下的所有bean(而这些应该就是Servlet,因为他是AnnotationConfigServletWebServerApplicationContext扫描的)。这些Servlet刚刚被扫描时以BeanDefinition的形式存在,后面被用this形式传进来的注册器registry,也就是这个有勇气的AnnotationConfigServletWebServerApplicationContext,调用自身父类的registerBeanDefinition方法将bean注册进自己的Map中。builder.contextFactory方法最终将这些配置(包括bean、可能使用的tomcat服务器)应用到创建的Application中去,然后run。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。