0%

Mybatis、SpringMVC与Junit、Log4J2集成

从头搭建一个完善的后端服务并不像看上去那么容易,一般都需要用到众多组件,并且要让这些组件相互协调。不仅能搭起来,还能明白为什么这么搭,就需要更多的脑容量了。

从降低开发和测试成本、快速定位错误的角度,需要版本控制、单元测试、日志监控;从项目可扩展、可维护和高性能的角度,采用成熟、优秀的框架不仅可以缩短开发周期,而且还能避免自己造轮子的过程中踩太多坑。在Java Web邻域,最常用的组件如下表所示:

|模块| 常用组件|
|—|—|—|
|版本控制| Git|
|单元测试| Junit|
|日志| Log4j|
|数据库| MySQL|
|持久化 |MyBatis|
|Bean容器| SpringMVC|

根据实际搭建经验,由于组件太多,不是这个出问题就是那个不听话。所谓按下葫芦浮起瓢说得就是这种情况。为了避免重复踩坑,本文把搭建过程和各组件之间的配置整理了一下,并提供了一个demo项目。

Maven

Maven用来管理包依赖想必是极好的,由于一直以来没惹太多麻烦,这次就暂且按下不表了。本文重点整顿的组件包括及版本号如下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
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.jeffzzf</groupId>
<artifactId>basic</artifactId>
<version>1.0.0</version>
<packaging>war</packaging>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.1.6.RELEASE</spring.version>
<mybatis.version>3.3.1</mybatis.version>
<log4j.version>2.7</log4j.version>
<junit.version>4.12</junit.version>
</properties>

<dependencies>

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.29</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</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-webmvc</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-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.2</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- 阿里巴巴数据源包 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.2</version>
</dependency>

<!--把项目中使用commons-logging输出的日志统一桥接到log4j2-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jcl</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-web</artifactId>
<version>${log4j.version}</version>
</dependency>

</dependencies>
</project>

Spring MVC

使用Spring作为Java Web框架的常见配置是在web.xml文件中注册加载Spring ApplicationContext的Listener——ContextLoaderListener,并通过contextConfigLocation参数指定ApplicationContext的配置文件。如果没有配置contextConfigLocation参数,ContextLoaderListener将默认使用/WEB-INF/applicationContext.xml作为配置文件。

