为什么需要整合SpringMVC
Spring和Mybatis以前已经整合过,这样能够更加方便的使用Mybatis,但为什么还需要整合SpringMVC呢?SpringMVC不是已经属性Spring的一部分了吗?为什么还需要整合?
是需要整合的,因为现在所有的Bean都在同一个容器里面。我们通常在Controller层调用Service层,Service层调用Dao层,如果在Service层也调用Controller层的代码,比如在Service层里面注入Controller,代码就会变得混乱。为了解决这个问题,我们需要从代码层面进行隔离,如果Controller层的代码放在一个容器、Service层的代码放在另一个容器里面,彼此容器相互隔离就好了,于是父子容器的方案就诞生啦。
相关准备和依赖
模型数据
package com.lff.domain;public class Customer {private Integer custId;private String custName;private String custAddress;private String custCity;private String custState;private String custZip;private String custCountry;private String custContact;private String custEmail;//getter setter}
Dao层
package com.lff.dao;import com.lff.domain.Customer;import org.apache.ibatis.annotations.Select;import java.util.List;public interface CustomerDao {Boolean save(Customer customer);Boolean update(Customer customer);List<Customer> list();Customer get(Integer id);Boolean remove(Integer id);}
Service层
package com.lff.service;import com.lff.domain.Customer;import java.util.List;public interface CustomerService {Boolean save(Customer customer);Boolean update(Customer customer);List<Customer> list();Customer get(Integer id);}
实现类
package com.lff.service.impl;import com.lff.dao.CustomerDao;import com.lff.domain.Customer;import com.lff.service.CustomerService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import java.util.List;@Componentpublic class CustomerServiceImpl implements CustomerService {@AutowiredCustomerDao customerDao;@Overridepublic Boolean save(Customer customer) {return customerDao.save(customer);}@Overridepublic Boolean update(Customer customer) {return customerDao.update(customer);}@Overridepublic List<Customer> list() {return customerDao.list();}@Overridepublic Customer get(Integer id) {return customerDao.get(id);}}
Controller层
package com.lff.controller;import com.lff.domain.Customer;import com.lff.service.CustomerService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import java.util.List;@Controller@RequestMapping("/customers")public class CustomerController {@Autowiredprivate CustomerService service;public void setService(CustomerService service) {this.service = service;}@RequestMapping("/list")@ResponseBodypublic List<Customer> list(){return service.list();}@RequestMapping("/update")@ResponseBodypublic String update() {Customer customers1 = new Customer();customers1.setCustId(1013);customers1.setCustName("lffxxx");customers1.setCustState("ss");customers1.setCustCity("BJ");customers1.setCustState("CN");customers1.setCustZip("10001");return service.update(customers1) ? "success":"error";}}
Maven依赖配置
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.ff</groupId><artifactId>Demo5</artifactId><version>1.0-SNAPSHOT</version><!--打包方式默认是jar,web工程打包为war,Java工程打包为jar --><packaging>war</packaging><!--属性信息比如字符编码等 --><properties><!--字符编码 --><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><!--Maven项目的JDK版本 --><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><!-- 依赖--><dependencies><!-- JavaEE基本依赖--><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version><scope>provided</scope></dependency><dependency><groupId>javax.servlet</groupId><artifactId>jsp-api</artifactId><version>2.0</version><scope>provided</scope></dependency><!-- SpringMVC --><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.2.8.RELEASE</version></dependency><!-- 面向切面编程--><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.6</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>1.9.6</version></dependency><!-- mybatis相关包--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.5</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>2.0.5</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.2.8.RELEASE</version></dependency><!-- 数据库--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.49</version></dependency><!-- 数据库连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.22</version></dependency><!-- 日志 --><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version></dependency><!-- 单元测试 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13</version><scope>test</scope></dependency><!-- domain模型转JSON字符串 --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.11.0</version></dependency><!-- 解析Multipart类型参数 --><dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.4</version></dependency></dependencies></project>
设置打包资源
<!--构建信息比如插件配置等--><build><finalName>demo5</finalName><!-- 说明资源的位置(哪些东西算是资源) --><resources><!-- resources文件里面的内容要进行打包 --><resource><directory>src/main/resources</directory></resource><!-- java文件里面的内容要进行打包 --><resource><directory>src/main/java</directory><includes><include>**/*.properties</include><include>**/*.xml</include></includes></resource></resources></build>
父子容器

父容器和子容器相互隔离,可以存在相同名称的Bean。子容器可以访问父容器里面的Bean,父容器不能访问子容器的Bean,也就是控制器层可以注入Service的代码,Service不能注入Controller层的代码。当在子容器中查找Bean时,比如调用getBean方法会先在子容器里面查找,如果没有就会向父容器里面查找,直到查找到为止。
配置父子容器文件
新建子容器配置文件dispatcherServlet.xml(名字可以随意起,建议叫这个)
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"><context:component-scan base-package="com.lff.controller"/><mvc:annotation-driven /></beans>
注意此时扫描的包,确保只扫描SpringMVC的Bean,如Controller等
<context:component-scan base-package="com.lff.controller"/>
父容器配置文件还是applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"xmlns:aop="http://www.springframework.org/schema/aop" xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"><context:component-scan base-package="com.lff.service"/><!-- 数据源(Druid) 当然可以使用其的数据源--><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/customer_test"/><property name="username" value="root"/><property name="password" value="root"/></bean><!-- 创建SqlSessionFactory --><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><!-- 数据源 --><property name="dataSource" ref="dataSource"/><!-- 这个包底下的类会自动设置别名(一般是领域模型) --><property name="typeAliasesPackage" value="com.lff.domain"/><!-- 映射文件的位置 --><!-- <property name="mapperLocations">--><!-- <array>--><!-- <value>classpath:mappers/*.xml</value>--><!-- </array>--><!-- </property>--></bean><!-- 扫描dao,生成Dao的代理对象 --><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><!-- 设置SqlSessionFactoryBean的id --><property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/><!-- 设置dao的包 --><property name="basePackage" value="com.lff.dao"/></bean><!-- 事务管理器 --><bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><!-- 数据源 --><property name="dataSource" ref="dataSource" /></bean><!-- 附加代码:事务管理代码 --><tx:advice id="txAdvice" transaction-manager="transactionManager"><tx:attributes><tx:method name="*"/><tx:method name="list*" read-only="true"/><tx:method name="get*" read-only="true"/></tx:attributes></tx:advice><!-- 切面 --><aop:config><aop:pointcut id="pc" expression="within(com.mj.service..*)"/><aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/></aop:config></beans>
注意父容器扫描的是Service层,千万不要扫描Controller层,一般扫描非SpringMVC共用的Bean
<context:component-scan base-package="com.lff.service"/>
上面的MyBatis配置文件中,我们并没有告诉Mapper文件的位置
<!-- 创建SqlSessionFactory --><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><!-- 数据源 --><property name="dataSource" ref="dataSource"/><!-- 这个包底下的类会自动设置别名(一般是领域模型) --><property name="typeAliasesPackage" value="com.lff.domain"/><!-- 映射文件的位置 --><!-- <property name="mapperLocations">--><!-- <array>--><!-- <value>classpath:mappers/*.xml</value>--><!-- </array>--><!-- </property>--></bean>
因为我们可以把mapper文件放在和Dao相同的目录下,并且文件名称与Dao的类名保持一致。
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.lff.dao.CustomerDao"><!--查询所有的顾客--><select id="list" resultMap="customerDomain">SELECT * FROM Customers</select><resultMap id="customerDomain" type="com.lff.domain.Customer"><result property="custName" column="cust_name" /><result property="custCity" column="cust_city" /></resultMap><!-- 移除一个顾客--><delete id="remove" parameterType="int">DELETE FROM Customers WHERE cust_id = #{id}</delete><update id="update" parameterType="com.lff.domain.Customer">UPDATE Customers SET cust_name=#{custName},cust_address = #{custAddress},cust_city=#{custCity},cust_state=#{custState},cust_zip=#{custZip} where cust_id = #{custId}</update><insert id="save" parameterType="com.lff.domain.Customer">INSERT INTO Customers(cust_id,cust_name,cust_address,cust_city,cust_state,cust_zip) values (#{custId},#{custName},#{custAddress},#{custCity},#{custState},#{custZip})</insert></mapper>
当然还需要在Maven配置文件中加如下配置,因为Maven默认情况下是不会对我们的源代码文件里面的XML、Properties文件进行打包的
<!--构建信息比如插件配置等--><build><finalName>demo5</finalName><!-- 说明资源的位置(哪些东西算是资源) --><resources><!-- resources文件里面的内容要进行打包 --><resource><directory>src/main/resources</directory></resource><!-- java文件里面的内容要进行打包 --><resource><directory>src/main/java</directory><includes><include>**/*.properties</include><include>**/*.xml</include></includes></resource></resources></build>
加载父容器、子容器
在web.xml文件中进行加载父容器、子容器
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"version="4.0"><!-- 使用SpringMVC提供的DispatcherServlet进行处理拦截客户端的请求--><servlet><servlet-name>springmvc</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!-- 加载Spring的配置文件,配置文件里面配置要扫描的类等--><init-param><param-name>contextConfigLocation</param-name><!-- 加载子容器--><param-value>classpath:dispatcherServlet.xml</param-value></init-param><!-- 项目部署到服务器,就会创建Servlet --><load-on-startup>0</load-on-startup></servlet><servlet-mapping><servlet-name>springmvc</servlet-name><!-- 拦截客户端所有请求路径--><url-pattern>/</url-pattern></servlet-mapping><!-- 父容器的配置--><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext.xml</param-value></context-param><!-- 监听器监听到项目的部署就会加载上面的context-param,加载父容器applicationContext.xml--><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!-- 使用springMVC自带的过滤器处理post请求乱码问题--><filter><filter-name>characterEncodingFilter</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param></filter><filter-mapping><filter-name>characterEncodingFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping></web-app>
这样在请求控制器层就可以了。
纯注解的方式实现SSM整合
做好上面准备和依赖后(Dao层、Service层、Controller层、Maven依赖)的处理步骤如下
web.xml的配置可以删除
当项目启动后会加载web.xml文件,我们可以使用如下方式替代
package com.lff.config;import org.springframework.web.filter.CharacterEncodingFilter;import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;import javax.servlet.FilterRegistration;import javax.servlet.ServletContext;import javax.servlet.ServletException;public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {/*** 父容器的配置类*/@Overrideprotected Class<?>[] getRootConfigClasses() {return new Class[]{SpringConfig.class};}/*** 子容器的配置类(SpringMVC相关的配置类)*/@Overrideprotected Class<?>[] getServletConfigClasses() {return new Class[]{SpringMVCConfig.class};}/*** 配置DispatcherServlet的url-pattern*/@Overrideprotected String[] getServletMappings() {return new String[]{"/"};}@Overridepublic void onStartup(ServletContext servletContext) throws ServletException {super.onStartup(servletContext);// 添加FilterFilterRegistration.Dynamic encodingFilter = servletContext.addFilter("CharacterEncodingFilter", new CharacterEncodingFilter("UTF-8"));encodingFilter.addMappingForUrlPatterns(null, false, "/*");}}
项目部署的时候,就会先看有没有实现WebApplicationInitializer接口,如果实现这个接口就会实例化实现该接口的类,创建该类的对象,调用onStartup方法。而上面AbstractAnnotationConfigDispatcherServletInitializer类的父类实现了WebApplicationInitializer接口
public interface WebApplicationInitializer {void onStartup(ServletContext var1) throws ServletException;}
有些项目也使用如下方式设置,直接实现WebApplicationInitializer接口
@Overridepublic void onStartup(ServletContext servletContext) throws ServletException {// 父容器配置AnnotationConfigWebApplicationContext springCtx = new AnnotationConfigWebApplicationContext();springCtx.register(SpringConfig.class);// 通过监听器加载配置信息servletContext.addListener(new ContextLoaderListener(springCtx));// 子容器配置AnnotationConfigWebApplicationContext mvcCtx = new AnnotationConfigWebApplicationContext();mvcCtx.register(SpringMVCConfig.class);ServletRegistration.Dynamic servlet = servletContext.addServlet("DispatcherServlet",new DispatcherServlet(mvcCtx));servlet.setLoadOnStartup(0);servlet.addMapping("/");// filterFilterRegistration.Dynamic encodingFilter = servletContext.addFilter("CharacterEncodingFilter",new CharacterEncodingFilter("UTF-8"));encodingFilter.addMappingForUrlPatterns(null, false, "/*");}
没有必要使用这种方式,不够清晰直观,并且不好记忆。
父容器的配置
package com.lff.config;import com.alibaba.druid.pool.DruidDataSource;import org.mybatis.spring.SqlSessionFactoryBean;import org.mybatis.spring.annotation.MapperScan;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.PropertySource;import org.springframework.core.io.ClassPathResource;import org.springframework.core.io.support.PathMatchingResourcePatternResolver;import org.springframework.jdbc.datasource.DataSourceTransactionManager;import org.springframework.transaction.annotation.EnableTransactionManagement;import javax.sql.DataSource;@ComponentScan({"com.lff.service","com.lff.converter"})@MapperScan("com.lff.dao")@EnableTransactionManagementpublic class SpringConfig {//配置数据源@Beanpublic DataSource dataSource() {DruidDataSource ds = new DruidDataSource();ds.setDriverClassName("com.mysql.jdbc.Driver");ds.setUrl("jdbc:mysql://localhost:3306/customer_test");ds.setUsername("root");ds.setPassword("root");return ds;}//配置核心Mybatis核心工厂@Beanpublic SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();bean.setDataSource(dataSource);bean.setTypeAliasesPackage("com.lff.domain");//设置实体类别名PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();// bean.setMapperLocations(resolver.getResources("classpath:/mappers/*.xml"));//配置Mapper映射文件的路径// 如果有mybatis-config配置文件可以在这里设置,指定配置文件的位置// bean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));return bean;}//配置事务管理器,替换配置里面的DataSourceTransactionManager@Beanpublic DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource){DataSourceTransactionManager dm = new DataSourceTransactionManager();dm.setDataSource(dataSource);return dm;}}
子容器的配置
package com.lff.config;import com.lff.converter.DateConverter;import com.lff.interceptor.MyInterceptor;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.format.FormatterRegistry;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.http.converter.StringHttpMessageConverter;import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;import org.springframework.web.multipart.commons.CommonsMultipartResolver;import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;import org.springframework.web.servlet.config.annotation.EnableWebMvc;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import org.springframework.web.servlet.view.InternalResourceViewResolver;import java.nio.charset.StandardCharsets;import java.util.List;//思路:如果想配置一个东西又不知道怎么配置可以查看该类的父类都可以配置哪些东西@ComponentScan({"com.lff.controller", "com.lff.interceptor"})@EnableWebMvc //一定要加这个注解,不要问为什么,否则不好使public class SpringMVCConfig implements WebMvcConfigurer {@Autowiredprivate MyInterceptor myInterceptor;@Autowiredprivate DateConverter dateConverter;// 可以在这里设置路径前缀、后缀@Beanpublic InternalResourceViewResolver viewResolver() {InternalResourceViewResolver resolver = new InternalResourceViewResolver();resolver.setPrefix("/WEB-INF/page/");resolver.setSuffix(".jsp");return resolver;}// 文件上传配置@Beanpublic CommonsMultipartResolver multipartResolver() {CommonsMultipartResolver resolver = new CommonsMultipartResolver();resolver.setDefaultEncoding("UTF-8");return resolver;}// 如果需要拦截器,在这里添加拦截器配置@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(myInterceptor).addPathPatterns("/**").excludePathPatterns("/asset/**");}// 如果需要转换器,在这里配置,比如日期格式转换等@Overridepublic void addFormatters(FormatterRegistry registry) {registry.addConverter(dateConverter);}/*** 相当于<mvc:default-servlet-handler/>* 用以处理静态资源*/@Overridepublic void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {configurer.enable();}// 用于处理字符编码乱码问题@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {// 设置返回的普通字符串的编码StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();stringConverter.setDefaultCharset(StandardCharsets.UTF_8);converters.add(stringConverter);// 设置返回的JSON数据的编码MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();jsonConverter.setDefaultCharset(StandardCharsets.UTF_8);converters.add(jsonConverter);}}
上面如果不使用拦截器和转换器可以去掉,根据情况在配置文件里面添加内容。
当配置一项的时候,要想清楚是属于Service层的东西还是属于Controller层的东西,Service层的东西就在父容器里面配置,Controller层的东西就在子容器里面配置,如果都需要就配置在父容器里面。
