前言
本系列文章的以学习为目的,结合Java中的spring、mybatis、maven和前端的vue等时下较为流行的开发技术,来实现一个简单的wiki项目,如果您对文中的开发思路或者行文观点有不同的意见,欢迎在文章下面留下您的观点。
建立Maven父子工程
1. 新建父工程
点击新建项目,选择maven项目,点击Next。
选择创建简单项目,点击Next
接下来我们需要为项目设置基本的信息,需要注意的是,这里的packaging的一定要选择pom,如下图所示:
点击Finsh后,会得到一个空的Maven 项目,可以修改下它的pom。
然后把根目录下作用不太大的src目录删除。
一般来讲,子工程会继承父工程中的jar,所以,我们可以将所有工程都需要的jar写入到父工程的pom中。
我的pom文件修改后代码如下:
4.0.0
org.wiki
wiki
1.0
pom
Wiki 工程父工程
http://localhost:8080/
http://localhost:8080/
小林子个人开发工作室
Wiki 工程的顶级父工程
commons-lang
commons-lang
2.6
commons-codec
commons-codec
1.10
commons-collections
commons-collections
3.2.1
commons-logging
commons-logging
1.2
commons-fileupload
commons-fileupload
1.3.1
commons-io
commons-io
2.5
commons-net
commons-net
3.3
org.apache.commons
commons-lang3
3.4
org.slf4j
slf4j-simple
1.6.1
test
log4j
log4j
1.2.17
com.alibaba
fastjson
1.2.7
redis.clients
jedis
2.8.0
org.mybatis
mybatis
3.4.1
mysql
mysql-connector-java
5.1.38
UTF-8
UTF-8
UTF-8
wiki
org.apache.maven.plugins
maven-compiler-plugin
1.7
UTF-8
因为考虑到项目可能会使用到json、缓存、数据库连接以及日志,我这里加入了fastjson、jedis、mybatis、mysql-connector-java、slf4j和log4j,其他的都是阿帕奇的公用jar
因为项目比较简单,可以利用多个子工程来实现wiki项目的分层实现,接下来开始对项目进行分层,这里分为Entity,DAO,Web。
2. 子工程 - 实体层
右键点击父工程,选择新建项目,选择Maven 模块,如下图所示:
接下来的对话框中录入模块的名称,并勾选 Create a simple project,因为Maven项目默认为jar项目,所以直接点击确定即可
接下来我们修改下实体层的pom,我的pom文件如下:
4.0.0
org.wiki
wiki
1.0
wiki
wiki-entity
1.0.0
jar
Wiki 实体工程
http://localhost:8080/
http://localhost:8080/
小林子个人开发工作室
Wiki 数据库操作工程
为了待会工程试运行,这里建立一个pojo来,映射数据库中的wiki_language表。如下图所示:
具体代码如下:
package org.wiki.entity.pojo;
import java.io.Serializable;
/**
* 映射数据库中的wiki_language表
*
* @author xiaolinzi
* @time 2018-04-12 19:58
*/
public class Language implements Serializable {
private static final long serialVersionUID = -4136761147966280970L;
private Long id;
private String name;
private Boolean popular;
/**
* 主键ID
*
* @return
*/
public Long getId() {
return id;
}
/**
* 主键ID
*
* @param id
*/
public void setId(Long id) {
this.id = id;
}
/**
* 语言的名称
*
* @return
*/
public String getName() {
return name;
}
/**
* 语言的名称
*
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 是否为流行语言
*
* @return
*/
public Boolean getPopular() {
return popular;
}
/**
* 是否为流行语言
*
* @param popular
*/
public void setPopular(Boolean popular) {
this.popular = popular;
}
@Override
public String toString() {
return "Language [id=" + id + ", name=" + name + ", popular=" + popular + "]";
}
}
3. 子工程 - 数据访问层
按照新建实体层的步骤,建立数据访问层。完成后界面如下:
因为需要在这个项目中将mybatis的xml文件包含进入jar中,所以我们需要将pom文件稍作修改,如下所示:
这样一来,在编译后,就能将mybatis的xml文件包含到jar中了,具体代码如下:
4.0.0
org.wiki
wiki
1.0
wiki
wiki-dao
1.0.0
jar
Wiki 数据库操作工程
http://localhost:8080/
http://localhost:8080/
小林子个人开发工作室
Wiki 数据库操作工程
wiki
wiki-entity
${project.version}
wiki-dao
src/main/java
**/*.xml
false
在之前的 数据库设计 的文章中,我已经将数据库插入了一些初始化的数据,那么,这里我们可以在DAO中将Language表中的数据查询出来,得到一个实体层的Language集合。
首先我们需要新建一个接口,命名为 LanguageMapper.java,代码如下:
package org.wiki.dao;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import org.wiki.entity.pojo.Language;
/**
* wiki_language 表查询接口
* @author xiaolinzi
* @time 2018-04-12 20:19
*/
public interface LanguageMapper {
/**
* 根据条件查询wiki_language表中的数据,返回Language对象的集合
* @param offset 索引起始值
* @param limit 数据条数
* @param orderBy 排序字段
* @return
*/
List select(@Param("offset") Integer offset, @Param("limit") Integer limit, @Param("orderBy") String orderBy);
}
接下来,需要新建一个mapper文件,命名为 LanguageMapper.xml,代码如下:
id, `name`, popular
完成后,接口层代码就写完了。如图所示:
4. 子工程 - 视图层
接下来需要建立war项目,所以,再建立子项目的时候就不能像上述那样操作了。在新建Maven模块的对话框中,到了如下图所示的步骤的时候,不要点击Finsh,点击Next,如下图:
在接下来的对话框中将Packaging选为War,然后点击Finsh即可。
接下来,就看到项目报错了,如下图所示:
因为这是一个web项目,这个错误的原因是在这个War项目中找不到web项目所必须的web.xml文件,不需要手动创建,按照如下图所示的步骤即可修正这个问题。
接下来就要将引入Spring全家桶了,打开pom文件,修改其中代码如下所示:
4.0.0
org.wiki
wiki
1.0
wiki
wiki-web
1.0.0
war
Wiki 网站工程
http://localhost:8080/
http://localhost:8080/
小林子个人开发工作室
Wiki 网站工程
com.collaborne.operations
tomcat-cors-regex-filter
1.0.0
org.mybatis
mybatis-spring
1.3.0
com.alibaba
druid
1.0.18
org.apache.tomcat
servlet-api
6.0.29
org.springframework.session
spring-session-data-redis
1.0.2.RELEASE
org.springframework.data
spring-data-redis
1.6.2.RELEASE
wiki
wiki-dao
${project.version}
org.springframework
spring-aop
${springmvc-version}
org.springframework
spring-aspects
${springmvc-version}
org.springframework
spring-beans
${springmvc-version}
org.springframework
spring-context
${springmvc-version}
org.springframework
spring-context-support
${springmvc-version}
org.springframework
spring-core
${springmvc-version}
org.springframework
spring-expression
${springmvc-version}
org.springframework
spring-jdbc
${springmvc-version}
org.springframework
spring-orm
${springmvc-version}
org.springframework
spring-oxm
${springmvc-version}
org.springframework
spring-tx
${springmvc-version}
org.springframework
spring-web
${springmvc-version}
org.springframework
spring-webmvc
${springmvc-version}
wiki-web
org.apache.maven.plugins
maven-jar-plugin
2.3.1
true
true
zh_CN
1.7
UTF-8
UTF-8
UTF-8
4.3.10.RELEASE
在pom文件中,我已经将项目依赖的DAO层引入,也引入了跨域过滤器,druid等jar。
接下来在 src/main/resources
新建如下几个文件
- 【自定义bean的声明文件】applicationContext-beans.xml
- 【数据连接配置文件】applicationContext-mybatis.xml
- 【spring程序上下文配置文件】applicationContext.xml
- 【mybatis配置文件】mybatis-config.xml
- 【spring-mvc配置文件】spring-mvc.xml
- 【数据库配置】jdbc.properties
- 【日hi配置】log4j.properties
- 【缓存配置】redis.properties
具体代码如下:
applicationContext-beans.xml
applicationContext-mybatis.xml
applicationContext.xml
mybatis-config.xml
spring-mvc.xml
text/html;charset=UTF-8
text/plain;charset=UTF-8
application/json;charset=UTF-8
text/html;charset=UTF-8
WriteMapNullValue
QuoteFieldNames
WriteDateUseDateFormat
- 需要注意的是,spring-mvc.xml 这个文件我加入了自定义拦截器org.wiki.base.BaseInterceptor,稍后我将介绍这个文件。
jdbc.properties
#jdbc
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:13306/wiki_utf8_alpha?autoReconnect=true&useUnicode=true&characterEncoding=utf-8
jdbc.username=wiki
jdbc.password=wiki
jdbc.maxActive=200
jdbc.initialSize=50
#druid
druid.initialSize=10
druid.minIdle=10
druid.maxActive=50
druid.maxWait=60000
druid.timeBetweenEvictionRunsMillis=60000
druid.minEvictableIdleTimeMillis=300000
druid.validationQuery=SELECT 'x' FROM DUAL
druid.testWhileIdle=true
druid.testOnBorrow=false
druid.testOnReturn=false
druid.poolPreparedStatements=true
druid.maxPoolPreparedStatementPerConnectionSize=20
druid.filters=wall,stat
log4j.properties
log4j.rootLogger=info,appender1
# appender1
log4j.appender.appender1=org.apache.log4j.ConsoleAppender
log4j.appender.appender1.layout=org.apache.log4j.PatternLayout
log4j.appender.appender1.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss:SSS}][%C-%M] %m%n
# mybatis
#log4j.logger.org.mybatis=DEBUG
log4j.logger.org.wiki.dao=DEBUG
# stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
redis.properties
redis.host=127.0.0.1
redis.port=6379
redis.pass=123456
redis.database=0
redis.maxIdle=300
redis.maxActive=1000
redis.maxWait=1000
redis.testOnBorrow=true
redis.timeout=1000
redis.pool.maxActive=1000
redis.pool.maxIdle=50
redis.pool.minIdle=0
redis.pool.maxWait=15000
redis.pool.testOnBorrow=false
redis.pool.testOnReturn=false
接下来,开始配置web.xml文件,具体如下:
wiki-web
蜂鸟参谋长项目WAR项目核心数据接口资源站,为整个所有的客户端提供数据支持
配置 Spring 的 contextConfigLocation属性以指向项目的配置文件
contextConfigLocation
classpath:applicationContext.xml,classpath:applicationContext-*.xml
配置 log4j的地址
log4jConfigLocation
classpath:log4j.properties
Spring字符集过滤器
characterEncodingFilter
org.springframework.web.filter.CharacterEncodingFilter
默认编码
encoding
UTF-8
强制编码
forceEncoding
true
characterEncodingFilter
/*
跨域过滤器
CorsFilter
org.apache.catalina.filters.CorsFilter
cors.allowed.origins
*
cors.allowed.methods
GET,POST,OPTIONS,PUT
cors.allowed.headers
Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers
cors.exposed.headers
Access-Control-Allow-Origin,Access-Control-Allow-Credentials
cors.support.credentials
true
cors.preflight.maxage
3600
CorsFilter
/*
org.springframework.web.context.ContextLoaderListener
org.springframework.web.util.Log4jConfigListener
org.springframework.web.util.IntrospectorCleanupListener
SpringMVC核心分发器
dispatcherServlet
org.springframework.web.servlet.DispatcherServlet
SpringMVC核心配置文件
contextConfigLocation
classpath:spring-mvc.xml
1
dispatcherServlet
*.json
dispatcherServlet
*.html
dispatcherServlet
*.do
6000000
/index.html
404
/favicon.ico
我的配置思路是,因为在数据请求的时候,需要获取json数据,获取html页面,所以,我配置了*.json和*.hmtl,同时,还需要post数据,这时候,我的想法是使用*.do。换言之,使用GET请求html资源和json资源的时候,会被控制器处理。当然,我在spring-mvc.xml中已经配置了将statis目录下的json设置为例外。
配置跨域过滤器的目的是为了以后开发node项目的时候,需要跨域数据请求。
接下来建立核心过滤器 org.wiki.base.BaseInterceptor.java,代码如下:
package org.wiki.base;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.InetAddress;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import com.alibaba.fastjson.JSONObject;
/**
* 核心过滤器
*
* @author xiaolinzi
* @time 2018-04-12 21:04
*/
public class BaseInterceptor extends HandlerInterceptorAdapter {
/**
* 日志对象
*/
private final Logger logger = Logger.getLogger(BaseInterceptor.class);
/**
* 记录时间的KEY
*/
private final String recordKey = "__RECORD_START_TIME__";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
super.preHandle(request, response, handler);
recordStart(request, response, handler, null);
if (request.getRequestURI().toLowerCase().endsWith(".json") || request.getRequestURI().toLowerCase().endsWith(".html")) {
// 如果是请求json数据,请求方式必须为GET
return "GET".equalsIgnoreCase(request.getMethod());
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
recordEnd(request, response, handler, ex);
}
/**
* 开始记录处理时间
*
* @param request
* @param response
* @param handler
* @param modelAndView
*/
private void recordStart(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
long begin_nao_time = System.nanoTime();
request.setAttribute(recordKey, begin_nao_time);
}
/**
* 结束记录处理时间
*
* @param request
* @param response
* @param handler
* @param ex
*/
private void recordEnd(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
final long begin_nao_time = (Long) request.getAttribute("__RECORD_START_TIME__");
final String realIp = getIpAddr(request);
final String uri = request.getRequestURI();
final long interval = System.nanoTime() - begin_nao_time;
String log = "URI:" + uri + ", METHOD:" + request.getMethod() + ", TOTAL_USE: " + interval + "ns (" + String.format("%.2f", ((double) interval / 1000000)) + "ms), CLIENT_IP: [" + realIp + "]";
if (ex != null) {
// 如果出现异常,则分析是因为什么参数导致的异常
String params = null;
try {
params = JSONObject.toJSONString(request.getParameterMap());
} catch (Exception e) {
params = null;
}
String errorInformation = null;
String readException = null;
try {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
ex.printStackTrace(pw);
sw.close();
pw.close();
errorInformation = sw.toString();
} catch (IOException e) {
readException = e.getMessage();
errorInformation = null;
}
logger.error("==TODO::[ERROR_INFORMATION_BEGIN]");
logger.info(log);
if (params != null) {
logger.error("PARAMS:" + params);
}
if (errorInformation != null) {
logger.error(errorInformation);
} else {
// 分析参数失败则直接输出异常对象
logger.error("Failed to analyze the error information, because: " + readException);
logger.error("The original error message is: ");
logger.error(ex.getMessage());
}
logger.error("==TODO::[ERROR_INFORMATION_END]");
} else {
// 如果没有异常,则打印相关信息
logger.info(log);
}
}
/**
* 获取客户端的IP地址
*
* @param request
*/
private String getIpAddr(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
if (ip.equals("127.0.0.1")) {
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (Exception e) {
e.printStackTrace();
}
ip = inet.getHostAddress();
}
}
if (ip != null && ip.length() > 15) {
if (ip.indexOf(",") > 0) {
ip = ip.substring(0, ip.indexOf(","));
}
}
return ip;
}
}
接下来我们新建LanguageController.java用来测试下刚刚的Language集合能不能被查询出来,代码如下:
package org.wiki.controller;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.wiki.dao.LanguageMapper;
import org.wiki.entity.pojo.Language;
/**
* 语言控制器
* @author xiaolinzi
* @time 2018-04-12 21:11
*/
@Controller
@RequestMapping("language")
public class LanguageController {
@Resource
private LanguageMapper languageMapper;
@ResponseBody
@RequestMapping("gets")
public List gets() {
return languageMapper.select(null, null, null);
}
}
一般来说,不应该将mapper直接在控制器中反射,应该在业务层,这里是为了测试能否正常访问数据库。
接下来我们建立IndexController.java 来显示视图,代码如下:
package org.wiki.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
/**
* 首页控制器
* @author xiaolinzi
* @time 2018-04-12 21:13
*/
@Controller
public class IndexController {
@RequestMapping("index")
public ModelAndView displayIndex() {
ModelAndView mv = new ModelAndView();
return mv;
}
}
在 spring-mvc.xml 中我已经配置了viewResolver的 prefix为/WEB-INF/views/,故而,需要在该目录下新建index.jsp来与上述代码中的@RequestMapping("index")
对应起来,因为很多页面的顶部和底部一样,可以将顶部和底部抽出来放入到布局目录下。
建立/WEB-INF/views/_layout 目录用于存储公共布局页面,并新建 header.jsp 和 footer.jsp 文件,代码分别如下:
header.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
footer.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
接下来,下载Layui的开发包,地址是:http://res.layui.com/download/layui/layui-v2.2.6.zip 下载Editor.md的开发包,地址是:https://github.com/pandao/editor.md/archive/master.zip 然后将他们解压放入到工程的statics目录下,同时将需要的jQuery,Editor.md,如图:
接下来在 /WEB-INF/views 下新建index.jsp来包含他们两个文件,代码如下
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
Wiki
到这里,Maven项目的建立就算初步完成了。
5. 将Web工程加入到Tomcat中试运行
代码写完了,我们需要试运行下项目有没有问题,首先,在Server面板添加之前添加好的Tomcat,操作如下:
在弹出的对话框中选择之前在Eclipse中添加好的对应版本的Tomcat,如下图所示。如果找不到对应版本,可能没有加入到Eclipse中,点击 这里 查看如何添加到Eclipse中。
接下来,双击刚刚新加的Tomcat,勾选如下圆圈中的选项,其目的是为了将项目发布到Tomcat安装目录下的wtpwebapps目录下,并发布模块上下文以分离XML文件。如下图所示:
接下来,右键点击刚刚新加的Tomcat,选择Add and Remove,在弹出来的对话框中,选择将我们新建的war项目【wiki-web】添加到tomcat中。如图所示
接下来,再双击刚刚新加的Tomcat,Modules选项卡中将刚刚新加的项目的Path设置为空,如图所示:
删除之后的界面应该是这样的:
接下来,我们先安装下maven的所有jar。操作如图所示:
如果出现如下图所示的界面,则说明安装成功
接下来再将maven项目编译下
在接下来对话框配置下需要执行的命令,为了方便下一次执行,可以在在顶部配置名称,然后点击Run:
如果接下来出现如下界面,则说明编译成功。
接下来可以点击刚刚新建的Tomcat右上方的绿色的小虫子,来运行下我们新加的项目,如图:
如果启动Tomcat过程中没有在控制台中出现Exception的蓝色字样,则说明启动成功。
因为我写好了两个控制器,分别是/index
和 /language/gets
,又因为我在web.xml文件中设置了欢迎页为index.html,所以直接访问 http://localhost:8080/ 能进入/index
控制器,显示如下图所示的界面:
直接访问 http://localhost:8080/language/gets.json 能进入到/language/gets
控制器,得到的json结构应该是这样的:
到这里,说明项目初始化已经完成,并试运行成功!
将工程提交到SVN仓库
接下来需要将项目提交到SVN仓库中,以便其他同事能够跟自己共同开发,因为这个项目是作为学习,这里使用到svn也是有助于开发的,以便自己知道,今天写了哪些代码之类的。在这之前,我们需要得到svn仓库的代码提交地址,因为我的机器名字是 java
,所以,我直接在地址栏中打开 https://java:8443 ,这里的8443是之前安装svn server 的时候配置的端口号,打开该地址之前,需要确保svn服务端程序为启动状态。打开该地址后,浏览器可能提示该连接不安全,没关系,继续访问就好了,接下来需要输入身份信息,使用之气那配置好的账户登录后,就能看到svn服务器中已经存在的仓库,如下图所示:
点击这里的wiki.org,接下来点击 branches ,在右侧的 checkout中选择迁出该目录,如下图所示:
我这里得到的地址是 : https://java:8443/svn/wiki.org/branches,接下来需要将刚刚建立好的Maven项目共享到这里仓库分支中。
在接下里的对话框中选择svn, 如图:
在接下来的对话框中,直接点击Next,即可,如图:
接下来,需要输入svn地址,输入 https://java:8443/svn/wiki.org/branches 后点击 Finsh,然后会弹出一个接受数字证书的对话框:
接下来,会弹出一个对话框,需要验证身份,输入对应的身份信息,并勾选保存密码,下一次就不需要在询问密码了。
接下来,弹出一个提示询问是否要打开资源同步视图,点击yes就好了,不过在这时候,也能看到控制台输出了svn迁出的信息。
接下来,能够看到我需要提交的文件的集合,但是需要说明的是,很多文件是不需要提交的,比如,.classpath,.project等,我们在提交之前需要将这些忽略掉。打开window --> Preferences --> Team --> Ignored Resources,然后将.project
,.classpath
,*/.settings/*
,*/.settings
,*/target/*
,\*/target
添加到忽略列表:如下图所示:
接下来右键父项目选择提交菜单,如图:
在接下来的对话框中输入本次提交的注释,这在团队开发中尤为重要,者表示你本次提交代码对项目做了哪些事情。
代码提交成功后,svn同步视图将看不到任何已经修改的文件,因为都提交了,点击 Eclipse右上方的如图所示的图标,切换回JavaEE的开发视图。
回到JavaEE开发视图后,能够看到项目前都加了一个黄色的小奶瓶,则说明同步代码正常。