在構建springmvc+mybatis項目時,更常用的方式是采用web.xml來配置,而且一般情況下會在web.xml中使用ContextLoaderListener加載applicationContext-*.xml,在DispatcherServlet中配置${servlet-name}-servlet.xml。
但是實際上除了采用xml方式,在springmvc+mybatis項目中也可以采用純代碼+注解方式來替換web.xml、applicationContext-*.xml、${servlet-name}-servlet.xml。
下邊我們先展示下采用web.xml配置方式,后邊講解如何使用代碼+注解方式來替換的方案(所有代碼請參考:https://github.com/478632418/springmv_without_web_xml/tree/master/test-pro-02)。
SpringMVC+Mybatis采用web.xml整合
/WEB-INF/web.xml配置
在/WEB-INF/web.xml中一般需要配置兩個部分主要內容:ContextLoaderListener、DispatcherServlet。
一般web.xml配置信息如下:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1"> <!-- 加載spring容器 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 文件上傳與下載過濾器:form表單中存在文件時,該過濾器可以處理http請求中的文件,被該過濾器過濾后會用post方法提交,form表單需設為enctype="multipart/form-data"--> <!-- 注意:必須放在HiddenHttpMethodFilter過濾器之前 --> <filter> <filter-name>multipartFilter</filter-name> <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class> <init-param> <param-name>multipartResolverBeanName</param-name> <!--spring中配置的id為multipartResolver的解析器--> <param-value>multipartResolver</param-value> </init-param> </filter> <filter-mapping> <filter-name>multipartFilter</filter-name> <!--<servlet-name>springmvc</servlet-name>--> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 注意:HiddenHttpMethodFilter必須作用於dispatcher前 請求method支持 put 和 delete 必須添加該過濾器 作用:可以過濾所有請求,並可以分為四種 使用該過濾器需要在前端頁面加隱藏表單域 <input type="hidden" name="_method" value="請求方式(put/delete)"> post會尋找_method中的請求式是不是put 或者 delete,如果不是 則默認post請求 --> <filter> <filter-name>hiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>hiddenHttpMethodFilter</filter-name> <!--servlet為springMvc的servlet名 --> <servlet-name>springmvc</servlet-name> <!--可以通過配置覆蓋默認'_method'值 --> <init-param> <param-name>methodParam</param-name> <param-value>_method</param-value> </init-param> <!--<url-pattern>/*</url-pattern>--> </filter-mapping> <!--結束后端數據輸出到前端亂碼問題--> <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> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- springmvc前端控制器 --> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.js</url-pattern> </servlet-mapping> </web-app>
備注:
1)上邊關於spring的ApplicationContext配置有兩處:ContextLoaderListener、DispatcherServlet,一般理解為:ContextLoaderListener為spring的parent ApplicationContext,DispatcherServlet為spring的child ApplicationContext。
2)ContextLoaderListener與DispatcherServlet所創建的上下文ApplicationContext的區別:
- ContextLoaderListener中創建ApplicationContext主要用於整個Web應用程序需要共享的一些組件,比如DAO,數據庫的ConnectionFactory、multipartResolver等。而由DispatcherServlet創建的ApplicationContext主要用於和該Servlet相關的一些組件,比如Controller、ViewResovler等。
- 對於作用范圍而言,在DispatcherServlet中可以引用由ContextLoaderListener所創建的ApplicationContext,而反過來不行。
- 為什么multipartResolver需要配置在ContextLoaderListener的applicationContext.xml?具體可以查看MultipartFilter中的代碼:
public class MultipartFilter extends OncePerRequestFilter { /** * The default name for the multipart resolver bean. */ public static final String DEFAULT_MULTIPART_RESOLVER_BEAN_NAME = "filterMultipartResolver"; private final MultipartResolver defaultMultipartResolver = new StandardServletMultipartResolver(); private String multipartResolverBeanName = DEFAULT_MULTIPART_RESOLVER_BEAN_NAME; /** * Set the bean name of the MultipartResolver to fetch from Spring's * root application context. Default is "filterMultipartResolver". */ public void setMultipartResolverBeanName(String multipartResolverBeanName) { this.multipartResolverBeanName = multipartResolverBeanName; } /** * Return the bean name of the MultipartResolver to fetch from Spring's * root application context. */ protected String getMultipartResolverBeanName() { return this.multipartResolverBeanName; } /** * Check for a multipart request via this filter's MultipartResolver, * and wrap the original request with a MultipartHttpServletRequest if appropriate. * <p>All later elements in the filter chain, most importantly servlets, benefit * from proper parameter extraction in the multipart case, and are able to cast to * MultipartHttpServletRequest if they need to. */ @Override protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { MultipartResolver multipartResolver = lookupMultipartResolver(request); HttpServletRequest processedRequest = request; if (multipartResolver.isMultipart(processedRequest)) { if (logger.isTraceEnabled()) { logger.trace("Resolving multipart request"); } processedRequest = multipartResolver.resolveMultipart(processedRequest); } else { // A regular request... if (logger.isTraceEnabled()) { logger.trace("Not a multipart request"); } } try { filterChain.doFilter(processedRequest, response); } finally { if (processedRequest instanceof MultipartHttpServletRequest) { multipartResolver.cleanupMultipart((MultipartHttpServletRequest) processedRequest); } } } /** * Look up the MultipartResolver that this filter should use, * taking the current HTTP request as argument. * <p>The default implementation delegates to the {@code lookupMultipartResolver} * without arguments. * @return the MultipartResolver to use * @see #lookupMultipartResolver() */ protected MultipartResolver lookupMultipartResolver(HttpServletRequest request) { return lookupMultipartResolver(); } /** * Look for a MultipartResolver bean in the root web application context. * Supports a "multipartResolverBeanName" filter init param; the default * bean name is "filterMultipartResolver". * <p>This can be overridden to use a custom MultipartResolver instance, * for example if not using a Spring web application context. * @return the MultipartResolver instance */ protected MultipartResolver lookupMultipartResolver() { WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); String beanName = getMultipartResolverBeanName(); if (wac != null && wac.containsBean(beanName)) { if (logger.isDebugEnabled()) { logger.debug("Using MultipartResolver '" + beanName + "' for MultipartFilter"); } return wac.getBean(beanName, MultipartResolver.class); } else { return this.defaultMultipartResolver; } } }
在代碼中lookupMultipartResolver()方法:
protected MultipartResolver lookupMultipartResolver() { WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); String beanName = getMultipartResolverBeanName(); if (wac != null && wac.containsBean(beanName)) { if (logger.isDebugEnabled()) { logger.debug("Using MultipartResolver '" + beanName + "' for MultipartFilter"); } return wac.getBean(beanName, MultipartResolver.class); } else { return this.defaultMultipartResolver; } }
在lookupMultipartResovler()方法中,會調用WebApplicationContextUtils.getWebApplicationContext(getServletContext())---根據子上下文(DipatcherServlet的上下文)獲取根上下文(ContextLoaderListener的上下文)。如果返回的結果為空,或者返回山下文中未配置multipartResolver都會直接使用默認的defaultMultipartResovler。
3)web.xml中還配置三個filter:characterEncodingFilter(編碼解析)、hiddenHttpMethodFilter(實現支持put、delete提交)、multipartFilter(定制上傳文件的解析組件一般不指定默認使用:StandardServletMultipartResolver)。使用hiddenHttpMethodFilter+multipartFilter可以實現:使用put方式上傳文件。
MultipartResolver是一個為多文件上傳提供了解決方案的策略接口,將普通的HttpServletRequest封裝成MultipartHttpServletRequest,在Spring中常見的兩個實現方式,分別是:
1)StandardServletMultipartResolver:使用Servlet3.0標准上傳方式,將HttpServletRequest轉化為StandardServletMultipartResolver,以tomcat為例,從 Tomcat 7.0.x的版本開始就支持 Servlet 3.0了。
2)CommonsMultipartResolver:使用apache的common-fileupload,將HttpServletRequest轉化為DefaultMultipartHttpServletRequest(需要依賴common-fileupload.jar,common-io.jar)。
4)一般情況下ContextLoaderListener配置的xml文件名稱格式為:applicationContext-*.xml;DispatcherServlet配置的xml的文件名稱格式為:${servlet-name}-servlet.xml。
5)如果不配置ContextLoaderListener,這時是無法實現CommonMutilpartResolver+Put上傳文件;若只是在ContextLoaderListener(全局配置項)中配置,而沒有配置DispatcherServlet ,那么aop會無效。
applicationContext-*.xml配置
1)一般情況下,applicationContext-*.xml中會配置DAO,數據庫的ConnectionFactory、multipartResolver等。
2)該配置文件在web.xml中由ContextLoaderListener來加載。
applicationContext.xml示例:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd "> <!--掃描Service里面的注解--> <context:component-scan base-package="com.dx.test.service"></context:component-scan> <!-- 文件上傳注意id --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 配置默認編碼 --> <property name="defaultEncoding" value="utf-8"></property> <!-- 配置文件上傳的大小 --> <property name="maxUploadSize" value="1048576"></property> </bean> <!-- 數據庫連接池配置文件Dao層 --> <!-- 加載配置文件 --> <context:property-placeholder location="classpath:db.properties"/> <!-- 數據庫連接池,使用dbcp --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <property name="maxActive" value="10"/> <property name="maxIdle" value="5"/> </bean> <!-- SqlSessionFactory配置 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <!-- 加載mybatis的全局配置文件 --> <property name="configLocation" value="classpath:sqlMapConfig.xml"/> <!-- 自動掃描mapping.xml文件,**表示迭代查找,也可在sqlMapConfig.xml中單獨指定xml文件--> <property name="mapperLocations" value="classpath:com/dx/test/**/*.xml" /> </bean> <!-- mapper掃描器 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 掃描包路徑,如果需要掃描多個包,中間使用半角逗號隔開 --> <property name="basePackage" value="com.Dao"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean> <!-- 事務管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> </beans>
其中db.properties是配置的數據庫連接信息,比如:
jdbc.url=jdbc\:oracle\:thin\:@localhost\:8080\:orcl
jdbc.username=admin
jdbc.password=pass123
${servlet-name}-servlet.xml配置
1)一般情況下,${servlet-name}-servlet.xml中會配置:由DispatcherServlet創建的ApplicationContext主要用於和該Servlet相關的一些組件,比如Controller、ViewResovler等。
2)該配置文件在web.xml中由DipatcherServlet來加載。
springmvc-servlet.xml示例:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd "> <!-- 開啟controller注解支持 --> <!-- 注意事項請參考:http://jinnianshilongnian.iteye.com/blog/1762632 --> <context:component-scan base-package="com.Controller" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" /> <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" /> </context:component-scan> <!--使用mvc:annotation-driven代替上邊注解映射器和注解適配器 配置 如果使用mvc:annotation-driven就不用配置上面的 RequestMappingHandlerMapping和RequestMappingHandlerAdapter--> <mvc:annotation-driven></mvc:annotation-driven> <!-- 單獨使用jsp視圖解析器時,可以取消掉注釋,同時注釋掉:下邊的‘配置多個視圖解析’配置--> <!-- <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/view/"/> <property name="suffix" value=".jsp"/> </bean> --> <!-- 使用thymeleaf解析 --> <bean id="templateResolver" class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver"> <property name="prefix" value="/WEB-INF/templates/" /> <!--<property name="suffix" value=".html" />--> <property name="templateMode" value="HTML" /> <property name="characterEncoding" value="UTF-8"/> <property name="cacheable" value="false" /> </bean> <bean id="templateEngine" class="org.thymeleaf.spring5.SpringTemplateEngine"> <property name="templateResolver" ref="templateResolver" /> </bean> <!--單獨使用thymeleaf視圖引擎時,可以取消掉注釋,同時注釋掉:下邊的‘配置多個視圖解析’配置 --> <!-- <bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver"> <property name="templateEngine" ref="templateEngine" /> <property name="characterEncoding" value="UTF-8"/> </bean> --> <!-- 配置多個視圖解析 參考:https://blog.csdn.net/qq_19408473/article/details/71214972--> <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"> <property name="viewResolvers"> <!-- 此時, 返回視圖:return "abc.jsp" ,將使用jsp視圖解析器,jsp的視圖模板文件在/WEB-INF/views/下; 返回視圖:return "abc.html",將使用 thymeleaf視圖解析器,thymeleaf的視圖模板文件在/WEB-INF/templates/下。 --> <list> <!--used thymeleaf --> <bean class="org.thymeleaf.spring3.view.ThymeleafViewResolver"> <property name="characterEncoding" value="UTF-8"/> <property name="templateEngine" ref="templateEngine" /> <property name="viewNames" value="*.html"/> <property name="order" value="2" /> </bean> <!-- used jsp --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"/> <!--<property name="suffix" value=".jsp"/>--> <property name="viewNames" value="*.jsp"/> <property name="order" value="1" /> </bean> </list> </property> </bean> </beans>
注意:
1)上邊配置文件包含了多視圖解析器,支持jsp、thymeleaf兩種視圖解析器,關於多視圖解析器配置以及使用方式請參考《springmvc 同時配置thymeleaf和jsp兩種模板引擎》
2)實際上可以單獨配置他們任意兩種方式。
SpringMVC+Mybatis使用代碼+注解方式整合
整體項目結構:
pom.xml如下:

