#Spring #SpringMVC #Mybatis #源码
# 添加 Sring+SpringMVC 相关依赖
因为使用的 maven,web 容器使用的 tomcat,所以先在 pom.xml 中加上:
<packaging>war</packaging> |
这样项目就会自动将依赖放进 WEB-INF 下面的 lib 中,否则会出现一系列 NoClassFound 等错误。
添加 spring-webmvc 依赖:
<dependency> | |
<groupId>org.springframework</groupId> | |
<artifactId>spring-webmvc</artifactId> | |
<version>${spring.version}</version> | |
</dependency> | |
<!-- 方便查看源码 --> | |
<dependency> | |
<groupId>javax.servlet</groupId> | |
<artifactId>javax.servlet-api</artifactId> | |
<version>4.0.1</version> | |
</dependency> |
只添加一个 spring-webmvc 就够了
可以看到已经将相关依赖全部加载进去了。
这里有一个问题,@Contoller 和 @GetMapping 等注解是在 spring-web 中,然而只有这两个注解,不去配置 DispatcherServlet 是不会处理请求的,也就是说如果只导入 spring-web 依赖,而没有 spring-webmvc,即使有 @Controller,也只是个普通的注解。
# 配置 spring
在 web.xml 中配置
<listener> | |
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> | |
</listener> | |
<!-- 采用注解方式配置 --> | |
<context-param> | |
<param-name>contextClass</param-name> | |
<param-value> | |
<!-- 使用注解实现类 --> | |
org.springframework.web.context.support.AnnotationConfigWebApplicationContext | |
</param-value> | |
</context-param> | |
<context-param> | |
<param-name>contextConfigLocation</param-name> | |
<!-- 配置类 --> | |
<param-value>com.sk.day31.config.ApplicationConfig</param-value> | |
</context-param> |
ContextLoaderListener 实现了 ServletContextListener 接口,ServletContextListener 在文档中的解释是
该接口的实现可以接收关于它们所处的 Web 应用程序的 Servlet 上下文变化的通知。为了接收通知事件,必须在 Web 应用程序的部署描述符中配置该实现类。
ServletContextListener 是 Java EE 标准接口之一,类似 Jetty,Tomcat,JBoss 等 java 容器启动时便会触发该接口的 contextInitialized。
# spring 加载流程
- 在 Web 容器启动后,触发 ContextLoaderListener,contextInitialized 方法被调用,接着调用 initWebApplicationContext 方法,并将 ServletContext 当作参数传入
@Override | |
public void contextInitialized(ServletContextEvent event) { | |
initWebApplicationContext(event.getServletContext()); | |
} |
- 进入 initWebApplicationContext 方法后,调用 createWebApplicationContext
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { | |
... | |
try { | |
// Store context in local instance variable, to guarantee that | |
// it is available on ServletContext shutdown. | |
if (this.context == null) { | |
this.context = createWebApplicationContext(servletContext); | |
} | |
... | |
} |
- 进入 createWebApplicationContext 方法, 源码如下:
protected WebApplicationContext createWebApplicationContext(ServletContext sc) { | |
Class<?> contextClass = determineContextClass(sc); | |
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { | |
throw new ApplicationContextException("Custom context class [" + contextClass.getName() + | |
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]"); | |
} | |
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); | |
} |
determineContextClass 用来返回一个实现了 WebApplicationContext 接口的类:
protected Class<?> determineContextClass(ServletContext servletContext) { | |
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM); | |
if (contextClassName != null) { | |
try { | |
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader()); | |
} | |
catch (ClassNotFoundException ex) { | |
throw new ApplicationContextException( | |
"Failed to load custom context class [" + contextClassName + "]", ex); | |
} | |
} | |
else { | |
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName()); | |
try { | |
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader()); | |
} | |
catch (ClassNotFoundException ex) { | |
throw new ApplicationContextException( | |
"Failed to load default context class [" + contextClassName + "]", ex); | |
} | |
} | |
} |
determineContextClass 根据 web.xml 中定义的 contextClass,来决定返回的实现类,如果没有配置,则返回默认的 XmlWebApplicationContext,最后通过 BeanUtils.instantiateClass (contextClass) 创建对象。
那么就出现一个问题,XmlWebApplicationContext 是什么?
首先要先讲一下 BeanFactory,根据官方文档,BeanFactory 接口提供了一个高级配置机制,能够管理任何类型的对象。简单来说就是 Spring 的容器,用来管理各种 Bean。
import org.springframework.beans.factory.InitializingBean; | |
import org.springframework.beans.factory.xml.XmlBeanFactory; | |
import org.springframework.core.io.ClassPathResource; | |
public class MainApp { | |
public static void main(String[] args) { | |
XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("Beans.xml")); | |
HelloWorld obj = (HelloWorld) factory.getBean("helloWorld"); | |
obj.getMessage(); | |
} | |
} |
上述例子就是用了 BeanFactory 的其中一个实现类 XmlBeanFactory 来创建 Bean。
而 ApplicationContext 是 BeanFactory 的扩展,提供了更多的功能,例如:
-
用于访问应用程序组件的 Bean 工厂方法。从 ListableBeanFactory 继承而来。
-
以通用方式加载文件资源的能力。从 ResourceLoader 接口继承而来。
-
向注册的听众发布事件的能力。从 ApplicationEventPublisher 接口继承而来。
-
解决消息的能力,支持国际化。继承自 MessageSource 接口。
ApplicationContext 常用的实现类有 ClassPathXmlApplicationContext,以及上面提到的 WebApplicationContext,这些实现类相当于特化,比如 ClassPathXmlApplicationContext 就是从类路径中获取配置文件:
public class TestSpring { | |
@Test | |
public void test(){ | |
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml"); | |
Hello hello=(Hello)context.getBean("helloWorld"); | |
hello.say(); | |
} | |
} |
WebApplicationContext 是专门为 web 应用准备的,它允许从相对于 Web 根目录的路径中装载资源配置文件完成初始化工作。而 WebApplicationContext 最常用的实现类也就是上面的 XmlWebApplicationContext 了,此实现类会默认从 WEB-INF 的目录下读取 applicationContext.xml 来创建容器,当然这个路径也可以自定义。
- 再回到前面,determineContextClass 返回了 XmlWebApplicationContext 类,并实例化,将其强转为 ConfigurableWebApplicationContext,并执行 configureAndRefreshWebApplicationContext
if (this.context instanceof ConfigurableWebApplicationContext) { | |
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context; | |
if (!cwac.isActive()) { | |
... | |
configureAndRefreshWebApplicationContext(cwac, servletContext); | |
} | |
} |
- 进入 configureAndRefreshWebApplicationContext 方法,通过 ServletContext 获取 web.xml 中的 contextConfigLocation 值,设置到 XmlWebApplicationContext 中。
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM); | |
if (configLocationParam != null) { | |
wac.setConfigLocation(configLocationParam); | |
} | |
... | |
wac.refresh(); |
- 执行 refresh 方法
public void refresh() throws BeansException, IllegalStateException { | |
Object var1 = this.startupShutdownMonitor; | |
synchronized(this.startupShutdownMonitor) { | |
// 容器预先准备,记录容器启动时间和标记 | |
prepareRefresh(); | |
// 创建 bean 工厂,里面实现了 BeanDefinition 的装载 | |
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); | |
// 配置 bean 工厂的上下文信息,如类装载器等 | |
prepareBeanFactory(beanFactory); | |
try { | |
// 在 BeanDefinition 被装载后,提供一个修改 BeanFactory 的入口 | |
postProcessBeanFactory(beanFactory); | |
// 在 bean 初始化之前,提供对 BeanDefinition 修改入口,PropertyPlaceholderConfigurer 在这里被调用 | |
invokeBeanFactoryPostProcessors(beanFactory); | |
// 注册各种 BeanPostProcessors,用于在 bean 被初始化时进行拦截,进行额外初始化操作 | |
registerBeanPostProcessors(beanFactory); | |
// 初始化 MessageSource | |
initMessageSource(); | |
// 初始化上下文事件广播 | |
initApplicationEventMulticaster(); | |
// 模板方法 | |
onRefresh(); | |
// 注册监听器 | |
registerListeners(); | |
// 初始化所有未初始化的非懒加载的单例 Bean | |
finishBeanFactoryInitialization(beanFactory); | |
// 发布事件通知 | |
finishRefresh(); | |
} catch (BeansException var5) { | |
if(this.logger.isWarnEnabled()) { | |
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var5); | |
} | |
this.destroyBeans(); | |
this.cancelRefresh(var5); | |
throw var5; | |
} | |
} | |
} |
完成 bean 的加载。
# JavaConfig 方式配置 ApplicationConfig
@Configuration | |
@ComponentScan(basePackages = "com.sk.day31", excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class)}) | |
public class ApplicationConfig { | |
} |
效果类似
<context:component-scan base-package="com.sk.day31"> | |
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>--> | |
</context:component-scan> |
# 配置 spring-webmvc
在 web.xml 中配置:
<servlet> | |
<servlet-name>dispatcherServlet</servlet-name> | |
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> | |
<init-param> | |
<param-name>contextClass</param-name> | |
<param-value> | |
<!-- 使用注解实现类 --> | |
org.springframework.web.context.support.AnnotationConfigWebApplicationContext | |
</param-value> | |
</init-param> | |
<init-param> | |
<param-name>contextConfigLocation</param-name> | |
<param-value>com.sk.day31.config.SpringMvcConfig</param-value> | |
</init-param> | |
<load-on-startup>1</load-on-startup> | |
</servlet> | |
<servlet-mapping> | |
<servlet-name>dispatcherServlet</servlet-name> | |
<url-pattern>/</url-pattern> | |
</servlet-mapping> |
@Configuration | |
@ComponentScan(basePackages = "com.sk.day31.controller") | |
@EnableWebMvc | |
public class SpringMvcConfig implements WebMvcConfigurer { | |
@Override | |
public void addResourceHandlers(ResourceHandlerRegistry registry) { | |
registry.addResourceHandler("/resources/**") | |
.addResourceLocations("classpath:/static/") | |
.setCacheControl(CacheControl.maxAge(Duration.ofDays(365))); | |
} | |
@Override | |
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { | |
configurer.enable(); | |
} | |
} |
@EnableWebMvc 效果等于 mvc:annotation-driven/。
configureDefaultServletHandling 方法作用等于 mvc:default-servlet-handler/
# 配置 mybatis
# 添加依赖
<dependency> | |
<groupId>org.springframework</groupId> | |
<artifactId>spring-jdbc</artifactId> | |
<version>${spring.version}</version> | |
</dependency> | |
<dependency> | |
<groupId>mysql</groupId> | |
<artifactId>mysql-connector-java</artifactId> | |
<version>8.0.28</version> | |
</dependency> | |
<dependency> | |
<groupId>com.alibaba</groupId> | |
<artifactId>druid</artifactId> | |
<version>1.1.22</version> | |
</dependency> | |
<dependency> | |
<groupId>org.mybatis</groupId> | |
<artifactId>mybatis-spring</artifactId> | |
<version>2.0.7</version> | |
</dependency> | |
<dependency> | |
<groupId>org.mybatis</groupId> | |
<artifactId>mybatis</artifactId> | |
<version>3.5.9</version> | |
</dependency> |
spring-jdbc 必须添加,不然会报错 java.lang.NoClassDefFoundError: org/springframework/dao/support/DaoSupport。
# 配置类
@Configuration | |
@PropertySource("classpath:druid.properties") | |
@MapperScan(basePackages = "com.sk.day31.mapper") | |
public class MybatisConfig { | |
@Value("${jdbc.driver}") | |
private String driver; | |
@Value("${jdbc.url}") | |
private String url; | |
@Value("${jdbc.username}") | |
private String username; | |
@Value("${jdbc.password}") | |
private String password; | |
@Bean | |
public DruidDataSource dataSource(){ | |
DruidDataSource dataSource=new DruidDataSource(); | |
dataSource.setDriverClassName(driver); | |
dataSource.setUrl(url); | |
dataSource.setUsername(username); | |
dataSource.setPassword(password); | |
return dataSource; | |
} | |
@Bean | |
public SqlSessionFactory sqlSessionFactory() throws Exception { | |
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); | |
factoryBean.setTypeAliasesPackage("com.sk.day31.entity"); | |
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml")); | |
factoryBean.setDataSource(dataSource()); | |
return factoryBean.getObject(); | |
} | |
} |
到此就算是整合完毕了