共计 11104 个字符,预计需要花费 28 分钟才能阅读完成。
在伪装是小白之重学 Spring MVC(一)中曾经介绍了 Spring MVC 比拟罕用的知识点,然而还是有些脱漏,比方将申请放入 Session,在配置文件中配置太烦了,咱们是否将配置文件挪动至配置类中,又比方 SSM(Spring Spring MVC MyBatis)整合。
本篇的次要内容就是介绍这些,将配置文件转换成配置类须要懂一些 Spring 的外围注解,比方 @Import,这个注解在欢迎光临 Spring 时代(一) 上柱国 IOC 列传曾经介绍过了,不懂的能够再翻一下这篇文章。
放入 session 中
咱们如何将数据放入 session 中呢? 在原生 Servlet 时代,咱们从 HttpServletRequest 中获取 HttpSession 对象就好,像上面这样:
当然你在 Spring MVC 框架还能够接着用,Spring MVC 又提供了 @SessionAttributes 注解用于将数据放入 Session 中,@SessionAttribute 用于将 session 中的值,通过翻阅源码,咱们能够发现 @SessionAttributes 只能作用于类上,在翻阅源码的过程中发现了 @SessionAttribute 注解,原本认为和 @SessionAttributes 是一样的用途,起初翻源码才发现,这个是用来取出 Session 中的值放到办法参数中,只能作用办法参数上。
又通过看源码的正文,咱们能够看到 SessionAttributes 的两个 value 和 name 是同义语,假如有申请域 (申请域中的数据都是呈 K、V 对模式,我门当初谈的就是 key) 中有和 value 值雷同的数据,那么就会将该数据也会放到 Session 中。而 Type 属性则是,只有是申请域的数据是该类型的,就放入到 Session 中。
咱们当初来测试一下 @SessionAttributes:
Controller
SessionAttributes(value = "password")
public class SessionDemoController {@RequestMapping(value = "/session", method = RequestMethod.GET)
public String testGet(@RequestParam(value = "name") String userName, @RequestParam(value = "password") String password ,Model model) {System.out.println("username:" + userName);
System.out.println("password:" + password);
model.addAttribute("password",password);
return "success";
}
}
success.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1> success </h1>
<h1> username: ${sessionScope.userName}</h1>
<h1> password: ${sessionScope.password}</h1>
</body>
</html>
url: http://localhost:8080/studySpringFrameWork_war_exploded/session?name=aa&&password=aaa
测试后果:
SessionAttribute:
RequestMapping(value = "/servlet", method = RequestMethod.GET)
public String testGet(@RequestParam(value = "name") String userName, @SessionAttribute(value = "password")String password) {System.out.println("username:" + userName);
System.out.println("password:" + password);
return "success";
}
url: http://localhost:8080/studySpringFrameWork_war_exploded/servlet?name=aaa
测试后果:
能够看到 url 中没有 password,password 仍然打印进去有值,这个就是 session 中的值。
配置文件的配置挪动至配置类
简略的说配置文件的实质就是,在读取配置文件的时候,依据标签将对应类的对象注册进 IOC 容器中。然而配置文件配置了太多也是很烦的,可不可以转换成配置类的模式呢?Spring MVC: 当然能够。
那首先第一个问题就是以前是配置文件中让 Tomcat 启动的时候读取配置文件,注册 DispacherServlet 怎么移植?
通过 org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer 来移植,下面的正文很清晰,有的时候碰见英文不要冲突,读一读多累积几个单词,语法大多都不难:
如果感觉这个格局不是很喜爱,能够去看对应的 Java Doc,上面是地址:
- https://docs.spring.io/spring…
接着咱们来看 WebApplicationInitializer 的正文:
Interface to be implemented in Servlet 3.0+ environments in order to configure the ServletContext programmatically — as opposed to (or possibly in conjunction with) the traditional web.xml-based approach.
该接口在在 Servlet 3.0 以上的版本才被实现可能实现以配置类的模式配置 ServletContext,和传统的基于 web.xml 的配置形式绝对。Implementations of this SPI will be detected automatically by SpringServletContainerInitializer, which itself is bootstrapped automatically by any Servlet 3.0 container. See its Javadoc for details on this bootstrapping mechanism.
SpringServletContainerInitializer 会主动监测该接口的实现类,而后被任何反对 Servlet 3.0 版本以上的容器主动加载。
感觉是不是有点拗口,有点不明确什么意思的感觉,我的了解就是在反对 Servlet 3.0 的 Servelt 容器能力应用 Spring MVC 配置类的模式。上面这段正文说的是该接口的实现类会被 Servlet 容器主动加载。
然而咱们这次并不是抉择实现 WebApplicationInitializer,这样做有些麻烦了,Spring MVC 提供了一个简略点的 Web 初始化器: org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer,供咱们应用。
AbstractAnnotationConfigDispatcherServletInitializer 是一个抽象类,咱们继承它,而后继承一下它看看:
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {return new Class[0];
}
@Override
protected Class<?>[] getServletConfigClasses() {return new Class[0];
}
/**
* 用于设置拦挡的 URL
* @return
*/
@Override
protected String[] getServletMappings() {return new String[0];
}
}
getRootConfigClasses 和 getServletConfigClasses 这两个办法,咱们就须要专门拿进去讲讲,从名字上能够推断都是加载配置类,那这两个办法有什么区别呢? 咱们还是翻翻官网文档,提供了 Java Config 和 基于 xml 的等价写法:
咱们看下比照哈:
也就是说 getServletConfigClasses 作用就是等同于加载 Spring MVC 的配置文件,getServletMappings 就是设定要拦挡的 URL。getRootConfigClasses。咱们本次学习的 Spring MVC 都是只注册了一个 DispatcherServlet,Spring MVC 容许咱们注册多个 DispacherServlet,那么多个 Spring MVC 的配置文件就是隔开的,然而呢,在这些不同的 DispatcherServlet 之间如果有一些 bean 须要共享怎么办呢?getRootConfigClasses 就加载顶层的配置类,被各个 DispatcherServlet 共享。
以后咱们就只有一个 DispatcherServlet,所以咱们就只建一个空类。所以 AppInitializer 就被革新成了上面这样:
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {return new Class[]{RootConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {return new Class[]{WebConfig.class};
}
/**
* 用于设置拦挡的 URL
*
* @return
*/
@Override
protected String[] getServletMappings() {return new String[]{"/"};
}
}
@Configuration
public class RootConfig {
}
@EnableWebMvc
@ComponentScan(basePackages = {"org.example.mvc"})
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new MyHandlerInterceptor());
}
}
@EnableWebMvc
是新面孔,咱们这里须要大抵讲一下。
@EnableWebMvc 简介
要齐全讲清楚 @EnableWebMvc
,咱们齐全能够再开一篇文章去讲,因为在这个注解身上,Spring 做了大量的工作,要齐全讲清楚不是一件简略的事件,这里我做的介绍只是让各位同学明确,@EnableWebMvc
相当于配置文件中做了什么?
能翻官网文档,尽量还是翻官网文档,官网文档有这部分的介绍:
这个截图仿佛还不是很清晰,咱们放在 IDE 里截图看一下:
<mvc:annotation-driven/> 这行不少视频都说,这个是把加上了 @Controller 的类变成解决申请的类,然而也不晓得是不是跟我用的 Spring MVC 版本有关系,我没加这个也行。
然而倡议你还是加,解释分明不加也行(可能会出一些莫名的问题),然而这也不是一件简略的事件,因为这要波及很多源码的解读,如果有兴致理解能够参看:
-
spring mvc 的
<mvc:annotation-driven/>
有什么用?
拦截器等标签的移植
各位认真看下面的 WebConfig 这个类,这个类继承了 WebMvcConfigurer,我在外面重写了 addInterceptors 办法,从办法名字上能够推断进去,这个办法是加拦截器的。而后咱们将残缺的写一下:
@EnableWebMvc
@ComponentScan(basePackages = {"org.example.mvc"})
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new MyHandlerInterceptor()).addPathPatterns("/**").excludePathPatterns("login");
}
}
咱们再度比照一下:
对于 bean 标签咱们不讲怎么移植,这在欢迎光临 Spring 时代(一) 上柱国 IOC 列传曾经讲过了,这里不再讲一遍。WebMvcConfigurer 的办法这里就不一一拿进去细讲了,能够本人看下正文。咱们这里只讲大抵的思维。
测试一下
留神要先正文掉 web.xml 事后加载的那个配置。基本上测试是胜利的。这里我就不再贴代码了。
我把代码放在码云上,各位有兴致的同学能够翻翻看看。基本上到这里,Spring MVC 就告一段落了。
地址如下:
- https://github.com/CXK6013/SSM
SSM 整合
SSM 整合前言
到这里咱们就能够开始讲一下,SSM 整合了,过后跟同学一块学 SSM 整合的时候,有的同学不了解思维,就是背,有的时候本人尝试做整合,背错了就整合失败了。这里我心愿讲思维,有了这个思维,我想再去做整合的时候,就会晓得本人错在哪里。
在看到这里的时候,我心愿你对 Spring 的思维有一点理解,倡议参看我 Spring 系列的文章:
- 欢迎光临 Spring 时代 - 绪论
- 欢迎光临 Spring 时代(一) 上柱国 IOC 列传
- 代理模式 -AOP 绪论
- 欢迎光临 Spring 时代(二) 上柱国 AOP 列传
留神这里是 SSM 整合,那 MyBatis 也要求会,如果不会请参看我 MyBatis 系列的文章。
- 伪装是小白之重学 MyBatis(一)
如果不会 Spring MVC,请参看我 Spring MVC 系列的文章:
- 伪装是小白之重学 Spring MVC(一)
SSM 整合的思维 - 天下归 Spring 统
Spring Framework 是一个胜利的框架,解决了对象之间有简单的依赖关系的时候,在不心愿是硬编码的状况下,取对象优雅和可配置化,那么其余框架也要纳入到 Spring Framework 中,让咱们取该框架的对象时候优雅一点。这也就是整合思维,将该框架的外围对象放入 IOC 容器中,而后取出来。再讲一遍,假如是 MyBatis 须要数据源、扫描 xml,咱们就对立在 IOC 容器中配置。
而后取的时候用 Spring 提供的取对象形式,取出来就行了。SSM 整合的核心思想就是将 Spring MVC、MyBatis 的外围对象放进 IOC 容器中,而后咱们取。
那么咱们对 MyBatis 整合的愿景是什么呢?咱们再看下咱们学习 MyBatis 的代码:
那怎么整合将 MyBatis 整合进入 Spring 能力实现这样的成果呢?仅仅看目前的代码,咱们是将 SqlSessionFactory 放入 IOC 容器中,而后从 IOC 容器获取 session,再调用 getMapper 办法。这样是不是有点麻烦呢? 有没有更简洁的计划呢? 这个解决方案叫 mybatis-spring,依赖如下:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
这个解决方案可能做到将 Mapper 接口 (即 Xml 对应的接口,下文会统称为 Mapper 对象) 对象放入到 IOC 容器中,咱们只用在须要对应 Mapper 的中央,用 @Autowired
注解注入即可
整合 MyBatis
咱们这里只介绍基于 xml 模式的,基于配置类的,将 bean 标签挪动至配置类即可。
基于 xml 模式的
首先咱们用 Druid 连接池治理数据源,那当然是在配置文件中配置一个即可:
<!-- 加载配置文件 -->
<bean id = "placeholderConfigurer" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:jdbc.properties</value>
</list>
</property>
</bean>
<bean id = "dataSource" class = "com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.dev.driver}">
</property>
<property name = "url" value="${jdbc.dev.driver}">
</property>
<property name = "username" value = "${jdbc.dev.username}">
</property>
<property name = "password" value = "${jdbc.dev.password}">
</property>
</bean>
要实现将 Mapper 接口的对象放入到 IOC 容器中,外围是两个类:
- org.mybatis.spring.SqlSessionFactoryBean
- org.mybatis.spring.mapper.MapperScannerConfigurer
SqlSessionFactoryBean 概览:
MapperScannerConfigurer 概览:
其实下面也有官网文档:
地址是: http://mybatis.org/spring/zh/…
这下面讲了 mybatis-spring 的起源和如何整合,咱们照着做就行。
<bean id = "sqlSessionFactory" class = "org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref = "dataSource"></property>
<property name = "mapperLocations" value="org\example\mybatis\*.xml">
</property>
</bean>
<bean id = "mapperScannerConfigurer" class = "org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value = "org.example.mybatis" />
<property name="sqlSessionFactoryBeanName" value = "sqlSessionFactory"></property>
</bean>
可能有同学会问,那之前的配置文件咋弄? 切换环境怎么办?
SqlSessionFactoryBean 中有一个 configuration 字段是 Configuration 类型的,咱们看下 Configuration 类:
而后再配置就行了。如果你不想配置 bean,也能够指定门路,通过 SqlSessionFactoryBean 的 configLocation 指定 MyBatis 的配置文件地位就能够了。
整合 Spring MVC
其实在下面介绍 Spring MVC 的时候曾经在用 Spring,大家可能感触不到整合的这个过程,这就叫无缝集成。
接下来讲的就是,如果你有多个 DisPatcherServlet,然而有一批 bean 是共享的,又不想在在每个配置文件中都配置一份。怎么办。在用配置类的状况下,咱们是通过 getRootConfigClasses 来配置的,还记得它的 xml 写法吗?
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 这里用来加载多有 DispatcherServlet 共享的 bean-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/root-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app1</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app1-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app1</servlet-name>
<url-pattern>/app1/*</url-pattern>
</servlet-mapping>
</web-app>
小插曲
我认为这里整合的逆风逆水一点故障都没有,就打算收尾了,而后就启动了我的项目,而后就报少 jar 包,少的是这两个:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
而后发现补上了之后,还是注入失败,此时我陷入了沉思,最大的苦楚还是,我不晓得我错在了哪里? 因为控制台也不报错,比报错还可怕的就是,程序不报错。于是我开始排查,在 main 函数里加载配置文件试了试看:
public static void main(String[] args) throws IOException {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-MVC.xml");
BlogMapper blogMapper = applicationContext.getBean(BlogMapper.class);
System.out.println(blogMapper.selectAllReturnMap());
}
发现胜利获取,于是我就狐疑我的配置文件没加载,上面是我的 web.xml 中的配置:
而后是 Spring MVC 的配置文件:
于是我猜测首先加载的是 context-param 标签中的配置文件,在这个过程中实现对各个 bean 的初始化。
而后再加载 Spring MVC 的配置文件,对 Spring MVC 的一些 bean 进行初始化。
而后我把整合 MyBatis 的代码就移入了 applicationContext.xml 中,而后就胜利了。
排错思路
基本上我也是在引入日志之后,才推断进去我的 Spring-MVC 配置文件没有胜利加载的,日志能够把 Spring IOC 容器创立的 bean 打印进去:
通过日志我发现我独自在 main 函数中加载 Spring MVC 的配置文件的时候,有 sqlSessionFactory 创立胜利的日志,然而我启动 Tomcat 就没有。所以我才推断 Spring-MVC 的配置文件加载的是不是有些晚了。而后验证一下果然是。日志配置也有一些坑,比如说 Log 4j 2 不反对 properties 的配置文件,名字也必须交 log4j2.properties。而后日志怎么配,跟着 GitHub 上的配就行了。GitHub 上对应我的项目的地址如下:
- https://github.com/CXK6013/SSM
源码选讲之 Spring MVC 的执行流程
接下来咱们钻研一下 Spring MVC 的执行流程,这也是高频面试题,钻研这个是为了让咱们对 Spring MVC 的了解更进一步。
首先申请总是先进入到 DispatcherServlet 中,而后咱们还是大抵看一下 DispatcherServlet 这个源码,源码太宏大了,不便于展现,这里就间接看两个外围办法了:
- HandlerExecutionChain (处理器执行链)
- doDispatch (申请散发)
咱们次要看 doDispath 办法:
基于此咱们能够大抵画出 Spring MVC 的执行流程:
其实这个 Spring MVC 执行流程曾经人尽皆知了,毕竟是高频面试题,然而我是想从源码的角度去看。基本上整个流程都在 DispatcherServlet 的 dispatch 办法中,有兴致的同学能够也看一下。
总结一下
每次重学框架都会有新的认知,这次也算是小有收货,把心里以前的疑难都扫清了。心愿对各位同学而学习 Spring MVC 会有所帮忙,然而到这里就完结了吗?远远没有,不感觉这些配置有些烦吗?还有依赖治理,其实我做的时候都是小心翼翼的,惟恐起了依赖抵触,能不能简化一下呢?当然能够,这些都将由 Spring 家族的一个明星成员 Spring Boot 给出答案。
参考资料
- 详解 @EnableWebMvc
- 如何应用纯 java config 来配置 spring mvc
- Spring MVC 官网文档
- getServletConfigClasses() 和 getRootConfigClasses()的不同点