现在主流的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.youkuaiyun.com/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。