项目中常用SpringMVC+Spring+Mybatis搭建项目。各个配置讲解。
maven构建项目
步骤:
- 通过maven(maven-archetype-webapp)创建空白项目
- 添加所需要的目录
- 配置maven使用的相关插件
- 使用maven-archetype-webapp可以快速创建web目录结构项目,省去自己创建相关目录
操作:
创建空白项目后,添加日志依赖slf4j-log4j12;因为是web项目,想要添加servlet相关依赖:javax.servlet.servlet-api、javax.servlet.jsp.jsp-api、jstl、javax.servlet-api(web 3.0使用);
注意点:servlet-api和jsp-api这个scope需要设置为provided,因为项目部署的容器中,一般都有这两个jar包。
在resources目录下添加日志配置文件log4j.properties:
1 2 3 4 5 6 7
| log4j.rootLogger=info, Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender log4j.appender.Console.layout=org.apache.log4j.PatternLayout log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
|
添加插件org.apache.maven.plugins.maven-compiler-plugin、org.codehaus.mojo.tomcat-maven-plugin
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>utf-8</encoding> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>tomcat-maven-plugin</artifactId> <version>1.1</version> <configuration> <path>/</path> <port>8080</port> <uriEncoding>utf-8</uriEncoding> <server>tomcat7</server> </configuration> </plugin>
|
- org.apache.maven.plugins.maven-compiler-plugin插件作用设置项目编译的jdk版本
- org.codehaus.mojo.tomcat-maven-plugin插件用于通过maven的tomcat插件直接启动项目。如果需要使用高版本tomcat,不建议使用该插件启动tomcat,该插件支持的tomcat插件版本较低。
启动项目,访问localhost:8080查看项目启动是否正常。
添加SpringMVC
步骤:
- 配置web.xml
- 配置SpringMVC配置文件
- 启动访问Controller
操作:
添加springmvc依赖,pom文件修改添加依赖org.springframework.spring-webmvc,webmvc依赖了常用的jar包,包括bean、context、aop等。
在默认的web.xml中约束需要修改为web-app_2_5.xsd(如果不修改为2.4以上在jsp页面需要写<%@ page isELIgnored=”false” %> $才会生效)。
1 2 3 4 5
| <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/" version="2.5">
|
配置servlet。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
|
load-on-startup节点:
- load-on-startup元素标记容器是否在启动的时候就加载这个servlet(实例化并调用其init()方法)
- 它的值必须是一个整数,表示servlet应该被载入的顺序
- 当值为0或者大于0时,表示容器在应用启动时就加载并初始化这个servlet
- 当值小于0或者没有指定时,则表示容器在该servlet被选择时才会去加载
- 正数的值越小,该servlet的优先级越高,应用启动时就越先加载
- 当值相同时,容器就会自己选择顺序来加载
默认在WEB-INF添加spring-servlet.xml配置文件,配置spring扫描,配置视图解析器,配置静态资源,配置入出参UTF-8
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <context:component-scan base-package="com.feiniu">
</context:component-scan>
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean>
<mvc:resources mapping="/static/**" location="/static/"/>
<mvc:annotation-driven> <mvc:message-converters register-defaults="true"> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <property name="supportedMediaTypes" value="text/html;charset=UTF-8"/> </bean> </mvc:message-converters> </mvc:annotation-driven>
|
添加Controller,添加jsp,添加js,看是否能正常访问。
Controller获取请求参数
CookieValue:获取cookie的值
ModelAttribute:表单提交封装对象
PathVariable:获取路径中的参数,和开发rest风格api同用
RequestBody:一般用户json请求
RequestParam:request请求中的值
SessionAttributes:session中的值
等其他注解
SpringMVC相关
SpringMVC原理是定义一个Servlet,配置请求路径,之后相关请求会全部走到配置的DispatcherServlet,请求的url匹配到相关的Controller,默认SpringMVC的Controller是单例的,使用的时候需要注意线程安全。
源码阅读入口:
- SpringMVC启动:DispatcherServlet父类HttpServletBean.init(因为实现了Servlet,所以web容器会调用init初始化Servlet)
- 请求到SpringMVC:doService
添加Spring配置
一般SpringMVC用于与页面数据交互,引入Spring控制后续相关操作,如事务,数据库,以及其他Spring用到的地方。在理论上所有配置配置到SpringMVC中效果也是一样的,在实际中SpringMVC相关配置和Spring相关配置拆分开,系统层次结构更加清晰。
步骤:
- 配置web.xml启动加载spring
- 添加Spring配置文件
- 启动服务看是否正常
操作:
在web.xml中添加listener(ContextLoaderListener)
1 2 3 4 5 6 7 8
| <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>
|
ContextLoaderListener实现了ServletContextListener,在服务启动的时候,容器会调用contextInitialized初始化Spring
在resources目录下添加Spring配置文件applicationContext.xml,配置Spring自动扫描
1 2
| <context:component-scan base-package="com.feiniu"> </context:component-scan>
|
添加Service,启动服务,查看是否有异常。
在使用SpringMVC和Spring配置文件的时候,beans根节点上有个属性default-autowire=”byName”,表示在设置bean的时候,自动装配实现。autowire有5种装配模式:
- no:默认,不采用自动装配,需要在配置文件中通过ref标签注入
- byName:通过属性的名称注入
- byType:通过类型自动注入
- constructor:通过构造方法注入,与byType不同在于,如果bean不存在会报错
- default:采用父级标签配置
在平常使用注解@Autowired,和byType是一个意思,通过类型注入,如果有多个同类型Bean需要添加注解@Qualifier指定名称
Spring相关
在Spring容器概念,基本概念可以理解为Spring有一个工厂类用于创建和销毁bean,同时有个Map管理所有bean。延伸出了bean的生命周期、属性管理等其他相关操作。
源码阅读入口:
web.xml中阅读源码入口:ContextLoaderListener.contextInitialized
所有Spring不同容器的最终加载启动入口:AbstractApplicationContext.refresh
添加数据库配置
对于数据库操作,一般采用Mybatis。采用Mybatis方便统一管理所有sql,统一sql写在xml配置中,解除程序与sql耦合。方便维护对象和数据库映射关系,相对原生sql编写较简单,且不会过多影响性能。
在使用数据的时候,如果频繁创建、释放数据库连接,会产生大量的性能开销,引入数据库连接池,可以让数据库连接得到重用。在启动数据库连接池时,会初始化一部分数据库连接,对于业务而言,直接利用现有可用连接,避免连接初始花费时间。而且入数据库连接池,统一管理数据库连接,避免数据库连接泄露。(采用第三方数据源:druid)
步骤:
- 配置数据库连接
- 配置Mybatis
- 启动服务
操作:
添加数据库依赖org.springframework.spring-jdbc、org.mybatis.mybatis、org.mybatis.mybatis-spring、mysql.mysql-connector-java、com.alibaba.druid
resources目录添加application-db.xml配置文件,在Spring配置文件中引入该文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="jdbc:mysql://localhost:3306/test"/> <property name="username" value="test"/> <property name="password" value="password"/> </bean>
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.feiniu.mapper"/> <property name="sqlSessionFactoryBeanName" value="sessionFactory"/> </bean>
|
mapper.xml
1 2 3 4
| <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper> </mapper>
|
添加Mapper和XML中的sql,启动服务,访问是否正常。
访问启动正常,但是在访问的时候会报错 org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.feiniu.mapper.DemoMapper.countDemo ,这个时候可以看target编译的目录中,并没有mapper.xml,因为如果不额外配置的话,只有src/main/java下面的java文件编译为class,需要修改pom文件,设置src/main/java下面资源文件不过滤。如下:
1 2 3 4 5 6 7 8 9 10
| <resources> <resource> <filtering>false</filtering> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> <include>**/*.properties</include> </includes> </resource> </resources>
|
配置事务
因为spring-jdbc中已经包含了事务所需要的jar包,所以不需要额外配置相关依赖
修改applicationContext-db.xml添加事务支持
1 2 3 4 5 6 7
| <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
<tx:annotation-driven transaction-manager="transactionManager" />
|
启动服务测试事务是否生效。启动服务正常,但是事务不会生效。
因为在事务等配置是写在Spring的配置文件中,在配置扫描的时候,Spring和SpringMVC都扫描到Controller和Service导致,所配置的扫描对象会在Controller和Service中个存在一份,用户访问时,先通过SpringMVC的servelt获取的MVC中对象,直到DAO中获取不到实例,从父容器中获取。而事务又是配置Spring容器中,导致事务失效。可以在Controller和Service中都注入ApplicationContext,比较下两边的容器。
解决办法:修改MVC容器只能加载Controller,Spring容器加载非Controller
spring-servlet.xml
1 2 3
| <context:component-scan base-package="com.feiniu" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
|
applicationContext.xml
1 2 3
| <context:component-scan base-package="com.feiniu"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
|
修改后测试事务是否生效。
- 在使用注解Transactional时,如果不写rollbackFor,默认只回滚RuntimeException、Error,具体源码可以查看RuleBasedTransactionAttribute的父类DefaultTransactionAttribute中rollbackOn
事务相关
事务特性:
- 事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
- 一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
- 可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
- 一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
对于Spring而言,Spring并不直接管理事务,Spring提供了事务管理器接口PlatformTransactionManager,把事务管理职责委托给各个平台,入JDBC(org.springframework.jdbc.datasource.DataSourceTransactionManager)、Hibernate(org.springframework.orm.hibernate3.HibernateTransactionManager)等事务管理器。
Spring接口TransactionDefinition定义了5个事务隔离级别:
- ISOLATION_DEFAULT(默认):使用数据库默认的事务隔离级别.另外四个与JDBC的隔离级别相对应
- ISOLATION_READ_UNCOMMITTED:这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
- ISOLATION_READ_COMMITTED:保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。(MYSQL默认事务级别)
- ISOLATION_REPEATABLE_READ:这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
- ISOLATION_SERIALIZABLE:这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。
Spring接口TransactionDefinition定义了7个事务传播行为:
- PROPAGATION_REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择,也是 Spring 默认的事务的传播。
- PROPAGATION_SUPPORTS:如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。
- PROPAGATION_MANDATORY:如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。
- PROPAGATION_REQUIRES_NEW:总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。
- PROPAGATION_NOT_SUPPORTED:总是非事务地执行,并挂起任何存在的事务。使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作为事务管理器。
- PROPAGATION_NEVER:总是非事务地执行,如果存在一个活动事务,则抛出异常。
- PROPAGATION_NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。
在使用事务传播行为时,如果没有深刻理解,随意使用会导致出现意外情况。
配置properties配置文件
在项目中有很多基本数据是写在配置文件中,需要配置Spring读取配置文件,或者在Spring配置文件中有${}表达式时,通过配置文件设置值。减少每次需要修改代码。
resources目录下添加conf/local/application.properties配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| mysql.jdbc.driver=com.mysql.jdbc.Driver mysql.jdbc.user=test mysql.jdbc.password=password mysql.jdbc.alias=feiniu_proxool mysql.jdbc.url=jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&useUnicode=true&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true mysql.jdbc.maximum-connection-count=20 mysql.jdbc.minimum-connection-count=1 mysql.jdbc.simultaneous-build-throttle=5 mysql.jdbc.verbose=true mysql.jdbc.trace=true mysql.jdbc.fatal-sql-exception=Fatal error mysql.jdbc.prototype-count=5 mysql.jdbc.statistics-log-level=ERROR mysql.jdbc.maximum-active-time=600000 mysql.jdbc.house-keeping-test-sql=SELECT CURRENT_DATE
fn.env=dev
|
在applicationContext.xml中添加:
1
| <context:property-placeholder location="classpath:/config/local/*.properties"/>
|
修改applicationContext-db.xml中配置的url、user、password
1 2 3 4 5
| <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${mysql.jdbc.url}"/> <property name="username" value="${mysql.jdbc.user}"/> <property name="password" value="${mysql.jdbc.password}"/> </bean>
|
启动测试连接数据库是否正常。
如果需要在实例中注入配置文件中配置,可以使用注解@Value。
在公司里,启动时,把所有配置文件加载进一个类的静态属性中,这样保证了在应用任何地方可以随意读取配置。
添加类SystemEnv保存所有配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class SystemEnv { private static final Logger log = LoggerFactory.getLogger(SystemEnv.class);
private final static Properties systemProperties = new Properties();
public static void addProperty(Properties properties) { if (properties != null) { for (Object key : properties.keySet()) { if (key != null) { if (systemProperties.keySet().contains(key)) { log.error("系统Property配置项 {} 重复", key); } systemProperties.put(key, properties.get(key)); } } } }
public static String getProperty(String key, String defaultValue) { String value = systemProperties.getProperty(key); return value != null? value: defaultValue; }
public static Properties getProperties() { return systemProperties; } }
|
新增一个类PropertyPlaceholderConfigurerEx加载读取配置文件,继承PropertyPlaceholderConfigurer,覆盖父类方法processProperties,在Spring加载配置文件后,把配置文件加载进系统。
1 2 3 4 5 6 7
| public class PropertyPlaceholderConfigurerEx extends PropertyPlaceholderConfigurer { @Override protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException { super.processProperties(beanFactoryToProcess, props); SystemEnv.addProperty(props); } }
|
删除之前applicationContext.xml中添加的加载properties文件。在resources下新增applicationContext-config.xml,在applicationContext.xml中引入该配置。
applicationContext-config.xml:
1 2 3 4 5 6 7 8
| <bean class="com.feiniu.config.PropertyPlaceholderConfigurerEx"> <property name="locations"> <list> <value>classpath:config/local/*.properties</value> </list> </property> </bean>
|
启动测试是否正常。
- PropertyPlaceholderConfigurer类实现了BeanFactoryPostProcessor,在Spring启动的时候回调用所有实现了BeanFactoryPostProcessor接口的postProcessBeanFactory方法。
统一出入参数
一般项目中存在后台访问和对外API接口,对外API中一般要求统一入参、出参。
添加自定义消息转换器,统一处理返回参数格式。添加Gson依赖com.google.code.gson.gson。设置接口入参params={},json出参{code:200, msg:”xxx”, data:xxx}
步骤:
- 编写GsonUtils
- 添加统一出参Vo
- 添加入参数验证
- 配置自定义消息转换器
操作:
GsonUtils.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public class GsonUtils { private static final Gson gson = new GsonBuilder().create(); private static final Gson gsonFormat = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
public static Gson createGson(){ return gson; }
public static Gson createGsonFormat(){ return gsonFormat; }
public static <T> String toJson(T t){ return gson.toJson(t); }
public static <T> String toJsonFormat(T t){ return gsonFormat.toJson(t); }
public static <T> T fromJson(String json, Class<T> clazzT){ return gson.fromJson(json, clazzT); }
public static <T> T fromJson(String json , Type type){ return gson.fromJson(json, type); }
public static <T> T fromJsonFormat(String json, Class<T> clazzT){ return gsonFormat.fromJson(json, clazzT); }
public static <T> T fromJsonFormat(String json, Type type){ return gsonFormat.fromJson(json, type); } }
|
添加统一vo,ResponseBodyVo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public class ResponseBodyVo { private Integer code; private String msg; private Object data;
public ResponseBodyVo() { }
public ResponseBodyVo(Integer code, String msg, Object data) { this.code = code; this.msg = msg; this.data = data; }
public Integer getCode() { return code; }
public void setCode(Integer code) { this.code = code; }
public String getMsg() { return msg; }
public void setMsg(String msg) { this.msg = msg; }
public Object getData() { return data; }
public void setData(Object data) { this.data = data; } }
|
自定义消息转换器GsonHttpMessageConverter基础抽象类AbstractHttpMessageConverter实现接口GenericHttpMessageConverter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| public class GsonHttpMessageConverter extends AbstractHttpMessageConverter<Object> implements GenericHttpMessageConverter<Object> { private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
@Override protected boolean supports(Class<?> clazz) { return true; }
@Override protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { String requestBody = StreamUtils.copyToString(inputMessage.getBody(), DEFAULT_CHARSET); String paramsData = URLDecoder.decode(requestBody.replace("params=", ""), "utf-8"); Object requestVo = GsonUtils.fromJson(paramsData, TypeToken.get(clazz).getType()); if (clazz.isArray()) { throw new HttpMessageNotReadableException("不支持集合泛型: "); } else if (requestVo instanceof Collection) { Collection<?> requests = (Collection<?>) requestVo; ValidationUtils.validate(requests, requests.size()); } else { ValidationUtils.validate(requestVo); } return requestVo; }
@Override protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { ResponseBodyVo responseBodyVo; if (o instanceof ResponseBodyVo) { responseBodyVo = (ResponseBodyVo) o; } else { responseBodyVo = new ResponseBodyVo(200, "success", o); } outputMessage.getBody().write(GsonUtils.toJson(responseBodyVo).getBytes());
}
@Override public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) { return true; }
@Override public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { String requestBody = StreamUtils.copyToString(inputMessage.getBody(), DEFAULT_CHARSET); String paramsData = URLDecoder.decode(requestBody.replace("params=", ""), "utf-8"); Object requestVo = GsonUtils.fromJson(paramsData, TypeToken.get(type).getType()); if (requestVo instanceof Object[]){ Object[] requests = (Object[]) requestVo; ValidationUtils.validate(requests, requests.length); } else if (requestVo instanceof Collection) { Collection<?> requests = (Collection<?>) requestVo; ValidationUtils.validate(requests, requests.size()); } else { ValidationUtils.validate(requestVo); } return requestVo; }
@Override public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) { return true; }
@Override public void write(Object o, Type type, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { ResponseBodyVo responseBodyVo; if (o instanceof ResponseBodyVo) { responseBodyVo = (ResponseBodyVo) o; } else { responseBodyVo = new ResponseBodyVo(200, "success", o); } outputMessage.getBody().write(GsonUtils.toJson(responseBodyVo).getBytes());
} }
|
添加测试Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @RequestMapping("api") @RestController public class ApiController {
@RequestMapping("list") public List<DemoVo> list(@RequestBody SearchDemoVo[] searchDemoVo){ List<DemoVo>list = new ArrayList<>(); for (int i = 0; i < 5; i++) { DemoVo demoVo = new DemoVo(); demoVo.setName(String.valueOf(i)); list.add(demoVo); } return list; }
}
|
在使用消息转换器的时候,需要注意的是,入参采用@RequestBody注解,出参数采用@ResponseBody注解。才可以使用消息转换器。
在spring-servlet.xml中添加自定义消息转换器。
1 2 3 4 5 6 7 8 9
| <bean class="com.feiniu.http.converter.GsonHttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/html;charset=UTF-8</value> <value>application/json</value> <value>application/x-www-form-urlencoded</value> </list> </property> </bean>
|
全局异常处理、入参数校验
在开发过程中,一般需要统一处理相关异常,避免内部异常暴露在外。需要统一处理异常以及消息校验。
添加依赖hibernate-validator校验
步骤:
- 添加依赖
- 添加校验Util
- 添加异常全局处理
- 消息转换器添加异常返回
操作:
pom文件添加依赖
添加校验Util
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class ValidationUtils { private static Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
public static <T> void validate(T t) throws ValidationException { Set<ConstraintViolation<T>> set = validator.validate(t); if(set.size()>0){ StringBuilder validateError = new StringBuilder(); for(ConstraintViolation<T> val : set){ validateError.append(val.getMessage()).append(";"); } throw new ValidationException(validateError.toString()); }
} public static <T> void validate(Collection<T>ts, int size) throws ValidationException { if (ts == null || ts.isEmpty()){ throw new IllegalArgumentException("参数为空"); } if (ts.size() > size){ throw new IllegalArgumentException("超出最大查询数量:" + size); } for (T t : ts) { validate(t); } } }
|
添加异常处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @ControllerAdvice(basePackages = "com.feiniu.controller.api") public class ApiControllerAdvice {
@ExceptionHandler(Exception.class) @ResponseBody public ResponseBodyVo handleGlobalException(HttpServletRequest request, Exception ex){ ex.printStackTrace(); ResponseBodyVo responseBodyVo ; if (ex instanceof ValidationException){ responseBodyVo = new ResponseBodyVo(500, "参数异常", ex.getMessage()); } else { responseBodyVo = new ResponseBodyVo(500, "系统错误", "后台数据异常"); } return responseBodyVo; } }
|
修改消息转换器,添加入参验证,出参数处理。
验证消息转换器是否正常。注意验证集合、数组、泛型、对象等。
- 在使用消息转换器的时候,如果不做特别判断,默认会处理所有的消息,有时候项目中包含API和后台模块,会导致后台ajax请求也会封装参数,所以在消息转换的时候判断是否需要转换消息。
本地Junit测试
在一般功能模块开发完成后,需要在本地进行测试,可以采用spring-test加Junit测试
在pom文件中添加spring-test和Junit相关依赖,设置scope为test。
举例创建测试文件DemoServiceTest测试DemoService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath*:applicationContext.xml") @WebAppConfiguration public class DemoServiceTest { @Autowired private DemoService demoService; @Test public void dbCount() throws Exception { int i = demoService.dbCount(); System.out.println("```````````" + i); assert i != 0; }
@Test @Rollback @Transactional public void insertDemo() throws Exception { int i = demoService.insertDemo(); assert i == 1; }
}
|
分环境打包项目
在项目启动后一般需要区分不同的环境,比如local、dev、beta、preview、online,各个环境配置不一样,需要分开读取配置文件。
步骤:
- 修改pom文件分环境
- 修改启动配置文件
操作:
pom根目录下添加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <profiles> <profile> <activation> <activeByDefault>true</activeByDefault> </activation> <id>default</id> <properties> <config.path>file:服务器路径</config.path> </properties> </profile> <profile> <id>local</id> <properties> <config.path>classpath:config/local</config.path> </properties> </profile> <profile> <id>dev</id> <properties> <config.path>classpath:config/dev</config.path> </properties> </profile> <profile> <id>beta</id> <properties> <config.path>classpath:config/beta</config.path> </properties> </profile> </profiles>
|
在打包的指定环境即可选择需要的包
在pom文件resources下添加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <resource> <filtering>true</filtering> <directory>src/main/resources</directory> <includes> <include>applicationContext-config.xml</include> <include>checksrv.properties</include> </includes> </resource> <resource> <filtering>false</filtering> <directory>src/main/resources</directory> <includes> <include>**/*.xml</include> <include>**/*.properties</include> </includes> </resource>
|
打包的时候替换指定参数
修改applicationContext-config.xml
1 2 3 4 5 6 7
| <bean class="com.feiniu.config.PropertyPlaceholderConfigurerEx"> <property name="locations"> <list> <value>${config.path}/*.properties</value> </list> </property> </bean>
|
测试打包是否正常