从头搭建一个完善的后端服务并不像看上去那么容易,一般都需要用到众多组件,并且要让这些组件相互协调。不仅能搭起来,还能明白为什么这么搭,就需要更多的脑容量了。
从降低开发和测试成本、快速定位错误的角度,需要版本控制、单元测试、日志监控;从项目可扩展、可维护和高性能的角度,采用成熟、优秀的框架不仅可以缩短开发周期,而且还能避免自己造轮子的过程中踩太多坑。在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>
<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> </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