背景
目前各系统之间的数据交互越来越频繁,频繁的给项目增加接口不仅增加整个系统的负担,还给开发人员麻烦,开发效率低,日常维护起来更是嫌弃;最近几年Spring微服务非常火,基于Spring Boot的Spring Cloud已经成为一站式微服务框架;准备入手Spring Cloud;首先从Spring Boot下手,使用Spring Boot写了个通用型的第三方接口功能,目前主要用于查询数据抛给调用者;其实也比较简单,主要就是实现业务逻辑,对于访问数据层是直接使用以前项目访问的方式,打成jar包,直接依赖使用等等;然后就是定义一下接口的规范,过滤掉一部分URL,输入的URI必须包含指定访问哪个数据库,条件可选;输出必须让调用者知道调用接口后的结果,尽可能简单明了;至于调用者需要哪些数据则需要双方协调沟通,配置好sql语句;尽可能实现调用者的需求。
具体实现
一、前提条件
1.安装JDK8或以上(springBoot 2.X版本)
2.开发工具(Eclipse或IDEA等,本次使用的Eclipse)
3.Spring开发套件工具(springsource-tool-suite)
4.apache-maven
二、开发准备
1.eclipse中安装Spring开发套件工具(下载时注意查看eclipse版本对应的springSource-tool-suite版本)
使用SpringSource-tool-suite插件是为开发Spring应用程序而定制的,它提供一个随时可用的环境来实现调试、运行和部署Spring应用程式,插件(sts3版本)集成了Pivatal tc Server(企业级的轻量java应用服务器,拓展了Apache Tomcat;可以说是企业版本的Apache Tomcat)、Pivotal Cloud Foundry(PCF是一个统一的多云平台,可以运行你的企业应用程序)、Git(分布式版本控制系统)、Maven(项目管理工具)、AspectJ(面向切面的框架AOP,拓展了java语言)等等。
1.1离线安装springSource-tool-suite
(1)下载(地址:https://spring.io/tools3/sts/all)
(2)打开Eclipse–>help–>install New Software…
(3)直接默认下一步Next,等待安装完成即可;安装完成之后就可以建Spring项目了。直接输入相关名称,然后默认下一步Next即可完成。
三、开发过程(1)Spring Boot项目结构
主要用到几个目录和文件:
1)src/main/java目录:建java项目文件等等;其中包含主程序入口xxxxApplication.java文件,通过直接运行该类来启动Spring Boot应用
2)src/main/resources:配置目录,该目录用来存放应用的一些配置信息,比如应用名、服务端口、数据库配置等等,也可以存放一些静态资源,如一些图片、CSS文件、template模板文件等等;
3)src/test/java:单元测试目录,生成的xxxxApplicationTests通过JUnit4实现,可以直接运行Spring Boot进行测试。
4)application.properties:用于存放程序的各种依赖模块的配置信息,比如服务端口,数据库连接配置等等;目前
设置了第三使用方接口服务端口server.port=10101。
5)pom.xml:maven项目文件,主要用于管理:源代码、配置文件、开发者的信息和角色、问题追踪系统、组织信息、项目授权、项目的URL、项目的依赖关系。
(2)pom.xml文件基本解析
<?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/maven-v4_0_0.xsd">
<!--
POM的版本
-->
<modelVersion>4.0.0</modelVersion>
<!--
含义:组织标识,定义了项目属于哪个组
用途:此名称则是本地仓库中的路径
命名规范:项目名称,模块,子模块
-->
<groupId>com.demo</groupId>
<!--
含义:项目名称也可以说你所模块名称,定义当面Maven项目在组中唯一的ID
用途:例如:filter,在M2_REPO目录下,将是:com/demo/filter对应下的目录
命名规范:唯一就好
-->
<artifactId>filter</artifactId>
<!--
含义:项目当前的版本号
用途:例如:0.0.1-SNAPSHOT,在M2_REPO目录下,将是:com/demo/filter/0.0.1-SNAPSHOT目录
-->
<version>0.0.1-SNAPSHOT</version>
<!-- 打包的格式,可以为:pom , jar , maven-plugin , ejb , war , ear , rar , par -->
<packaging>war</packaging>
<!-- 元素声明了一个对用户更为友好的项目名称 -->
<name>Spring-Boot-Demo</name>
<!-- 依赖继承的父类的jars -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!--
相关属性配置,如改变版本号、统一编码方式等等
-->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<springfox.version>2.2.2</springfox.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<skipTests>true</skipTests>
</properties>
<!--
依赖的额外jar包 ,根据自己实际需要去依赖,如果maven的中央库有就直接去取;
本地仓库需要自己指定或直接导到项目目录,依赖指定位置即可,groupId、artifacId和version名字随便取
-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${springfox.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${springfox.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<!-- 使用spring boot Maven插件将依赖的jar包生成可执行jar包 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainclass>com.myselft.ThirdPartyInterfaceApplication</mainclass>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
</plugins>
</build>
</project>
(3)程序设计
1)基本设计流程
如下:
2)程序设计
One: 最主要的Application.java文件;
/*
1.导入java基本依赖包
2.导入spring boot基本依赖包
3.导入自己的需求包;如操作IO流的工具包,自定义的过滤器、还有一些基本封装工具包
*/
import java.util.ArrayList;
import java.util.List;
import javax.servlet.Filter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import com.myself.filter.ThirdPartyCommonFilter;
import com.myself.util.IOUtil;
/*
使用的注解:
1.@EnableSwagger2: 启动Swagger;使用Swagger生成API,可以得到交互式文档,自动生成代码的SDK以及API的发现特性等等;
其实Swagger就是一个简单、功能强大的API表达工具;在springboot中的主要功能:
方便进行测试后台的restful形式的接口,实现动态的更新,当我们在后台的接口修改了以后。swagger
可以实现自动的更新,而不需要单独使用一些工具(如postman)来测试后台修改的接口。目前没有使用到,
先启动再说;依赖的jar包在
pom.xml文件引入:
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.2.2</version>
</dependency>
2.@Bean:注解会将方法中的返回对象在SpringBoot启动的时候放入IOC容器中,加载时自动实现实例化
*/
@EnableSwagger2
public class ThirdPartyInterfaceApplication extends SpringBootServletInitializer {
//注册Filter
@Bean
public FilterRegistrationBean<Filter> filterRegistrationBean(){
/*
1.创建并注册filter;
ThirdPartyCommonFilter类为自定义的过滤器
*/
FilterRegistrationBean<Filter> regFilter=new FilterRegistrationBean<>();
regFilter.setFilter(new ThirdPartyCommonFilter());
List<String> urlHeader=new ArrayList<String>();
//读取配置文件设置的需要过滤的url参数;自己封装的io工具类;方法可以自行
String url=IOUtil.getPropertyByName("com.myself.url.header", "ConfigFile/myself.properties").trim();
urlHeader.add(url);
/*
2.设置filter的相关属性
*/
regFilter.setUrlPatterns(urlHeader);//url过滤
regFilter.setName("ThirdPartyCommon");//过滤器名称
regFilter.setOrder(1);//过滤优先顺序
regFilter.setEnabled(true);//设置过滤器是否有效
return regFilter;//返回直接加载到IOC容器
}
public static void main(String[] args) {
//主程序入口,加载启动xxxxApplication类文件
SpringApplication.run(ThirdPartyInterfaceApplication.class, args);
}
/*
override重写 configure方法;可以修改启动类
*/
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(ThirdPartyInterfaceApplication.class);
}
}
Two: 过滤器Filter文件
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.myself.thirdpartyinterface.controller.ThirdPartyCommonController;
/*
创建自己的过滤器并实现Filter接口,以及重写Filter接口的方法,init()、doFilter()、destroy()
*/
public class ThirdPartyCommonFilter implements Filter {
//Filter过滤器初始化
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// TODO Auto-generated method stub
}
//过滤器所需要做的事情;即从接收到请求request之后所做的一系列动作,最后response响应返回
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// TODO Auto-generated method stub
//1.满足条件之后就会去该控制器执行自己设计的业务逻辑
ThirdPartyCommonController commonController=new ThirdPartyCommonController();
//2.设置request和response
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
//3.设置访问权限属性,设置无限制
resp.setHeader("Access-Control-Allow-Origin", "*");
resp.setHeader("Access-Control-Allow-Methods", "*");
resp.setHeader("Access-Control-Allow-Headers", "*");
//4.请求方法为OPTIONS时不做任何处理
if(req.getMethod().equals("OPTIONS")) {
resp.setStatus(HttpServletResponse.SC_OK);
return;
}
//5.设置响应的编码方式和响应返回的数据格式为json格式
resp.setCharacterEncoding(request.getCharacterEncoding());
resp.setContentType("application/json");
//6.开始过滤部分接收到的url,符合要求就继续往下执行业务;并返回结果
if(!req.getParameterMap().containsKey("TokenId")) {
//7.请求参数没有包含TokenId
resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);//400
resp.getWriter().write("The Request param: TokenId can not null!");
}else {
resp.setStatus(HttpServletResponse.SC_OK);//200
//8.进入controller执行业务,并将返回结果响应给调用者
resp.getWriter().write(commonController.dealRequest(req));
}
}
@Override
public void destroy() {
// TODO Auto-generated method stub
}
}
Three: controller业务逻辑控制层
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.myselft.action.Constants;
import com.myself.thirdpartyinterface.service.ThirdPartyCommonService;
import com.myself.util.IOUtil;
import net.sf.json.JSONObject;
public class ThirdPartyCommonController {
private static Logger log=LoggerFactory.getLogger(ThirdPartyCommonController.class);
private ThirdPartyCommonService commonService=new ThirdPartyCommonService(log);
/*
* 处理request请求
* @param HttpServletRequest
* @return String
* */
public String dealRequest(HttpServletRequest request) {
JSONObject jsonObj_res=new JSONObject();//返回响应的结果信息
jsonObj_res.put("Result", "Fail");
jsonObj_res.put("Message", "response fail");
String url_match=IOUtil.getPropertyByName("com.myself.url.header", "ConfigFile/myself.properties").trim();
/*
* 1.获取请求url的信息,包括:url、api_path_name、参数key-value值
*
* */
String url=request.getRequestURI(); //获取url
String api_path_name=url.substring(url_match.length()-2);//获取api_path_name
JSONObject jsonObj=new JSONObject();//用来保存请求传过来的所有参数
Map<String, String[]> maps=request.getParameterMap();//获取参数的key-value值
for(String key:maps.keySet()) {//遍历请求的参数值
String value=maps.get(key)[0].toString();
if(!value.equals("")) {
jsonObj.put(key.toUpperCase(), value);
}
}
/*
* 2.根据api_path_name获取需要查询的信息
* 包括:
* 1).所做的动作action,如query,add,update,delete
* 2).api_path_name,获取执行的环境schema和执行的sql
* 3).将sql的参数使用请求保存的jsonObj来代替
* */
String selectInfo=commonService.getInfoToUri(api_path_name);
if(selectInfo.startsWith("Exception")) {//异常
jsonObj_res.put("Result", "Exception");
jsonObj_res.put("Message", selectInfo.substring(10));
}else if(selectInfo.equals("[]")){
jsonObj_res.put("Message", "URL address Error,please Checked");
}else{
/*
* 3.根据jsonObj参数匹配并替换掉selectInfo的sql参数,并执行sql,返回json格式参数
* */
String query_res=commonService.selectSQLjsonArr(jsonObj, selectInfo);
if(query_res.startsWith("Exception")) {
jsonObj_res.put("Result", "Exception");
jsonObj_res.put("Message", query_res.substring(10));
}else if(query_res.equals("Null")) {
jsonObj_res.put("Message", "execute select sql not successed");
}else {
jsonObj_res.put("Result", "OK");
jsonObj_res.put("Message", query_res);
}
}
return jsonObj_res.toString();
}
}
Four: service层,主要与数据库交道;目前此使用的是oracle数据库;真正抛送给用户数据的执行sql保存在数据表里面,需要自行配置
//导入操作一些自己操作数据库的基本包
import java.sql.SQLException;
import org.slf4j.Logger;
import com.myself.function.dao.BaseDao;
import com.myself.function.service.BaseServices;
import com.myself.util.JsonUtil;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
public class ThirdPartyCommonService extends BaseServices<BaseDao>{
public ThirdPartyCommonService(Logger log) {
super(log);
// TODO Auto-generated constructor stub
}
/*
* 1.根据uri获取所有配置的相关信息
* @param String api_path_name
* @return String
* */
public String getInfoToUri(String api_path_name) {
String query_sql="自己需要配置的额sql语句,用来查询相关信息;比如根据api_path_name来获取需要执行的sql语句";
try {
/*
1.1自己的工具包,将查询出来的结果集转化成jsonArray格式
参数(sql语句,schema名称,条件参数)
*/
return JsonUtil.setToJsonArr(super.getResultSet(query_sql, "schema", new Object[]{api_path_name}));
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return "Exception: "+e.getMessage();
}
}
/*
* 2.根据jsonObj参数匹配并替换掉selectInfo的sql参数,并执行sql,返回json格式参数
* @param JSONObject
* @param Sting
* @return String
* */
public String selectSQLjsonArr(JSONObject jsonObj,String selectInfo ) {
String jsonArr_str="Null";
JSONArray jsonArr=JSONArray.fromObject(selectInfo);
ThirdPartyCommonService service=new ThirdPartyCommonService(log);
if(jsonArr.size()>0) {
for(int i=0;i<jsonArr.size();i++) {
try {
System.out.println(jsonArr.size());
JSONObject jsonObj_param=jsonArr.getJSONObject(i);
String schema=service.getSchemaToFactory(jsonObj.getString("TokenId"));
String query_sql=jsonObj_param.getString("SQL");
for(Object key:jsonObj.keySet()) {
query_sql=query_sql.replaceAll("@"+key.toString().toUpperCase()+"@", jsonObj.get(key).toString());
}
jsonArr_str=JsonUtil.setToJsonArr(super.getResultSet(query_sql, schema, new Object[] {}));
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println(e.getMessage());
jsonArr_str="Exception:"+e.getMessage();
}
}
}
return jsonArr_str;
}
/*
* 3.根据TokenId获取对应的数据库schema名称
* @param String
* @return String
* */
public String getSchemaToFactory(String factory) throws SQLException {
String query_sql=
"根据TokenId来筛选获取对应去哪个数据库环境抓数据";
String schema_qry=(String) super.getResultColumn("schema", query_sql, new Object[] {TokenId});
return schema_qry;
}
}
四、运行程序以及服务器简单jar部署
1)涉及到Maven相关配置的,如pom.xml文件,运行之前先Update Project…
2)然后直接RunAs–>Spring Boot App
3)生成jar包(war也是可以的;目前程式是前后端分离,所以jar包部署比较方便)
(1)RunAs–>Maven clean;清除之前生成的jar等相关文件。
(2)RunAs–>Maven install;生成可执行的jar包;
(3)之后会在项目下的target文件夹生成两个东西:xxx.jar和xxx.jar.original
PS:打包生成jar时Maven install会经常出现一个报错:
No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK?
解决办法:
点击项目右键,选中Build Path–>Configure Build Path;
将JRE运行环境改为JDK即可。
4)将生成的在target的两个文件上传到服务器(目前使用的是linux服务器,需要有jdk8以上的运行环境);
然后指定目录;直接后台一直运行: java -jar xxxx.jar & ;即可启动成功:
总结
此处省略三千行…;放手去盘spring boot吧