<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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.dx.test</groupId> <artifactId>test-pro</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>test-pro Maven Webapp</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <org.springframework.version>5.2.0.RELEASE</org.springframework.version> <com.alibaba.version>1.1.21</com.alibaba.version> <mysql.version>8.0.11</mysql.version> <org.mybatis.version>3.4.6</org.mybatis.version> <org.mybatis.spring.version>2.0.3</org.mybatis.spring.version> <org.aspectj.version>1.9.4</org.aspectj.version> <jackson.version>2.9.10.1</jackson.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${org.springframework.version}</version> </dependency> <!--AOP aspectjweaver 支持 --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>${org.aspectj.version}</version> </dependency> <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>${org.aspectj.version}</version> </dependency> <!-- https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf --> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf</artifactId> <version>3.0.9.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf-spring5 --> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring5</artifactId> <version>3.0.9.RELEASE</version> </dependency> <!--訪問RDBMS-MySQL依賴 --> <!--MyBatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${org.mybatis.version}</version> </dependency> <!-- Mybatis自身實現的Spring整合依賴 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>${org.mybatis.spring.version}</version> </dependency> <!--MySql數據庫驅動 --> <!-- https://mvnrepository.com/artifact/com.alibaba/druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${com.alibaba.version}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <!--Rest Support支持 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> <version>${jackson.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.module</groupId> <artifactId>jackson-module-parameter-names</artifactId> <version>${jackson.version}</version> </dependency> <!--form 設置為enctype="multipart/form-data",多文件上傳,在applicationContext.xml中配置了bean multipartResolver時,需要依賴該包。 --> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.5</version> </dependency> <!-- 編譯依賴 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!--日志支持 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.26</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.26</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies> <build> <finalName>test-pro</finalName> </build> </project>
替代web.xml的WebAppInit.java
public class WebAppInit extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[] { BeanFactoryConfig.class }; } @Override protected Class<?>[] getServletConfigClasses() { return new Class[] { WebMvcConfig.class }; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } /** * 設置Multipart具體細節(必須) <br> * 指定文件存放的臨時路徑 <br> * 上傳文件最大容量 <br> * 整個請求的最大容量 <br> * 0表示將所有上傳的文件寫入到磁盤中 <br> */ @Override protected void customizeRegistration(ServletRegistration.Dynamic registration) { registration.setMultipartConfig(new MultipartConfigElement("/Users/dz/temp", 20971520, 41943040, 0)); } /** * 配置其他的 servlet 和 filter*/ @Override public void onStartup(ServletContext servletContext) throws ServletException { FilterRegistration.Dynamic encodingFilter = servletContext.addFilter("encodingFilter", CharacterEncodingFilter.class); encodingFilter.setInitParameter("encoding", String.valueOf(StandardCharsets.UTF_8)); encodingFilter.setInitParameter("forceEncoding", "true"); encodingFilter.addMappingForUrlPatterns(null, false, "/*"); FilterRegistration.Dynamic httpMethodFilter = servletContext.addFilter("hiddenHttpMethodFilter", HiddenHttpMethodFilter.class); httpMethodFilter.setInitParameter("method", "_method"); httpMethodFilter.addMappingForUrlPatterns(null, false, "/*"); FilterRegistration.Dynamic multipartFilter = servletContext.addFilter("multipartFilter", MultipartFilter.class); multipartFilter.setInitParameter("multipartResolverBeanName", "multipartResolver"); multipartFilter.addMappingForUrlPatterns(null, false, "/*"); super.onStartup(servletContext); } }
備注:
1)getServletConfigClasses() 方法相當於web.xml中配置applicationContext-*.xml;
2)getServletMappings()方法相當於web.xml中配置${servlet-name}-servlet.xml。
3)customizeRegistration(..)中注冊MutilPartConfigElement()是為了解決上傳文件問題,否則上傳文件會拋出異常:registration.setMultipartConfig(new MultipartConfigElement("/Users/dz/temp", 20971520, 41943040, 0));
4)另外在該配置文件中還注冊了三個filter:multipartResolver、hiddenHttpMethodFilter、ecodingFilter。
替代applicationContext-*.xml的BeanFactoryConfig.java
@Configuration @ComponentScan(basePackages = { "com.dx.test.service", "com.dx.test.properties" }) @MapperScans(value = { @MapperScan("com.dx.test.dao") }) @EnableTransactionManagement public class BeanFactoryConfig { @Autowired private JdbcConfig jdbcConfig; @Bean public DataSource dataSource() { DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setUrl(jdbcConfig.getUrl()); druidDataSource.setUsername(jdbcConfig.getUsername()); druidDataSource.setPassword(jdbcConfig.getPassword()); druidDataSource.setMaxActive(jdbcConfig.getPoolMaxActive()); druidDataSource.setInitialSize(jdbcConfig.getPoolInit()); druidDataSource.setMinIdle(jdbcConfig.getPoolMinIdle()); return druidDataSource; } /** * mybatis plugin 打印執行sql * */ @Bean public SqlStatementInterceptor sqlStatementInterceptor() { return new SqlStatementInterceptor(); } @Bean public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) throws IOException { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSource); sqlSessionFactoryBean.setTypeAliasesPackage("com.dx.test.model"); // 添加mybatis plugin實現打印執行sql sqlSessionFactoryBean.setPlugins(sqlStatementInterceptor()); // 動態獲取SqlMapper PathMatchingResourcePatternResolver pathResource = new PathMatchingResourcePatternResolver(); sqlSessionFactoryBean.setMapperLocations(pathResource.getResources("classpath*:/mappers/*.xml")); return sqlSessionFactoryBean; } @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; } // 配置MultipartResolver 解析器 @Bean public MultipartResolver multipartResolver() { CommonsMultipartResolver multipartRe = new CommonsMultipartResolver(); multipartRe.setMaxUploadSize(1024000000); multipartRe.setResolveLazily(true); multipartRe.setMaxInMemorySize(4096); multipartRe.setDefaultEncoding("UTF-8");// 設置默認字符集 // try { // multipartRe.setUploadTempDir(new FileSystemResource("/tmp/spittr/uploads")); // } catch (IOException e) { // e.printStackTrace(); // } return multipartRe; } }
備注:
上邊代碼一共配置了一下信息:
1)MultipartResolver bean配置,在web.xml中添加的 multipartFilter 會依賴該配置;
2)SqlSessionFactoryBean bean配置,mybatis連接數據庫使用,並且該bean內部配置mybatis的配置:dataSource/typeAliasesPackage/puglins/mybatis的mapper配置文件路徑:classpath*:/mappers/*.xml,注意:plugins下注入了bean(SqlStatementInterceptor是一個mybatis的plugin),其他代碼:

/** * 數據庫操作性能攔截器,記錄耗時 * 來自:https://blog.csdn.net/sys1159378858/article/details/84665653 */ @Intercepts(value = { @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }), @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class }), @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }) }) public class SqlStatementInterceptor implements Interceptor { private static Logger logger = Logger.getLogger(SqlStatementInterceptor.class); private Properties properties; @Override public Object intercept(Invocation invocation) throws Throwable { Object returnValue; long start = System.currentTimeMillis(); returnValue = invocation.proceed(); long end = System.currentTimeMillis(); long time = end - start; try { final Object[] args = invocation.getArgs(); // 獲取原始的ms MappedStatement ms = (MappedStatement) args[0]; Object parameter = null; // 獲取參數,if語句成立,表示sql語句有參數,參數格式是map形式 if (invocation.getArgs().length > 1) { parameter = invocation.getArgs()[1]; } String sqlId = ms.getId();// 獲取到節點的id,即sql語句的id BoundSql boundSql = ms.getBoundSql(parameter); // BoundSql就是封裝myBatis最終產生的sql類 Configuration configuration = ms.getConfiguration(); // 獲取節點的配置 String sql = getSql(configuration, boundSql, sqlId, time); // 獲取到最終的sql語句 logger.error(sql); logger.warn(sql); System.out.println(sql); } catch (Exception e) { logger.error("攔截sql處理出差" + e.getMessage()); e.printStackTrace(); } return returnValue; } // 封裝了一下sql語句,使得結果返回完整xml路徑下的sql語句節點id + sql語句 public static String getSql(Configuration configuration, BoundSql boundSql, String sqlId, long time) { String sql = showSql(configuration, boundSql); StringBuilder str = new StringBuilder(100); str.append("============================begin【sql】================================\n"); str.append(sqlId); str.append(":耗時【"); str.append(time); str.append("】毫秒"); str.append(sql); str.append("\n============================end【sql】================================"); return str.toString(); } /* <br> *如果參數是String,則添加單引號, 如果是日期,則轉換為時間格式器並加單引號; 對參數是null和不是null的情況作了處理<br> */ private static String getParameterValue(Object obj) { String value = null; if (obj instanceof String) { value = "'" + obj.toString() + "'"; } else if (obj instanceof Date) { DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA); value = "'" + formatter.format(new Date()) + "'"; } else { if (obj != null) { value = obj.toString(); } else { value = ""; } } return value; } // 進行?的替換 public static String showSql(Configuration configuration, BoundSql boundSql) { Object parameterObject = boundSql.getParameterObject(); // 獲取參數 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); String sql = boundSql.getSql().replaceAll("[\\s]+", " "); // sql語句中多個空格都用一個空格代替 if (!CollectionUtils.isEmpty(parameterMappings) && parameterObject != null) { TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); // 獲取類型處理器注冊器,類型處理器的功能是進行java類型和數據庫類型的轉換<br> // // // 如果根據parameterObject.getClass()可以找到對應的類型,則替換 if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(parameterObject))); } else { MetaObject metaObject = configuration.newMetaObject(parameterObject);// MetaObject主要是封裝了originalObject對象,提供了get和set的方法用於獲取和設置originalObject的屬性值,主要支持對JavaBean、Collection、Map三種類型對象的操作 for (ParameterMapping parameterMapping : parameterMappings) { String propertyName = parameterMapping.getProperty(); if (metaObject.hasGetter(propertyName)) { Object obj = metaObject.getValue(propertyName); sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj))); } else if (boundSql.hasAdditionalParameter(propertyName)) { Object obj = boundSql.getAdditionalParameter(propertyName); // 該分支是動態sql sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj))); } else { sql = sql.replaceFirst("\\?", "缺失"); } // 打印出缺失,提醒該參數缺失並防止錯位 } } } return sql; } @Override public Object plugin(Object arg0) { return Plugin.wrap(arg0, this); } @Override public void setProperties(Properties arg0) { this.properties = arg0; } }
更多信息請查閱:《SpringMVC打印完整SQL並記錄SQL耗時》。
3)transactionManager,事務管理器;
4)JdbcConfig是注入的bean對象,用來加載jdbc.properties配置文件內容。具體代碼:

@Component @Configuration @PropertySource(value = {"classpath:jdbc.properties"}) public class JdbcConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Value("${jdbc.pool.init}") private int poolInit; @Value("${jdbc.pool.minIdle}") private int poolMinIdle; @Value("${jdbc.pool.maxActive}") private int poolMaxActive; @Value("${jdbc.testSql}") private String testSql; public String getDriver() { return driver; } public String getUrl() { return url; } public String getUsername() { return username; } public String getPassword() { return password; } public int getPoolInit() { return poolInit; } public int getPoolMinIdle() { return poolMinIdle; } public int getPoolMaxActive() { return poolMaxActive; } public String getTestSql() { return testSql; } /** * <!-- 數據庫配置文件位置 --> * <context:property-placeholder location="classpath:jdbc.properties" /> * 更多信息請參考:https://blog.csdn.net/wrs120/article/details/84554366 * */ // @Bean // public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { // return new PropertySourcesPlaceholderConfigurer(); // } }
/src/main/resources/jdbc.properties:

#jdbc settings jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false jdbc.username=root jdbc.password=123456 #pool settings jdbc.pool.init=1 jdbc.pool.minIdle=3 jdbc.pool.maxActive=20 #jdbc.testSql=SELECT 'x' jdbc.testSql=SELECT 'x' FROM DUAL
5)類上通過注解方式指定了mybatis的model類,service;
6)類上通過@EnableTransactionManagement開啟了事務管理。
替代${serlvet-name}-servlet.xml的WebMvcConfig.java
@Configuration @EnableWebMvc @ComponentScans(value = { @ComponentScan("com.dx.test.controller"), @ComponentScan("com.dx.test.aspect") }) @EnableAspectJAutoProxy(proxyTargetClass = true) public class WebMvcConfig implements WebMvcConfigurer { // https://blog.csdn.net/tianjun2012/article/details/47809739 @Bean public LoggerAspect loggerAspect() { return new LoggerAspect(); } // 日期輸出格式化 @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder().indentOutput(true) .dateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).modulesToInstall(new ParameterNamesModule()); converters.add(new MappingJackson2HttpMessageConverter(builder.build())); converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build())); } // 解決靜態資源加載問題 @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**").addResourceLocations("/static/").setCachePeriod(3000); } // 接收格式控制 @Override public void addFormatters(FormatterRegistry registry) { registry.addFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss")); } /** * 下面三個bean 配置 Thymeleaf 模板 */ @Bean public SpringResourceTemplateResolver templateResolver() { SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver(); templateResolver.setPrefix("/WEB-INF/templates/"); //templateResolver.setSuffix(".html"); templateResolver.setTemplateMode(TemplateMode.HTML); templateResolver.setCharacterEncoding(String.valueOf(StandardCharsets.UTF_8)); templateResolver.setCacheable(false); return templateResolver; } @Bean public TemplateEngine templateEngine(SpringResourceTemplateResolver templateResolver) { SpringTemplateEngine templateEngine = new SpringTemplateEngine(); templateEngine.setTemplateResolver(templateResolver); return templateEngine; } //@Bean public ViewResolver viewResolver(TemplateEngine templateEngine) { ThymeleafViewResolver viewResolver = new ThymeleafViewResolver(); viewResolver.setTemplateEngine((ISpringTemplateEngine) templateEngine); viewResolver.setCharacterEncoding(String.valueOf(StandardCharsets.UTF_8)); viewResolver.setViewNames(new String[] {"*.html"}); viewResolver.setOrder(1); return viewResolver; } // 配置jsp 視圖解析器 //@Bean public ViewResolver viewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix("/WEB-INF/views/");// 設置預加載路徑/目錄 //resolver.setSuffix(".jsp"); // 設置允許后綴 //返回時也自動匹配到該后綴 resolver.setExposeContextBeansAsAttributes(true); resolver.setViewNames("*.jsp"); resolver.setOrder(2); return resolver; } // 參考:https://blog.csdn.net/qq_19408473/article/details/71214972 @Bean public ContentNegotiatingViewResolver viewResolvers(){ ContentNegotiatingViewResolver contentNegotiatingViewResolver=new ContentNegotiatingViewResolver(); List<ViewResolver> viewResolverList=new ArrayList<ViewResolver>(); // <!--used thymeleaf --> viewResolverList.add(viewResolver(templateEngine(templateResolver()))); // <!-- used jsp --> viewResolverList.add(viewResolver()); contentNegotiatingViewResolver.setViewResolvers(viewResolverList); return contentNegotiatingViewResolver; } }
注意:
1)@EnableAspectJAutoProxy(proxyTargetClass = true)+bean loggerAspect()實現注入AspectJ,LoggerAspect用來攔截各個controller的方法,在方法調用前后打印參數信息:

// 來自:https://www.cnblogs.com/qinglangyijiu/p/8425653.html /** * 切面通知包含:<br> * 前置通知(@Before,在執行方法之前,參數為JoinPoint)<br> * 后置通知(@After,無論方法拋不拋異常都會執行,所以獲取不到方法的返回值。參數為JoinPoint)<br> * 返回通知(@AfterReturning,在方法正常結束后執行,可以獲取到方法的返回值。參數為JoinPoint和result(Object))<br> * 異常通知(@AfterThrowing,在方法拋出異常時執行,可以獲取到異常對象,且可以指定在出現特定異常時執行的代碼,參數為JoinPoint何Exception)<br> * 環繞通知(@Around,環繞通知需要攜帶的類型為ProceedingJoinPoint類型的參數, * 環繞通知包含前置、后置、返回、異常通知;ProceedingJoinPoin * 類型的參數可以決定是否執行目標方法,且環繞通知必須有返回值,返回值即目標方法的返回值)<br> */ @Aspect // 該標簽把LoggerAspect類聲明為一個切面 @Order(1) // 設置切面的優先級:如果有多個切面,可通過設置優先級控制切面的執行順序(數值越小,優先級越高) @Component // 該標簽把LoggerAspect類放到IOC容器中 public class LoggerAspect { /** * 定義一個方法,用於聲明切入點表達式,方法中一般不需要添加其他代碼 使用@Pointcut聲明切入點表達式 * 后面的通知直接使用方法名來引用當前的切點表達式;如果是其他類使用,加上包名即可 */ @Pointcut("execution(public * com.dx.test.controller.*Controller.*(..))") //@Pointcut("execution(* com.dx.test.controller.*.*(..))") public void declearJoinPointExpression() { } /** * 前置通知 * * @param joinPoint */ @Before("declearJoinPointExpression()") // 該標簽聲明次方法是一個前置通知:在目標方法開始之前執行 public void beforMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); List<Object> args = Arrays.asList(joinPoint.getArgs()); System.out.println("this method " + methodName + " begin. param<" + args + ">"); } /** * 后置通知(無論方法是否發生異常都會執行,所以訪問不到方法的返回值) * * @param joinPoint */ @After("declearJoinPointExpression()") public void afterMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); System.out.println("this method " + methodName + " end."); } /** * 返回通知(在方法正常結束執行的代碼) 返回通知可以訪問到方法的返回值! * * @param joinPoint */ @AfterReturning(value = "declearJoinPointExpression()", returning = "result") public void afterReturnMethod(JoinPoint joinPoint, Object result) { String methodName = joinPoint.getSignature().getName(); System.out.println("this method " + methodName + " end.result<" + result + ">"); } /** * 異常通知(方法發生異常執行的代碼) 可以訪問到異常對象;且可以指定在出現特定異常時執行的代碼 * * @param joinPoint * @param ex */ @AfterThrowing(value = "declearJoinPointExpression()", throwing = "ex") public void afterThrowingMethod(JoinPoint joinPoint, Exception ex) { String methodName = joinPoint.getSignature().getName(); System.out.println("this method " + methodName + " end.ex message<" + ex + ">"); } /** * 環繞通知(需要攜帶類型為ProceedingJoinPoint類型的參數) 環繞通知包含前置、后置、返回、異常通知;ProceedingJoinPoin * 類型的參數可以決定是否執行目標方法 且環繞通知必須有返回值,返回值即目標方法的返回值 * * @param point */ @Around(value = "declearJoinPointExpression()") public Object aroundMethod(ProceedingJoinPoint point) { Object result = null; String methodName = point.getSignature().getName(); try { // 前置通知 System.out.println("The method " + methodName + " start. param<" + Arrays.asList(point.getArgs()) + ">"); // 執行目標方法 result = point.proceed(); // 返回通知 System.out.println("The method " + methodName + " end. result<" + result + ">"); } catch (Throwable e) { // 異常通知 System.out.println("this method " + methodName + " end.ex message<" + e + ">"); throw new RuntimeException(e); } // 后置通知 System.out.println("The method " + methodName + " end."); return result; } }
具體請參考:《springmvc配置AOP的兩種方式》
2)@EnableSpringMvc:注解的類自身來實現WebMvcConfigurer接口,然后在該類中重寫需要覆蓋的默認配置所對應的方法或者添加相關配置。
3)@Configuration:用於定義配置類,可替換xml配置文件,被注解的類內部包含有一個或多個被@Bean注解的方法,這些方法將會被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext類進行掃描,並用於構建bean定義,初始化Spring容器。
4)@ComponentScans用來自動掃描加載bean,這里自動掃描的aspect類所在包和controller包。
5)bean viewResolvers()中注解了兩個視圖解析器:jsp、thymeleaf。注解中隱含的意思:
5.1)當訪問controller方法中返回視圖的名字后綴是*.jsp時,會去/WEB-INF/views下加載解析該視圖x.jsp。
5.2)當訪問controller方法中返回視圖的名字后綴是*.html時,會去/WEB-INF/templates下加載解析該視圖x.html。
對於SpringMVC+Mybatis整合應用測試:
初始化數據庫:
新建mydb的mysql庫,新建表:
CREATE TABLE `log` ( `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '自增id', `title` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '日志標題', `content` text CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT '日志內容', `module_type` varchar(32) NOT NULL COMMENT '模塊類型', `operate_type` varchar(32) NOT NULL COMMENT '操作類型', `data_id` varchar(64) NOT NULL COMMENT '操作數據記錄id', `create_time` datetime NOT NULL COMMENT '日志記錄時間', `create_user` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '操作人', `create_user_id` varchar(64) NOT NULL COMMENT '操作人id', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=104 DEFAULT CHARSET=utf8
添加model/dao/service/controller/視圖文件/配置web.xml
1)添加model
ModuleType.java

public enum ModuleType { Unkown(0), /** * 文章模塊 */ Article_Module(1), /** * 文章分類模塊 **/ Article_Category_Module(2), /** * 配置模塊 */ Settings_Module(3); private int value; ModuleType(int value) { this.value = value; } public int getValue() { return this.value; } }
OperateType.java

public enum OperateType { /** * 如果0未占位,可能會出現錯誤。 * */ Unkown(0), /** * 新增 */ Create(1), /** * 修改 */ Modify(2), /** * 刪除 */ Delete(3), /** * 查看 */ View(4), /** * 作廢 */ UnUsed(5); private int value; OperateType(int value) { this.value = value; } public int getValue() { return this.value; } }
Log.java

public class Log { private Long id; // 自增id private String title;// 日志msg private ModuleType moduleType;// 日志歸屬模塊 private OperateType operateType; // 日志操作類型 private String dataId; // 操作數據id private String content; // 日志內容簡介 private Date createTime; // 新增時間 private String createUser; // 新增人 private String createUserId; // 新增人id public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public ModuleType getModuleType() { return moduleType; } public void setModuleType(ModuleType moduleType) { this.moduleType = moduleType; } public OperateType getOperateType() { return operateType; } public void setOperateType(OperateType operateType) { this.operateType = operateType; } public String getDataId() { return dataId; } public void setDataId(String dataId) { this.dataId = dataId; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } public String getCreateUser() { return createUser; } public void setCreateUser(String createUser) { this.createUser = createUser; } public String getCreateUserId() { return createUserId; } public void setCreateUserId(String createUserId) { this.createUserId = createUserId; } @Override public String toString() { return "Log [id=" + id + ", title=" + title + ", moduleType=" + moduleType + ", operateType=" + operateType + ", dataId=" + dataId + ", content=" + content + ", createTime=" + createTime + ", createUser=" + createUser + ", createUserId=" + createUserId + "]"; } }
ArticleModel.java

/** * 文章內容 */ public class ArticleModel implements Serializable { private static final long serialVersionUID = 1L; private Long id; private Long categoryId; private String title; private String content; private Date createTime; private String createUser; private String createUserId; private Date modifyTime; private String modifyUser; private String modifyUserId; public ArticleModel() { } public ArticleModel(Long id, Long categoryId, String title, String content) { this.id = id; this.categoryId = categoryId; this.title = title; this.content = content; } /** * @return the id */ public Long getId() { return id; } /** * @param id the id to set */ public void setId(Long id) { this.id = id; } /** * @return the categoryId */ public Long getCategoryId() { return categoryId; } /** * @param categoryId the categoryId to set */ public void setCategoryId(Long categoryId) { this.categoryId = categoryId; } /** * @return the title */ public String getTitle() { return title; } /** * @param title the title to set */ public void setTitle(String title) { this.title = title; } /** * @return the content */ public String getContent() { return content; } /** * @param content the content to set */ public void setContent(String content) { this.content = content; } /** * @return the createTime */ public Date getCreateTime() { return createTime; } /** * @param createTime the createTime to set */ public void setCreateTime(Date createTime) { this.createTime = createTime; } /** * @return the createUser */ public String getCreateUser() { return createUser; } /** * @param createUser the createUser to set */ public void setCreateUser(String createUser) { this.createUser = createUser; } /** * @return the createUserId */ public String getCreateUserId() { return createUserId; } /** * @param createUserId the createUserId to set */ public void setCreateUserId(String createUserId) { this.createUserId = createUserId; } /** * @return the modifyTime */ public Date getModifyTime() { return modifyTime; } /** * @param modifyTime the modifyTime to set */ public void setModifyTime(Date modifyTime) { this.modifyTime = modifyTime; } /** * @return the modifyUser */ public String getModifyUser() { return modifyUser; } /** * @param modifyUser the modifyUser to set */ public void setModifyUser(String modifyUser) { this.modifyUser = modifyUser; } /** * @return the modifyUserId */ public String getModifyUserId() { return modifyUserId; } /** * @param modifyUserId the modifyUserId to set */ public void setModifyUserId(String modifyUserId) { this.modifyUserId = modifyUserId; } @Override public String toString() { return "ArticleModel{" + "id=" + id + ", categoryId=" + categoryId + ", title='" + title + '\'' + ", content='" + content + '\'' + '}'; } }
2)添加dao
LogSqlProvider.java

public class LogSqlProvider { /** * 生成插入日志SQL * @param log 日志實體 * @return 插入日志SQL * */ public String insert(Log log) { return new SQL() { { INSERT_INTO("log"); INTO_COLUMNS("title", "module_type", "operate_type","data_id", "content", "create_time","create_user","create_user_id"); INTO_VALUES("#{title}", "#{moduleType,typeHandler=org.apache.ibatis.type.EnumOrdinalTypeHandler}", "#{operateType,typeHandler=org.apache.ibatis.type.EnumOrdinalTypeHandler}","#{dataId}", "#{content}", "now()","#{createUser}","#{createUserId}"); } }.toString(); } }
LogMapper.java
@Mapper public interface LogMapper { /** * 入庫日志 * * @param log 待入庫實體 * @return 影響條數 */ @Options(useCache = true, flushCache = Options.FlushCachePolicy.TRUE, useGeneratedKeys = true, keyProperty = "id", keyColumn = "id") @InsertProvider(type = LogSqlProvider.class, method = "insert") public int insert(Log log); /** * 根據文章id,查詢日志詳情 * * @param id 日志id * @return 返回查詢到的日志詳情 */ @Options(useCache = true, flushCache = Options.FlushCachePolicy.FALSE, timeout = 60000) @Results(id = "logResult", value = { @Result(property = "id", column = "id", id = true), @Result(property = "title", column = "title"), @Result(property = "content", column = "content"), @Result(property = "moduleType", column = "module_type", javaType = ModuleType.class,typeHandler = EnumOrdinalTypeHandler.class), @Result(property = "operateType", column = "operate_type", javaType = OperateType.class,typeHandler = EnumOrdinalTypeHandler.class), @Result(property = "dataId", column = "data_id"), @Result(property = "createUser", column = "create_user"), @Result(property = "createUserId", column = "create_user_id"), @Result(property = "createTime", column = "create_time") }) @Select({ "select * from `log` where `id`=#{id}" }) Log getById(@Param("id") Long id); @Options(useCache = true, flushCache = Options.FlushCachePolicy.FALSE, timeout = 60000) @ResultMap("logResult") @Select({ "select * from `log` " }) List<Log> getList(); @Options(useCache = true, flushCache = Options.FlushCachePolicy.FALSE, timeout = 60000) @ResultMap("logResult") @Select({ "select * from `log` where `id`<#{log.id}" }) List<Log> getListWithPager(@Param("log")Log log,@Param("pageNum") int pageNum,@Param("pageSize") int pageSize); }
3)添加service
LogService.java
@Service public class LogService { @Autowired private LogMapper logMapper; public int insert(Log log) { return this.logMapper.insert(log); } public Log getById(Long id) { return this.logMapper.getById(id); } public List<Log> getList() { return this.logMapper.getList(); } public List<Log> getListWithPager(Log log, int pageNum, int pageSize) { return this.logMapper.getListWithPager(log, pageNum, pageSize); } }
4)添加controller
HomeController.java
@Controller public class HomeController { @Autowired private LogService logService; /** * 測試:走 Jsp 視圖引擎,去/WEB-INF/views/下找 index.jsp 視圖 * */ @RequestMapping(value = "/", method = RequestMethod.GET) public String def() { return "index.jsp"; } /** * 測試:走 Jsp 視圖引擎,去/WEB-INF/views/下找 index.jsp 視圖 * */ @RequestMapping(value = "/index", method = RequestMethod.GET) public String index() { return "index.jsp"; } /** * 測試:走 Thymeleaf 視圖引擎 * */ @RequestMapping(value="/log-list",method = RequestMethod.GET) public ModelAndView logList(Map<String, Object> map) { ModelAndView mView=new ModelAndView(); mView.setViewName("log-list.html"); mView.addObject("logs", logService.getList()); return mView; } }
FileController.java
@Controller public class FileController { /** * 測試:走 Jsp 視圖引擎,去/WEB-INF/views/下找 index.jsp 視圖 * */ @RequestMapping(value = "/upload", method = RequestMethod.GET) public String upload() { return "file/upload.jsp"; } /** * 測試:走 Jsp 視圖引擎,去/WEB-INF/views/下找 index.jsp 視圖 * */ // 參考:https://blog.csdn.net/qq_27607965/article/details/80332467 @RequestMapping(value = "/update_with_put_file", method = RequestMethod.PUT) //@ResponseBody public String update_with_put_file(@ModelAttribute(value = "article") ArticleModel article, HttpServletRequest request) throws IOException, ServletException { System.out.println(article); Collection<Part> parts = request.getParts(); for (Part part : parts) { System.out.println(part.getName() + "->" + part.getContentType()); } String id = request.getParameter("id"); String title = request.getParameter("title"); String content = request.getParameter("content"); System.out.println(String.format("%s,%s,%s", id, title, content)); return "redirect:/index"; } /** * 測試:走 Jsp 視圖引擎,去/WEB-INF/views/下找 index.jsp 視圖 * */ @RequestMapping(value = "/update_with_post_file", method = RequestMethod.POST) public String update_with_post_file(@ModelAttribute(value = "article") ArticleModel article, HttpServletRequest request) throws IOException, ServletException { System.out.println(article); Collection<Part> parts = request.getParts(); for (Part part : parts) { System.out.println(part.getName() + "->" + part.getContentType()); } String id = request.getParameter("id"); String title = request.getParameter("title"); String content = request.getParameter("content"); System.out.println(String.format("%s,%s,%s", id, title, content)); return "index.jsp"; } }
LogController.java
@RestController @RequestMapping("/api/v1") public class LogController { @Autowired private LogService logService; @RequestMapping(value = "/logs", method = RequestMethod.GET) @ResponseBody public List<Log> getAll() { return logService.getList(); } @RequestMapping(value = "/log/{id}", method = RequestMethod.GET) @ResponseBody public Log getById(Long id) { return this.logService.getById(id); } @RequestMapping(value = "/log/create", method = RequestMethod.GET) @ResponseBody public int create() { Log log = new Log(); log.setTitle("test log title"); log.setContent("test log content"); log.setModuleType(ModuleType.Article_Module); log.setOperateType(OperateType.Modify); log.setDataId(String.valueOf(1L)); log.setCreateTime(new Date()); log.setCreateUser("create user"); log.setCreateUserId("user-0001000"); int result = this.logService.insert(log); return result; } }
備注:
該類不需要視圖,是采用restful方式訪問。
5)在src/main/webapp/WEB-INF/下添加視圖文件:
/WEB-INF/templates/index.html
<h1>Thymeleaf Index Page</h1>
/WEB-INF/templates/log-list.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Good Thymes Virtual Grocery</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <h1>Log list</h1> <table> <tr> <th>id</th> <th>title</th> <th>model type</th> <th>operate type</th> <th>data id</th> <th>content</th> <th>create time</th> <th>create user</th> <th>create user id</th> </tr> <tr th:each="log : ${logs}"> <td th:text="${log.id}">id</td> <td th:text="${log.title}">title</td> <td th:text="${log.moduleType}">module type</td> <td th:text="${log.operateType}">operate type</td> <td th:text="${log.dataId}">data id</td> <td th:text="${log.content}">content</td> <td th:text="${log.createTime}">create time</td> <td th:text="${log.createUser}">create user</td> <td th:text="${log.createUserId}">create user id</td> </tr> </table> </body> </html>
/WEB-INF/views/index.jsp
<%@ page language="java" pageEncoding="UTF-8"%> <%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <!-- 屏蔽tomcat 自帶的 EL表達式 --> <%@ page isELIgnored="false" %> <html> <body> <h2>Hello World!</h2> </body> </html>
/WEB-INF/views/file/upload.jsp
<%@ page language="java" pageEncoding="UTF-8"%> <%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <!-- 屏蔽tomcat 自帶的 EL表達式 --> <%@ page isELIgnored="false" %> <html> <body> <h2>Post包含上傳文件提交:</h2> <form method="POST" name="article" action="${pageContext.request.contextPath }/update_with_post_file" enctype="multipart/form-data"> <!-- <input type="hidden" name="_method" value="PUT" /> --> Id:<input name="id" id="id" value="${article.id}" /><br /> Title:<input name="title" id="title" value="${article.title}" /><br /> Content:<input name="content" id="content" value="${article.content}" /><br /> yourfile: <input type="file" name="files" /><br /> <input type="file" name="files" /><br /> <input type="file" name="files" /><br /> yourfile2: <input type="file" name="execelFile" /><br /> <input type="submit" value="Submit" /> </form> <h2>Put包含上傳文件提交:</h2> <form method="POST" name="article" action="${pageContext.request.contextPath }/update_with_put_file/" enctype="multipart/form-data"> <input type="hidden" name="_method" value="PUT" /> Id:<input name="id" id="id" value="${article.id}" /><br /> Title:<input name="title" id="title" value="${article.title}" /><br /> Content:<input name="content" id="content" value="${article.content}" /><br /> yourfile: <input type="file" name="files" /><br /> <input type="file" name="files" /><br /> <input type="file" name="files" /><br /> yourfile2: <input type="file" name="execelFile" /><br /> <input type="submit" value="Submit" /> </form> </body> </html>
6)/src/main/webapp/WEB-INF/web.xml配置內容:
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> </web-app>
測試訪問
訪問http://localhost:8080/test-pro,使用jsp視圖引擎,渲染頁面/WEB-INF/views/index.jsp。
訪問http://localhost:8080/test-pro/index,使用jsp視圖引擎,渲染頁面/WEB-INF/views/index.jsp。
訪問http://localhost:8080/test-pro/upload,使用jsp視圖引擎,渲染頁面/WEB-INF/views/file/upload.jsp,在該視圖中支持post+commonMultipartResolver和put+commonMultipartResovler實現上傳文件的用法。
訪問http://localhost:8080/test-pro/api/v1/logs,使用restful訪問所有日志信息。
訪問http://localhost:8080/test-pro/api/v1/log/{id},使用restful訪問某個日志信息。
訪問http://localhost:8080/test-pro/api/v1/log/create,使用restful訪問新增日志信息。
訪問http://localhost:8080/test-pro/log-list,使用thymeleaf視圖引擎,渲染頁面/WEB-INF/templates/log-list.html頁面,頁面中會渲染日志列表。
參考:
《【小家Spring】Spring MVC控制器中Handler的四種實現方式:Controller、HttpRequestHandler、Servlet、@RequestMapping》
《SpringMVC的REST中錯誤:HTTP Status 405 - JSPs only permit GET POST or HEAD.》
《springmvc 同時配置thymeleaf和jsp兩種模板引擎》