1
2
3
4
5
6
7
8
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring/*.xml</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

如果要使用Spring MVC处理请求(request,session,globalSession,application和websocket这五种scope的bean),则需要额外的配置。通过Spring DispatcherServlet或DispatcherPortlet处理的所有请求都会解决scope相关的所有问题。DispatcherServlet将所有请求分发到对应的Controller,所以,在正确初始化Spring后,如果要配置Spring MVC作为Java Web的MVC框架,只需要正确配置DispatcherServlet即可。

每个DispatcherServlet对象都拥有自己的WebApplicationContext对象,并且继承根WebApplicationContext中定义的所有bean。根WebApplicationContext应该包含在所有Context和Servlet间共享的bean和配置。并且,这些继承的bean和配置可以在各自的空间内覆盖。

初始化DispatcherServlet时,Spring MVC默认在WEB-INF/目录下查找[servlet-name]-servlet.xml文件,也可以通过配置Servlet初始化参数contextConfigLocation来指定该配置文件位置。当然,也可以只配置一个根Content和单个DispatcherServlet。这种情况下,只需要将contextConfigLocation配置成空即可。更多详细配置可以参考Spring MVC的官方文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<servlet>
<servlet-name>Dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/spring/DispatcherServlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>Dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

Spring与MyBatis集成

MyBatis与Spring的集成,需要依赖jar包Mybatis-Spring,通过该包,MyBatis可以在Spring框架下创建mapper和SQLSession,并且将它们注入到Spring Bean中。MyBatis还可以使用Spring的事务,并将异常包装成Spring的DataAccessException。

配置Spring和Mybatis也比较简单,首先配置MyBatis-Spring提供的sqlSessionFactory,指定使用的dataSource、sql映射文件位置和映射到的Java bean所在的包等。

还可以配置MapperScannerConfigurer定位Mapper所在的包,Mapper的定义是接口而不是类。配置好MapperScannerConfigurer后,包下面的所有mapper都会被扫描。扫描注册后这些mapper(dao对象)就可以被注入到相关的服务中了。更多配置方法可以参考Mybatis-Spring的官方文档。

一个配置了dataSource、MyBatis和事务的Spring ApplicationContext文件如下所示:

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
<?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:tx="http://www.springframework.org/schema/tx" 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/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

<context:component-scan base-package="com.jeffzzf.basic" />

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=false"/>
<property name="username" value="{user}"/>
<property name="password" value="{password}"/>
</bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations" value="classpath*:sqlmapper/*.xml" />
<property name="typeAliasesPackage" value="com.jeffzzf.basic.po" />
</bean>

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.jeffzzf.basic.dao" />
</bean>

<!-- 对数据源进行事务管理 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>

</beans>

Log4J2

Log4j2作为Spring的日志框架

日志是快速定位和分析问题的不二之选,Spring中唯一的外部强制依赖就是日志框架Jakarta Commons Logging API (JCL)。Spring与Log4j搭档其实很简单,Spring已经提供了配置和使用log4j的组件,在一些模块提供了编译时选择log4j作为日志依赖的机制。使用时只需要把Log4j的依赖添加到Maven中,并提供log4j的配置文件即可。

Log4j2作为Web的日志框架

在Java EE Web项目中使用日志的过程中,要特别注意在Web应用卸载或容器关闭时将日志模块使用的资源(例如日志数据库连接、日志文件等)释放掉。Log4j应该在应用部署时启动,在应用卸载或容器关闭时关闭。

Log4j作为Java Web的日志框架配置随Servlet版本(低于2.5或高于3.0)的不同而不同,但是无论哪个版本,都需要将log4j-web的jar包添加到项目中。为了避免错误,添加log4j-web的jar包后,Log4j的钩子函数会自动关闭。

Java Web中配置Log4J作为日志框架的方法是在web.xml文件中配置log4jConfiguration这个环境参数(context parameter).如果log4jConfiguration参数的值是一个位置,那么将使用该位置指示的文件作为Log4J的配置文件;否则,将使用WEB-INF目录下以“log4j2”开头的文件作为配置文件或者配置的URL形式的文件作为配置文件。

为了提高性能,Tomcat 7 <7.0.43的版本中,catalina.properties配置文件跳过了log4j的jar包,如果使用的是这些版本的Tomcat或其它跳过加载log4j jar包的容器,应该把”log4j*.jar”从jarsToSkip属性中移除。

Servlet 3.0+

受益于Servlet 3.0版本中提供的ServletContainerInitializer API,配置好的Log4j可以在应用部署时自动注册相应的Filter和ServletContextListener,并在应用卸载时自动卸载这些组件。因此,Servlet 3.0以上,即web.xml文件的= 3.0时,只需要log4jConfiguration环境参数正确定位到log4j2的配置文件,并确保Log2j相关的jar包被正确加载即可。针对Log4jServletFilter和 Log4jServletContextListener的个性化定制,可以参考Log4j2官方文档
Servlet 3.1, Tomcat 8.0.33,并使用Log4J 2.7的web.xml配置如下:

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
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>SpringMVC</display-name>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring/*.xml</param-value>
</context-param>

<context-param>
<param-name>log4jConfiguration</param-name>
<param-value>classpath:/log/log4j2.xml</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<servlet>
<servlet-name>Dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/spring/DispatcherServlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>Dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>

</web-app>

Servlet 2.5

Log4j2不支持2.4及以下版本的Servlet,如果Servlet版本是2.5或者Servlet版本是3.0+并且通过isLog4jAutoInitializationDisabled关闭了自动初始化,那么,就需要手工配置Log4jServletContextListener和Log4jServletFilter了。Filter需要匹配所有类型的请求,Listener应该是web.xml文件中配置的第一个listen。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<listener>
<listener-class>org.apache.logging.log4j.web.Log4jServletContextListener</listener-class>
</listener>

<filter>
<filter-name>log4jServletFilter</filter-name>
<filter-class>org.apache.logging.log4j.web.Log4jServletFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>log4jServletFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
<dispatcher>ASYNC</dispatcher><!-- Servlet 3.0 w/ disabled auto-initialization only; not supported in 2.5 -->
</filter-mapping>

Log4j2作为Spring & Junit的日志框架

Spring已经完美集成Junit作为单元测试的框架,通过配置SpringJUnit4ClassRunner并正确加载Spring的配置文件,就可以实现Junit的单元和集成测试。可以将配置都定义在抽象父类中,子类继承所有配置即可进行测试。

1
2
3
4
5
6
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({
"classpath*:spring/*.xml"
})
public abstract class AbstractTest {
}

使用Log4j作为测试日志框架也很简单,只需要将log4j2.xml配置文件放到测试的classpath上即可(一般为src/test/resources/下)。一份简单的log4j2.xml文件如下所示:

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
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Properties>
<Property name="path" value="/data/applogs/jeffzzf/test"/>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<RollingFile name="mainAppender" fileName="${path}/mainLog.log"
filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="250 MB"/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Logger name="mainLogger" level="debug">
<AppenderRef ref="mainAppender"/>
</Logger>
<Root level="debug">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>

Demo

配置好MyBatis,Spring MVC和Junit、Log4j 2的Demo:git@github.com:Jeffzzf/BackendDemo.git