从零开始搭建SSM框架(Spring + Spring MVC + Mybatis)
最近在回顾和总结一些技术,想到了把之前比较火的 SSM 框架重新搭建出来,作为一个小结,同时也希望本文章写出来能对大家有一些帮助和启发,因本人水平有限,难免可能会有一些不对之处,欢迎各位大神拍砖指教,共同进步。
本文章示例使用 IntelliJ IDEA 来开发,JDK 使用 11 版本,其余各框架和技术基本上使用了文章撰写当时的最新版本。
好的,下面直接进入正题。
打开 IntelliJ IDEA,File > New > Project > Maven,选中“Create from archetype”,然后再选中“org.apache.maven.archetypes:maven-archetype-webapp”:
Next,输入项目的“GroupId”、“ArtifactId”和Version:
Next,指定“Maven home directory”等配置:
Next,修改Project Name:
Finish,打开项目,添加一些必要的目录,最终项目框架目录图如下:
修改pom.xml文件,指定各依赖和插件的版本等信息:
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>11</java.version> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <spring.version>5.1.6.RELEASE</spring.version> <junit.version>4.12</junit.version> <lombok.version>1.18.6</lombok.version> <mybatis-plus.version>3.1.1</mybatis-plus.version> <freemarker.version>2.3.28</freemarker.version> <druid.version>1.1.16</druid.version> <jsqlparser.version>2.0</jsqlparser.version> <mysql-connector.version>8.0.16</mysql-connector.version> <jstl-api.version>1.2</jstl-api.version> <servlet-api.version>4.0.1</servlet-api.version> <jsp-api.version>2.3.3</jsp-api.version> <springfox-swagger.version>2.9.2</springfox-swagger.version> <commons-lang3.version>3.9</commons-lang3.version> <jackson.version>2.9.8</jackson.version> <mapstruct.version>1.3.0.Final</mapstruct.version> <log4j.version>2.11.2</log4j.version> <slf4j.version>1.7.26</slf4j.version> <clean.plugin.version>3.1.0</clean.plugin.version> <resources.plugin.version>3.1.0</resources.plugin.version> <compiler.plugin.version>3.8.0</compiler.plugin.version> <surefire.plugin.version>3.0.0-M3</surefire.plugin.version> <war.plugin.version>3.2.2</war.plugin.version> <install.plugin.version>3.0.0-M1</install.plugin.version> <deploy.plugin.version>3.0.0-M1</deploy.plugin.version> </properties>
在<dependencyManagement>标签里面管理各依赖的版本号:
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> <version>${mybatis-plus.version}</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>${mybatis-plus.version}</version> <scope>test</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>${freemarker.version}</version> <scope>test</scope> <optional>true</optional> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid.version}</version> </dependency> <dependency> <groupId>com.github.jsqlparser</groupId> <artifactId>jsqlparser</artifactId> <version>${jsqlparser.version}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql-connector.version}</version> </dependency> <dependency> <groupId>javax.servlet.jsp.jstl</groupId> <artifactId>jstl-api</artifactId> <version>${jstl-api.version}</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>${servlet-api.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>${jsp-api.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>${springfox-swagger.version}</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>${springfox-swagger.version}</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>${commons-lang3.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>${jackson.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>${mapstruct.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>${log4j.version}</version> </dependency> </dependencies> </dependencyManagement>
添加项目依赖:
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> </dependency> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>javax.servlet.jsp.jstl</groupId> <artifactId>jstl-api</artifactId> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> </dependency> </dependencies>
管理<build>:
<build> <finalName>ssm</finalName> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-clean-plugin</artifactId> <version>${clean.plugin.version}</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>${resources.plugin.version}</version> <configuration> <encoding>${project.build.sourceEncoding}</encoding> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>${compiler.plugin.version}</version> <configuration> <source>${maven.compiler.source}</source> <target>${maven.compiler.target}</target> <encoding>${project.build.sourceEncoding}</encoding> <annotationProcessorPaths> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </path> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${mapstruct.version}</version> </path> </annotationProcessorPaths> <compilerArgs> <compilerArg>-Amapstruct.defaultComponentModel=spring</compilerArg> <compilerArg>-Amapstruct.unmappedTargetPolicy=IGNORE</compilerArg> </compilerArgs> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>${surefire.plugin.version}</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>${war.plugin.version}</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-install-plugin</artifactId> <version>${install.plugin.version}</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> <version>${deploy.plugin.version}</version> </plugin> </plugins> </pluginManagement> </build>
依赖配置好之后,开始整合。
先整合Log4j2日志,在项目classpath目录(src/main/resources)下创建log4j2.xml文件,添加Log4j2日志配置:
<?xml version="1.0" encoding="UTF-8"?> <configuration status="WARN" monitorInterval="30"> <appenders> <console name="STDOUT" target="SYSTEM_OUT"> <PatternLayout disableAnsi="false" pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight{%5level} %magenta{%pid{-1}} --- [%-15.15t] %cyan{%c{1.}.%M(%F:%L)} : %m%n"/> </console> </appenders> <loggers> <root level="DEBUG"> <appenderRef ref="STDOUT"/> </root> </loggers> </configuration>
再整合 Spring 和 Mybatis,本次还整合了 Mybatis Plus 开源框架,新建 src/main/resources/mybatis/mybatis-config.xml 文件:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <!--配置允许懒加载--> <setting name="lazyLoadingEnabled" value="true"/> <!--取消关联查询积极性--> <setting name="aggressiveLazyLoading" value="false"/> <!--哪些方法触发关系查询--> <setting name="lazyLoadTriggerMethods" value="clone"/> </settings> <!--配置别名--> <typeAliases> <!--<package name=""/>--> </typeAliases> </configuration>
新建 src/main/resources/properties/jdbc.properties 文件,配置数据源连接信息:
jdbc.driverClassName=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/ssm?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=GMT%2B8 jdbc.username=root jdbc.password=root
新建 src/main/resources/spring/applicationContext-dao.xml 文件,配置 Druid 数据源,SqlSessionFactory 会话工厂,Mybatis 的 Mapper 接口扫描等信息:
<?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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:property-placeholder location="classpath:properties/jdbc.properties"/> <!-- Druid和Spring关联监控配置 --> <bean id="druidStatInterceptor" class="com.alibaba.druid.support.spring.stat.DruidStatInterceptor"/> <bean id="druidStatPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut" scope="prototype"> <property name="patterns"> <list> <value>com.example.ssm.service.*</value> <value>com.example.ssm.mapper.*</value> </list> </property> </bean> <aop:config expose-proxy="true" proxy-target-class="true"> <aop:advisor advice-ref="druidStatInterceptor" pointcut-ref="druidStatPointcut"/> </aop:config> <!-- Druid 配置 StatFilter:用于统计监控信息 --> <bean id="statFilter" class="com.alibaba.druid.filter.stat.StatFilter"> <property name="mergeSql" value="true"/> <property name="slowSqlMillis" value="10000"/> <property name="logSlowSql" value="true"/> </bean> <!-- Druid 配置 WallFilter:防御SQL注入攻击 --> <bean id="wallFilter" class="com.alibaba.druid.wall.WallFilter"> <property name="dbType" value="mysql"/> </bean> <!-- Druid 配置 Slf4jLogFilter:用于SQL日志打印 --> <bean id="slf4jLogFilter" class="com.alibaba.druid.filter.logging.Slf4jLogFilter"> <property name="dataSourceLogEnabled" value="true"/> <property name="statementExecutableSqlLogEnable" value="true"/> <property name="resultSetLogEnabled" value="false"/> </bean> <!-- 配置数据源 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="name" value="dataSource"/> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <property name="initialSize" value="5"/> <property name="minIdle" value="5"/> <property name="maxActive" value="20"/> <property name="maxWait" value="60000"/> <property name="timeBetweenEvictionRunsMillis" value="60000"/> <property name="minEvictableIdleTimeMillis" value="300000"/> <property name="testOnBorrow" value="false"/> <property name="testOnReturn" value="false"/> <property name="testWhileIdle" value="true"/> <property name="removeAbandoned" value="false"/> <property name="removeAbandonedTimeout" value="120"/> <property name="logAbandoned" value="true"/> <property name="validationQuery" value="SELECT 1"/> <property name="poolPreparedStatements" value="true"/> <property name="maxOpenPreparedStatements" value="20"/> <property name="asyncInit" value="true"/> <property name="proxyFilters"> <list> <ref bean="statFilter"/> <ref bean="wallFilter"/> <ref bean="slf4jLogFilter"/> </list> </property> <property name="useGlobalDataSourceStat" value="true"/> </bean> <!-- 配置会话工厂 --> <bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/> <property name="typeAliasesPackage" value="com.example.ssm.entity"/> <property name="mapperLocations" value="classpath:mapper/*Mapper.xml"/> <property name="plugins"> <array> <!-- 分页插件配置 --> <bean id="paginationInterceptor" class="com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor"/> <!-- 乐观锁插件 --> <bean id="optimisticLockerInterceptor" class="com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor"/> </array> </property> </bean> <!-- 配置扫描 Mapper 接口 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.example.ssm.mapper"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean> </beans>
新建 src/main/resources/spring/applicationContext-tx.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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <context:component-scan base-package="com.example.ssm.service"/> <!-- 事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:annotation-driven/> </beans>
配置 Mybatis Plus 自动生成 Controller、Service 和 Mapper 文件:
// 自动代码生成器 AutoGenerator autoGenerator = new AutoGenerator(); // 全局配置 GlobalConfig globalConfig = new GlobalConfig(); String projectPath = System.getProperty("user.dir"); globalConfig.setOutputDir(projectPath + "/src/main/java"); globalConfig.setAuthor("calvinit"); globalConfig.setOpen(false); globalConfig.setFileOverride(true); // Druid 1.1.16 版本貌似不支持 java8 新的时间类型,如 java.time.LocalDateTime globalConfig.setDateType(DateType.ONLY_DATE); autoGenerator.setGlobalConfig(globalConfig); // 数据源配置 DataSourceConfig dataSourceConfig = new DataSourceConfig(); dataSourceConfig.setDbType(DbType.MYSQL); dataSourceConfig.setUrl("jdbc:mysql://127.0.0.1:3306/ssm?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=GMT%2B8"); // dataSourceConfig.setSchemaName("public"); dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver"); dataSourceConfig.setUsername("root"); dataSourceConfig.setPassword("root"); autoGenerator.setDataSource(dataSourceConfig); // 包配置 PackageConfig packageConfig = new PackageConfig(); packageConfig.setParent("com.example.ssm"); autoGenerator.setPackageInfo(packageConfig); // 自定义配置 InjectionConfig injectionConfig = new InjectionConfig() { @Override public void initMap() { // to do nothing } }; // 如果模板引擎是 freemarker String templatePath = "/templates/mapper.xml.ftl"; // 自定义输出配置 List<FileOutConfig> focList = new ArrayList<>(); // 自定义配置会被优先输出 focList.add(new FileOutConfig(templatePath) { @Override public String outputFile(TableInfo tableInfo) { tableInfo.setImportPackages("com.baomidou.mybatisplus.annotation.TableName"); tableInfo.setConvert(true); // 自定义输出文件名 return projectPath + "/src/main/resources/mapper/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; } }); injectionConfig.setFileOutConfigList(focList); autoGenerator.setCfg(injectionConfig); // 配置模板 TemplateConfig templateConfig = new TemplateConfig(); // 配置自定义输出模板 // templateConfig.setEntity(ConstVal.TEMPLATE_ENTITY_JAVA); // templateConfig.setService(ConstVal.TEMPLATE_SERVICE); // templateConfig.setController(ConstVal.TEMPLATE_CONTROLLER); templateConfig.setXml(null); autoGenerator.setTemplate(templateConfig); // 策略配置 StrategyConfig strategy = new StrategyConfig(); strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel); // strategy.setSuperEntityClass("com.example.ssm.entity.BaseEntity"); strategy.setEntityLombokModel(true); strategy.setEntityColumnConstant(true); strategy.setRestControllerStyle(true); // strategy.setSuperControllerClass("com.example.ssm.controller.BaseController"); strategy.setInclude("t_user", "t_group", "t_user_group_relation"); // strategy.setSuperEntityColumns("id"); strategy.setControllerMappingHyphenStyle(true); strategy.setTablePrefix(packageConfig.getModuleName() + "_"); strategy.setEntityTableFieldAnnotationEnable(true); autoGenerator.setStrategy(strategy); autoGenerator.setTemplateEngine(new FreemarkerTemplateEngine()); autoGenerator.execute();
然后整合 Spring 和 Spring MVC,其中对日期类型返回json作了自定义格式化处理:
import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.module.SimpleModule; import java.io.IOException; import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; /** * Jackson ObjectMapper 工厂类 */ public class ObjectMapperFactory { public static ObjectMapper getMapper() { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); SimpleModule module = new SimpleModule(); module.addSerializer(LocalDate.class, new LocalDateSerializer()); module.addSerializer(LocalTime.class, new LocalTimeSerializer()); module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer()); objectMapper.registerModule(module); return objectMapper; } static class LocalDateSerializer extends JsonSerializer<LocalDate> { private static final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); /** * {@inheritDoc} */ @Override public void serialize(LocalDate value, JsonGenerator jgen, SerializerProvider provider) throws IOException { jgen.writeString(dateFormatter.format(value)); } } static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> { private static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); /** * {@inheritDoc} */ @Override public void serialize(LocalDateTime value, JsonGenerator jgen, SerializerProvider provider) throws IOException { jgen.writeString(dateTimeFormatter.format(value)); } } static class LocalTimeSerializer extends JsonSerializer<LocalTime> { private static final DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss"); /** * {@inheritDoc} */ @Override public void serialize(LocalTime value, JsonGenerator jgen, SerializerProvider provider) throws IOException { jgen.writeString(timeFormatter.format(value)); } } }
还集成了 Swgger UI ,方便接口调试:
import org.springframework.context.annotation.Bean; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.Contact; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @EnableSwagger2 public class Swagger2Configuration { @Bean("docket") public Docket createDocket() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.basePackage("com.example.ssm.controller")) .paths(PathSelectors.any()) .build() .apiInfo(apiInfo()); } private ApiInfo apiInfo() { Contact contact = new Contact("calvinit", "https://gitee.com/calvinit/ssm-demo", ""); return new ApiInfoBuilder() .title("SSM框架搭建Demo") .description("SSM框架搭建Demo,仅供猿友们学习交流之用,请勿用于商业用途") .contact(contact) .version("1.0-SNAPSHOT") .build(); } }
新建 src/main/resources/spring/spring-mvc.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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <context:component-scan base-package="com.example.ssm.controller"/> <mvc:default-servlet-handler/> <!-- Jackson ObjectMapper --> <bean id="objectMapper" class="com.example.ssm.json.ObjectMapperFactory" factory-method="getMapper"/> <mvc:annotation-driven> <mvc:message-converters> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="objectMapper" ref="objectMapper"/> <property name="supportedMediaTypes"> <list> <value>application/json;charset=UTF-8</value> </list> </property> </bean> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <constructor-arg value="UTF-8"/> </bean> </mvc:message-converters> </mvc:annotation-driven> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/view/"/> <property name="suffix" value=".jsp"/> </bean> <!-- Swagger2 配置 --> <bean class="com.example.ssm.configure.Swagger2Configuration"/> <mvc:resources mapping="swagger-ui.html" location="classpath:/META-INF/resources/"/> <mvc:resources mapping="/webjars/**" location="classpath:/META-INF/resources/webjars/"/> </beans>
同时整合了MapStruct,方便 Entity、DTO 和 VO 等之间的转换,使用示例:
import com.example.ssm.entity.User; import com.example.ssm.vo.UserVo; import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; import java.util.List; @Mapper public interface UserVoConverter { @Mappings({ @Mapping(source = "id", target = "userId"), @Mapping(source = "name", target = "userName"), @Mapping(target = "createDt", dateFormat = "yyyy-MM-dd HH:mm:ss"), @Mapping(target = "lastUpdateDt", dateFormat = "yyyy-MM-dd HH:mm:ss") }) UserVo entityToVo(User user); List<UserVo> batchEntityToVo(List<User> userList); @InheritInverseConfiguration User voToEntity(UserVo userVo); List<User> batchVoToEntity(List<UserVo> userVoList); }
新建 src/main/resources/spring/applicationContext-common.xml 文件,配置 MapStruct 的 bean 被 Spring 管理:
<?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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.example.ssm.converter"/> </beans>
修改 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_4_0.xsd" id="WebApp_ID" version="4.0"> <display-name>ssm</display-name> <!-- 初始化spring容器 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/applicationContext-*.xml</param-value> </context-param> <!-- 配置log4j2 --> <context-param> <param-name>log4jConfigLocation</param-name> <param-value>classpath:log4j2.xml</param-value> </context-param> <!-- 解决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> <!-- Druid 配置 WebStatFilter:用于采集web-jdbc关联监控的数据 --> <filter> <filter-name>druidWebStatFilter</filter-name> <filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class> <init-param> <param-name>exclusions</param-name> <param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value> </init-param> <init-param> <param-name>profileEnable</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>druidWebStatFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- springMVC 前端控制器 --> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-mvc.xml</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> <!-- Druid 配置 StatViewServlet:用于展示Druid的统计信息 --> <servlet> <servlet-name>druidStatView</servlet-name> <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class> <init-param> <!-- 访问白名单 --> <param-name>allow</param-name> <param-value>127.0.0.1</param-value> </init-param> <init-param> <!-- 访问用户名 --> <param-name>loginUsername</param-name> <param-value>user</param-value> </init-param> <init-param> <!-- 访问密码 --> <param-name>loginPassword</param-name> <param-value>password</param-value> </init-param> <init-param> <!-- 是否允许清空统计数据 --> <param-name>resetEnable</param-name> <param-value>true</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>druidStatView</servlet-name> <url-pattern>/druid/*</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
修改 index.jsp 文件:
<%@ page contentType="text/html;charset=UTF-8" %> <html> <head> <title>ssm</title> </head> <body> <h2>Hello World!</h2> <a href="${pageContext.request.contextPath}/swagger-ui.html">打开 Swagger UI</a> </body> </html>
至此,框架基本整合完毕,下面写一个测试 Contoller 测试 Spring 和 Spring MVC 整合结果:
package com.example.ssm.controller; import com.google.common.collect.Maps; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import springfox.documentation.annotations.ApiIgnore; import java.util.Map; @Api(tags = "首页控制器") @Controller @RequestMapping public class IndexController { @ApiIgnore @ApiOperation(value = "首页") @GetMapping public String index() { return "index"; } @ApiOperation(value = "返回纯字符串") @GetMapping("/hello") @ResponseBody public String hello() { return "Hello World!"; } @ApiOperation(value = "测试日期 json 返回") @GetMapping("/date/test") @ResponseBody public Map<String, Object> testDate() { Map<String, Object> map = Maps.newHashMap(); map.put("java.util.Date", new java.util.Date()); map.put("java.sql.Date", new java.sql.Date(System.currentTimeMillis())); map.put("java.time.LocalDate", java.time.LocalDate.now()); map.put("java.time.LocalTime", java.time.LocalTime.now()); map.put("java.time.LocalDateTime", java.time.LocalDateTime.now()); return map; } }
右上角“Add Configuration”添加 Tomcat 配置,将项目部署信息配置好:
然后我们启动 Tomcat,打开 http://localhost:8080/ssm,如果正常,应该显示如下界面:
点击界面上的超链接,进入 Swagger UI 的界面。
再测试一下 Spring 和 Mybatis 整合是否完成:
package com.example.ssm.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.example.ssm.converter.UserVoConverter; import com.example.ssm.entity.User; import com.example.ssm.service.IUserService; import com.example.ssm.vo.PageVo; import com.example.ssm.vo.UserVo; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.lang.NonNull; import org.springframework.web.bind.annotation.*; import java.util.List; @Api @RestController @RequestMapping("/users") public class UserController { private IUserService userService; private UserVoConverter userVoConverter; @Autowired public UserController(@NonNull IUserService userService, @NonNull UserVoConverter userVoConverter) { this.userService = userService; this.userVoConverter = userVoConverter; } @ApiOperation(value = "分页查询用户列表") @GetMapping("/page") @ResponseBody public PageVo<UserVo> page( @ApiParam(value = "当前页", required = true, defaultValue = "1", example = "1") @RequestParam("pageNum") int pageNum, @ApiParam(value = "页面大小", required = true, defaultValue = "10", example = "10") @RequestParam(value = "pageSize", defaultValue = "10") int pageSize) { Page<User> page = new Page<>(); page.setSize(pageSize); page.setCurrent(pageNum); page.setAsc(User.CREATE_DT, User.LAST_UPDATE_DT); IPage<User> userPage = userService.selectPage(page); List<User> userList = userPage.getRecords(); List<UserVo> userVoList = userVoConverter.batchEntityToVo(userList); return new PageVo<UserVo>() .setPageNum(userPage.getCurrent()) .setPageSize(userPage.getSize()) .setPageTotal(userPage.getPages()) .setRowTotal(userPage.getTotal()) .setRowList(userVoList) .get(); } @ApiOperation(value = "查询某个用户") @GetMapping("/{id}") @ResponseBody public User page( @ApiParam(value = "用户Id", required = true, example = "1") @PathVariable(value = "id") int id) { return userService.selectByPrimaryKey(id); } @ApiOperation(value = "获取所有00后用户列表") @GetMapping("/00/list") @ResponseBody public List<User> list00() { LambdaQueryWrapper<User> userLambdaQueryWrapper = Wrappers.lambdaQuery(); userLambdaQueryWrapper.ge(User::getBirthday, "2000-01-01"); return userService.list(userLambdaQueryWrapper); } }
在 Swagger UI 的界面里面测试接口,返回正确:
另外,Druid 的监控信息页面链接为:http://localhost:8080/ssm/druid/index.html,访问白名单、账号和密码在 web.xml 文件中配置。
至此,SSM 框架集成完毕,测试通过!
因篇幅关系,一些代码并没有在此文章中展现,可到我的Gitee上看完整框架代码。