引用两个或多个数据库里的数据,项目数据库的配置方法

1、首先配置两个数据源(数据库)以及一个动态数据库: <!– 数据源1 spring自带的 –> <bean id=“dataSource” class=“org.springframework.jdbc.datasource.DriverManagerDataSource”> <!– 这里的driverClassName指的是数据库驱动这里要根据使用的数据库进行更改这里使用的SQLServer数据库 –> <property name=“driverClassName” value=“com.microsoft.sqlserver.jdbc.SQLServerDriver”></property> <!– 这里url主要说明的数据库的位置和调用那个数据库其中jdbc后面是位置而DatebaseName所说的是数据库的名称 –> <property name=“url” value=“jdbc:sqlserver://数据库ip地址:端口号;DatabaseName=数据库1名称;?useUnicode=true&amp;characterEncoding=UTF-8” /> <!– 下面的2行username和password主要是说明的登录的用户名和密码这里根据项目的不同进行修改 –> <property name=“username” value=“数据库连接账号”></property> <property name=“password” value=“数据库连接密码”></property> </bean> <!– 数据源2–> <bean id=“dataSource2” class=“org.springframework.jdbc.datasource.DriverManagerDataSource”> <!– 这里的driverClassName指的是数据库驱动这里要根据使用的数据库进行更改这里使用的SQLServer数据库 –> <property name=“driverClassName” value=“com.microsoft.sqlserver.jdbc.SQLServerDriver”></property> <!– 这里url主要说明的数据库的位置和调用那个数据库其中jdbc后面是位置而DatebaseName所说的是数据库的名称 –> <property name=“url” value=“jdbc:sqlserver://数据库ip地址:端口号;DatabaseName=数据库2名称;?useUnicode=true&amp;characterEncoding=UTF-8” /> <!– 下面的2行username和password主要是说明的登录的用户名和密码这里根据项目的不同进行修改 –> <property name=“username” value=“数据库连接账号”></property> <property name=“password” value=“数据库连接密码”></property> </bean> <!–配置动态数据库–> <bean id=“dynamicDataSource” class=“com.datasource.DynamicDataSource” > <property name=“targetDataSources”> <map key-type=“java.lang.String”> <entry value-ref=“dataSource” key=“dataSource”></entry> <entry value-ref=“dataSource2” key=“dataSource2”></entry> </map> </property> <property name=“defaultTargetDataSource” ref=“dataSource” > </property> </bean>2、sqlSessionFactory引用的数据库是动态数据库dynamicDataSource:<!– 扫描mybatis的配置结合上面的DataSource,会生成一个sqlFactory。这里一般来说不用改 –> <bean id=“sqlSessionFactory” class=“org.mybatis.spring.SqlSessionFactoryBean”> <property name=“dataSource” ref=“dynamicDataSource” /> <!– 将mybatis的配置文件引入 –> <property name=“configLocation” value=“classpath:myBatisConfig.xml”></property> </bean>4、事务管理这里要管理的也是上面配置的dynamicDataSource动态数据库:<!– 配置了事务的管理。一般来说不用改 –> <bean id=“txManager” class=“org.springframework.jdbc.datasource.DataSourceTransactionManager”> <property name=“dataSource” ref=“dynamicDataSource”></property> </bean>5、写两个数据源配置类DataSourceContextHolder.java和DynamicDataSource.java来配置数据源,利用ThreadLocal解决线程安全问题。DataSourceContextHolder 类:package com.datasource;public class DataSourceContextHolder { private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); public static void setCustomerType(String customerType) { contextHolder.set(customerType); } public static String getCustomerType() { return contextHolder.get(); } public static void clearCustomerType() { contextHolder.remove(); }}DynamicDataSource 类继承 AbstractRoutingDataSource,并实现determineCurrentLookupKey方法:package com.datasource;import java.sql.SQLFeatureNotSupportedException;import java.util.logging.Logger;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;public class DynamicDataSource extends AbstractRoutingDataSource { @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { // TODO 自动生成的方法存根 return null; } @Override protected Object determineCurrentLookupKey() { // TODO 自动生成的方法存根 return DataSourceContextHolder.getCustomerType(); }}6、最后就可以在需要切换数据库的地方使用以下方法来切换数据库了,要切换的数据库名字即之前在配置动态数据库时给引用的数据库赋的名字:DataSourceContextHolder.setCustomerType(“要切换的数据库名字”);7、下面附上我的目录结构:注:每次使用完切换数据库的方法后,系统会自动切换回默认数据库,不过这之间存在一点小延迟,会出现在调用完切换数据库的方法后,立刻去跳转到引用另外一个数据库数据的页面,系统还是使用着切换后的数据库。解决办法:可以在使用完切换数据库的方法拿到需要的数据后,再次调用 DataSourceContextHolder.setCustomerType() 方法切换回接下来需要用到的数据库。 ...

March 18, 2019 · 1 min · jiezi

Tomcat安装

本文演示Tomcat环境:CentOS7 (VMware)IP:192.168.0.133安装JDK:下载并解压jdk-8u201-linux-x64.tar.gztar zxvf jdk-8u201-linux-x64.tar.gz -C /opt/ #解压到/opt目录下cd /opt #切换目录ln -s jdk1.8.0_201 jdk #创建软连接配置环境变量:vim /etc/profile.d/jdk.sh#写入如下内容,配置jdk的环境变量,退出后保存export JAVA_HOME=/opt/jdkPATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATHCLASSPATH=$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib:$JAVA_HOME/lib/tools.jarsource /etc/profile.d/jdk.sh #声明环境变量,使全局生效java -version #显示java版本信息,检查安装是否成功安装Tomcat下载并解压apache-tomcat-8.5.38.tar.gztar -zxvf apache-tomcat-8.5.38.tar.gz -C /opt/ #解压到/opt目录cd /opt #切换目录ln -s apache-tomcat-8.5.38 tomcat #创建软连接配置环境变量:vim /etc/profile.d/tomcat.sh #创建tomcat变量文件##写入如下内容,退出并保存export TOMCAT_HOME=/opt/tomcatsource /etc/profile.d/tomcat.sh #声明环境变量使全局生效##启动Tomcat/opt/tomcat/bin/startup.sh 打开浏览器打开浏览器输入192.168.0.133:8080访问成功。开启后台控制:vim /opt/tomcat/conf/tomcat-users.xml #编辑配置文件##在最后一行的</tomcat-users>上面加上以下配置的账号密码内容,保存并退出<role rolename=“manager-gui”/><role rolename=“admin-gui”/><user username=“tomcat” password=“admin” roles=“manager-gui,admin-gui”/>Tomcat8.0开始还需要 编辑webapps/manager/META-INF/context.xml和webapps/host-manager/META-INF/context.xml来打开允许访问控制##将上面两个文件内的<Valve className=“org.apache.catalina.valves.RemoteAddrValve” allow=“127.\d+.\d+.\d+|::1|0:0:0:0:0:0:0:1” />内容删除或配置允许访问的ip/opt/tomcat/bin/shutdown.sh && /opt/tomcat/bin/startup.sh #重启tomcat访问成功。

March 12, 2019 · 1 min · jiezi

实现简单的Tomcat | Tomcat原理学习(1)

缘起用了那么久tomcat,突然觉得自己对它或许并没有想象中的那么熟悉,所以趁着放假我研究了一下这只小猫咪,实现了自己的小tomcat,写出这篇文章同大家一起分享!照例附上github链接。项目结构项目结构如下:实现细节创建MyRequest对象首先创建自定义的请求类,其中定义url与method两个属性,表示请求的url以及请求的方式。其构造函数需要传入一个输入流,该输入流通过客户端的套接字对象得到。输入流中的内容为浏览器传入的http请求头,格式如下:GET /student HTTP/1.1Host: localhost:8080Connection: keep-alivePragma: no-cacheCache-Control: no-cacheUpgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8Accept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9Cookie: Hm_lvt_eaa22075ffedfde4dc734cdbc709273d=1549006558; _ga=GA1.1.777543965.1549006558通过对以上内容的分割与截取,我们可以得到该请求的url以及请求的方式。package tomcat.dome;import java.io.IOException;import java.io.InputStream;//实现自己的请求类public class MyRequest { //请求的url private String url; //请求的方法类型 private String method; //构造函数 传入一个输入流 public MyRequest(InputStream inputStream) throws IOException { //用于存放http请求内容的容器 StringBuilder httpRequest=new StringBuilder(); //用于从输入流中读取数据的字节数组 byte[]httpRequestByte=new byte[1024]; int length=0; //将输入流中的内容读到字节数组中,并且对长度进行判断 if((length=inputStream.read(httpRequestByte))>0) { //证明输入流中有内容,则将字节数组添加到容器中 httpRequest.append(new String(httpRequestByte,0,length)); } //将容器中的内容打印出来 System.out.println(“httpRequest = [ “+httpRequest+” ]”); //从httpRequest中获取url,method存储到myRequest中 String httpHead=httpRequest.toString().split("\n")[0]; url=httpHead.split("\s")[1]; method=httpHead.split("\s")[0]; System.out.println(“MyRequests = [ “+this+” ]”); } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getMethod() { return method; } public void setMethod(String method) { this.method = method; } }创建MyResponse对象创建自定义的响应类,构造函数需要传入由客户端的套接字获取的输出流。定义其write方法,像浏览器写出一个响应头,并且包含我们要写出的内容content。package tomcat.dome;import java.io.IOException;import java.io.OutputStream;//实现自己的响应类public class MyResponse { //定义输出流 private OutputStream outputStream; //构造函数 传入输出流 public MyResponse(OutputStream outputStream) { this.outputStream=outputStream; } //创建写出方法 public void write(String content)throws IOException{ //用来存放要写出数据的容器 StringBuffer stringBuffer=new StringBuffer(); stringBuffer.append(“HTTP/1.1 200 OK\r\n”) .append(“Content-type:text/html\r\n”) .append("\r\n") .append("<html><head><title>Hello World</title></head><body>") .append(content) .append("</body><html>"); //转换成字节数组 并进行写出 outputStream.write(stringBuffer.toString().getBytes()); //System.out.println(“sss”); outputStream.close(); } }创建MyServlet对象由于我们自己写一个Servlet的时候需要继承HttpServlet,因此在这里首先定义了一个抽象类——MyServlet对象。在其中定义了两个需要子类实现的抽象方法doGet和doSet。并且创建了一个service方法,通过对传入的request对象的请求方式进行判断,确定调用的是doGet方法或是doPost方法。package tomcat.dome;//写一个抽象类作为servlet的父类public abstract class MyServlet { //需要子类实现的抽象方法 protected abstract void doGet(MyRequest request,MyResponse response); protected abstract void doPost(MyRequest request,MyResponse response); //父类自己的方法 //父类的service方法对传入的request以及response //的方法类型进行判断,由此调用doGet或doPost方法 public void service(MyRequest request,MyResponse response) throws NoSuchMethodException { if(request.getMethod().equalsIgnoreCase(“POST”)) { doPost(request, response); }else if(request.getMethod().equalsIgnoreCase(“GET”)) { doGet(request, response); }else { throw new NoSuchMethodException(“not support”); } }}创建业务相关的Servlet这里我创建了两个业务相关类:StudentServlet和TeacherServlet。package tomcat.dome;import java.io.IOException;//实现自己业务相关的Servletpublic class StudentServlet extends MyServlet{ @Override protected void doGet(MyRequest request, MyResponse response) { //利用response中的输出流 写出内容 try { //System.out.println("!!!!!!!!!!!!!!!!!!"); response.write(“I am a student.”); //System.out.println(“9999999999999999”); }catch(IOException e) { e.printStackTrace(); } } @Override protected void doPost(MyRequest request, MyResponse response) { //利用response中的输出流 写出内容 try { response.write(“I am a student.”); }catch(IOException e) { e.printStackTrace(); } }}package tomcat.dome;import java.io.IOException;//实现自己业务相关的Servletpublic class TeacherServlet extends MyServlet{ @Override protected void doGet(MyRequest request, MyResponse response) { //利用response中的输出流 写出内容 try { response.write(“I am a teacher.”); }catch(IOException e) { e.printStackTrace(); } } @Override protected void doPost(MyRequest request, MyResponse response) { //利用response中的输出流 写出内容 try { response.write(“I am a teacher.”); }catch(IOException e) { e.printStackTrace(); } }}创建映射关系结构ServletMapping该结构实现的是请求的url与具体的Servlet之间的关系映射。package tomcat.dome;//请求url与项目中的servlet的映射关系public class ServletMapping { //servlet的名字 private String servletName; //请求的url private String url; //servlet类 private String clazz; public String getServletName() { return servletName; } public void setServletName(String servletName) { this.servletName = servletName; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getClazz() { return clazz; } public void setClazz(String clazz) { this.clazz = clazz; } public ServletMapping(String servletName, String url, String clazz) { super(); this.servletName = servletName; this.url = url; this.clazz = clazz; }}映射关系配置对象ServletMappingConfig配置类中定义了一个列表,里面存储着项目中的映射关系。package tomcat.dome;import java.util.ArrayList;import java.util.List;//创建一个存储有请求路径与servlet的对应关系的 映射关系配置类public class ServletMappingConfig { //使用一个list类型 里面存储的是映射关系类Mapping public static List<ServletMapping>servletMappings=new ArrayList<>(16); //向其中添加映射关系 static { servletMappings.add(new ServletMapping(“student”,"/student", “tomcat.dome.StudentServlet”)); servletMappings.add(new ServletMapping(“teacher”,"/teacher", “tomcat.dome.TeacherServlet”)); }}主角登场 MyTomcat!在服务端MyTomcat中主要做了如下几件事情:1)初始化请求的映射关系。2)创建服务端套接字,并绑定某个端口。3)进入循环,用户接受客户端的链接。4)通过客户端套接字创建request与response对象。5)根据request对象的请求方式调用相应的方法。6)启动MyTomcat!package tomcat.dome;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.ServerSocket;import java.net.Socket;import java.util.HashMap;import java.util.Map;//Tomcat服务器类 编写对请求做分发处理的相关逻辑public class MyTomcat { //端口号 private int port=8080; //用于存放请求路径与对应的servlet类的请求映射关系的map //相应的信息从配置类中获取 private Map<String, String>urlServletMap=new HashMap<>(16); //构造方法 public MyTomcat(int port) { this.port=port; } //tomcat服务器的启动方法 public void start() { //初始化请求映射关系 initServletMapping(); //服务端的套接字 ServerSocket serverSocket=null; try { //创建绑定到某个端口的服务端套接字 serverSocket=new ServerSocket(port); System.out.println(“MyTomcat begin start…”); //循环 用于接收客户端 while(true) { //接收到的客户端的套接字 Socket socket=serverSocket.accept(); //获取客户端的输入输出流 InputStream inputStream=socket.getInputStream(); OutputStream outputStream=socket.getOutputStream(); //通过输入输出流创建请求与响应对象 MyRequest request=new MyRequest(inputStream); MyResponse response=new MyResponse(outputStream); //根据请求对象的method分发请求 调用相应的方法 dispatch(request, response); //关闭客户端套接字 socket.close(); } } catch (IOException e) { e.printStackTrace(); } } //初始化请求映射关系,相关信息从配置类中获取 private void initServletMapping() { for(ServletMapping servletMapping:ServletMappingConfig.servletMappings) { urlServletMap.put(servletMapping.getUrl(), servletMapping.getClazz()); } } //通过当前的request以及response对象分发请求 private void dispatch(MyRequest request,MyResponse response) { //根据请求的url获取对应的servlet类的string String clazz=urlServletMap.get(request.getUrl()); //System.out.println("====="+clazz); try { //通过类的string将其转化为对象 Class servletClass=Class.forName(“tomcat.dome.StudentServlet”); //实例化一个对象 MyServlet myServlet=(MyServlet)servletClass.newInstance(); //调用父类方法,根据request的method对调用方法进行判断 //完成对myServlet中doGet与doPost方法的调用 myServlet.service(request, response); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } } //main方法 直接启动tomcat服务器 public static void main(String[] args) { new MyTomcat(8080).start(); } }测试结果 ...

March 10, 2019 · 3 min · jiezi

Eclipse 配置 Tomcat 服务器和发布 Web 应用

步骤1:服务器运行环境配置依次点击:Window/Preferences/Server/Runtime Environments,选择已安装的Tomcat 版本,然后选择其安装路径,如图。出现下面这个就成功完成步骤一了。步骤2:设置发布位置双击这个:找到 Server Locations 按图中顺序设置:部署位置 webapps 文件夹在 Tomcat 安装目录里。 如果是灰色的不能更改,需要把 Tomcat 下的所有项目移除,并右击,clean 之后方可设置,启动后将可以更改。步骤2完成之后Tomcat的配置就完成了。发布Web应用右击项目—> Run As —> Run on Server —>选择服务器 —> 添加资源 —> 完成

March 9, 2019 · 1 min · jiezi

内网穿透与反向代理,浅谈前后台分离

自去年毕业来到杭州,想想也该有大半年了,本身是软件工程的科班出身,在校时理论掌握的还可以,但应用到实践当中时,有些还是不大理解,于是,不停地向带我的人请教,毕竟,三人行,必有我师焉。经过一段时间理论加实践,多少也掌握了其中的门路。前端和后端(服务器端、客户端)分离前后端不分离在从业的过程中,也和其他程序员交流过,他们很多人都没有前后端(服务器和客户端)分离,而是前后端一起做掉。如果前后端不分离,此时的服务器端主要是指java代码,客户端主要是指jsp,通过spring MVC 将数据封装到ResponseBody中,再返回给jSP。JSP拿到数据,渲染页面。这里 不需要考虑端口号的问题。比如: /** * Created By zby on 16:03 2019/3/5 / @RequestMapping(value = “/”, method = RequestMethod.GET) @ResponseBody public Result fun() { return null; }前后端分离当然,前后端分离时,后端还是以java代码为主,前端就变化多端了。. 后端java通过springMVC的Rest模式的Controller层,接收前端页面传来的接口和参数,经过一系列的入参校验,调用事务层(也就是service层)这里主要是hibernate(mybatis)的事务层,实现数据库的事务操作。再调用dao(data Access object)层实现事务的原子性操作,即将顺时态的java对象转化为持久状态的数据库对象。层层深入,层层返回,将通过Result回传给前端。. 前端前端主要用h5进行页面布局,CSS3实现页面的美化。JavaScript配合jQuery调用后端的接口,传递参数和获取后端回传的数据。通过vue.js实现回传的数据的双向绑定。还可能涉及到其他框架,比如页面布局的bootstrap,数据table方式展示的jqgrid等等。前后端分离,如何实现数据交互我们将写好的java代码部署在服务器上,比如Tomcat、Jboss主流服务器。这里以Tomcat来讲解,我们将项目部署在Tomcat的上,具体如何部署Tomcat,可以参考这篇教程,Tomcat8权威指南。我们现在一般在maven中以插件的方式配置Tomcat,便于本地测试,路径为根路径,如以下代码: <build> <defaultGoal>install</defaultGoal> //maven生成的war生成的名字 <finalName>cloudCodeSaleManager</finalName> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> </resources> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <port>58081</port> <path>/</path> <uriEncoding>UTF-8</uriEncoding> <finalName>zfounder-custmom-member-portal</finalName> <server>tomcat7</server> </configuration> </plugin> </plugins> </build>在真实的项目中,一般会有测试服和正式服,测试服我们用户的测试数据库和测试服务器,正式服我们用到的是正式数据库和正式服务器,有人说,这样输简直是废话。但是,我们测试数据库和正式数据库是不一样的,因而,如果都写在同一个配置文件中,修改势必麻烦。因而,我们可以在打包时,会有测试包和正式包,这里就涉及到maven的profile的配置文件(是在pom中配置): <profiles> <profile> <id>dev</id> <activation> <activeByDefault>true</activeByDefault> </activation> <build> <filters> <filter>../../platform-dev.properties</filter> </filters> </build> </profile> <profile> <id>prd</id> <build> <filters> <filter>../../platform-prd.properties</filter> </filters> </build> </profile> </profiles>我们Tomcat启动后,访问后端接口(url)的格式如下:scheme://host.domain:port/path/filenamescheme - 定义因特网服务的类型。最常见的类型是 httphost - 定义域主机(http 的默认主机是 www)domain - 定义因特网域名,比如 runoob.com:port - 定义主机上的端口号(http 的默认端口号是 80)path - 定义服务器上的路径(如果省略,则文档必须位于网站的根目录中)。filename - 定义文档/资源的名称当然,如果没有域名的话,我们想要访问本地,也可以是这样的:http://ip:port/path/filename这里的ip涉及到内网和本机地址。内网也就是局域网,一般以192.168..打头。本机地址是:127.0.0.1。它们两个有什么区别呢?假设访问我的server_path如下所示constant = { dev: { server_path: “http://127.0.0.1:58081/”, imgPre: “http://web.cs.wentonghuishou.com/", commonParams: {} },}_env = “dev”;window.constant = constant[_env];我做后端Java的,开启了Tomcat。我的同事是做前端的,他用上面的server_path访问我,也就是说,想通过我本机ip请求我的接口,是没办法访问我后端的接口。因为他,这是我本机的ip,只有我个人才能访问。相反,我自己是可以访问的。如图所示:如果他把server_path改成了server_path: “http://192.168.40.177:58081/",,那么,他想通过局域网访问我的接口,这是可以访问我的。因为,我们同处在这个局域网下的。如图所示:外网如何访问,也就是,内网穿透假如,我和我的同事,不在同一局域网,但他,想访问我后端的接口,这时该怎么办?应该是需要摆脱网域限制,能够访问我的内网,也就是访问的本机。这时,就出现了,内网穿透的软件,比如ngrok,小米求等。小米球可以实现内网穿透,他是怎么实现内网穿透,主要是通过域名的反向代理,这也就是所谓的反向代理。其实,反向代理没那么高大上,不要被它吓到了。当然,这里需要输入端口号,这里前端的hbuilder的端口号,也就是8020端口号。为什么需要端口号,端口号能够确定本机唯一的进程。比如mysql的3306端口号,Tomcat的80端口号等。为什么是前端的端口号,因为我们首先访问的是页面,页面通过server_path来访问后端接口,这里我们不需要考虑这方面的。小米求的配置如下,这里是免费版的:当我们,在浏览器的地址栏输入http://zby.ngrok.xiaomiqiu.cn…,你会发现,它能访问到我的前端页面,并调用了我后端的接口,这就实现了ip的反向代理。域名解析也是同样的道理,利用了ip的反向代理。如图所示: ...

March 5, 2019 · 1 min · jiezi

一个奇怪的问题:tomcat 栈溢出 StackOverflowError错误

一个栈溢出错误:ava.lang.StackOverflowError at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.findNext(ApplicationHttpRequest.java:996) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.findNext(ApplicationHttpRequest.java:996) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.hasMoreElements(ApplicationHttpRequest.java:971) at >>>>>>>>>>>>>>>>>>> 中间省略几百行同样的内容 <<<<<<<<<<<<<<<<<<<<<<< org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.findNext(ApplicationHttpRequest.java:996) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.hasMoreElements(ApplicationHttpRequest.java:971) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.findNext(ApplicationHttpRequest.java:996) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.hasMoreElements(ApplicationHttpRequest.java:971) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.findNext(ApplicationHttpRequest.java:996) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.hasMoreElements(ApplicationHttpRequest.java:971) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.findNext(ApplicationHttpRequest.java:996) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.hasMoreElements(ApplicationHttpRequest.java:971) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.findNext(ApplicationHttpRequest.java:996) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.hasMoreElements(ApplicationHttpRequest.java:971) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.findNext(ApplicationHttpRequest.java:996) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.hasMoreElements(ApplicationHttpRequest.java:971) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.findNext(ApplicationHttpRequest.java:996) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.hasMoreElements(ApplicationHttpRequest.java:971) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.findNext(ApplicationHttpRequest.java:996) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.hasMoreElements(ApplicationHttpRequest.java:971) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.findNext(ApplicationHttpRequest.java:996) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.hasMoreElements(ApplicationHttpRequest.java:971) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.findNext(ApplicationHttpRequest.java:996) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.hasMoreElements(ApplicationHttpRequest.java:971) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.findNext(ApplicationHttpRequest.java:996) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.hasMoreElements(ApplicationHttpRequest.java:971) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.findNext(ApplicationHttpRequest.java:996) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.hasMoreElements(ApplicationHttpRequest.java:971) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.findNext(ApplicationHttpRequest.java:996) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.hasMoreElements(ApplicationHttpRequest.java:971) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.findNext(ApplicationHttpRequest.java:996) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.hasMoreElements(ApplicationHttpRequest.java:971) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.findNext(ApplicationHttpRequest.java:996) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.hasMoreElements(ApplicationHttpRequest.java:971) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.findNext(ApplicationHttpRequest.java:996) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.hasMoreElements(ApplicationHttpRequest.java:971) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.findNext(ApplicationHttpRequest.java:996) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.hasMoreElements(ApplicationHttpRequest.java:971) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.findNext(ApplicationHttpRequest.java:996) at org.apache.catalina.core.ApplicationHttpRequest$AttributeNamesEnumerator.hasMoreElements(ApplicationHttpRequest.java:971) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:873) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967) at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:869) at javax.servlet.http.HttpServlet.service(HttpServlet.java:661) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843) at javax.servlet.http.HttpServlet.service(HttpServlet.java:742) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.logging.log4j.web.Log4jServletFilter.doFilter(Log4jServletFilter.java:64) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:728)tomcat版本是8.5.38版本,spring是4.2.6.RELEASE,出错的代码也找到了:DispatcherServlet:protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { if (logger.isDebugEnabled()) { String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : “”; logger.debug(“DispatcherServlet with name ‘” + getServletName() + “’” + resumed + " processing " + request.getMethod() + " request for [" + getRequestUri(request) + “]”); } // Keep a snapshot of the request attributes in case of an include, // to be able to restore the original attributes after the include. Map<String, Object> attributesSnapshot = null; if (WebUtils.isIncludeRequest(request)) { attributesSnapshot = new HashMap<String, Object>(); Enumeration<?> attrNames = request.getAttributeNames(); while (attrNames.hasMoreElements()) { //这个地方死循环了 String attrName = (String) attrNames.nextElement(); if (this.cleanupAfterInclude || attrName.startsWith(“org.springframework.web.servlet”)) { attributesSnapshot.put(attrName, request.getAttribute(attrName)); } } } …..} ApplicationHttpRequest中的内部类AttributeNamesEnumerator: @Override public boolean hasMoreElements() { return ((pos != last) || (next != null) || ((next = findNext()) != null)); } protected String findNext() { String result = null; while ((result == null) && (parentEnumeration.hasMoreElements())) { String current = parentEnumeration.nextElement(); if (!isSpecial(current)) { result = current; } } return result; }从错误日志上来看,先调用hasMoreElements,再调用findNext, 以此为循环,直到栈溢出。不知道怎么回事,只找到一篇tomcat的issue,有知道的指点一些。 ...

February 28, 2019 · 2 min · jiezi

SpringBoot 实战 (十一) | 整合数据缓存 Cache

微信公众号:一个优秀的废人如有问题或建议,请后台留言,我会尽力解决你的问题。前言如题,今天介绍 SpringBoot 的数据缓存。做过开发的都知道程序的瓶颈在于数据库,我们也知道内存的速度是大大快于硬盘的,当需要重复获取相同数据时,一次又一次的请求数据库或者远程服务,导致大量时间耗费在数据库查询或远程方法调用上,导致性能的恶化,这便是数据缓存要解决的问题。Spring 的缓存支持Spring 定义了 org.springframework.cache.CacheManager 和 org.springframework.cache.Cache 接口用于统一不同的缓存技术。其中,CacheManager 是 Spring 提供的各种缓存技术的抽象接口,Cache 接口则是包含了缓存的各种操作(增加,删除,获取缓存,一般不会直接和此接口打交道)。1、Spring 支持的 CacheManager 针对不同的缓存技术,实现了不同的 CacheManager ,Spring 定义了下表所示的 CacheManager:CacheManager描述SimpleCacheManager使用简单的 Collection 来存储缓存,主要用于测试ConcurrentMapCacheManager使用 ConcurrentMap 来存储缓存NoOpCacheManager仅测试用途,不会实际缓存数据EhCacheCacheManager使用 EhCache 作为缓存技术GuavaCacheManager使用 Google Guava 的 GuavaCache 作为缓存技术HazelcastCacheManager使用 Hazelcast 作为缓存技术JCacheCacheManager支持 JCache(JSR-107) 标准的实现作为缓存技术,如 ApacheCommonsJCSRedisCacheManager使用 Redis 作为缓存技术在使用以上任意一个实现的 CacheManager 的时候,需注册实现的 CacheManager 的 Bean,如:@Beanpublic EhCacheCacheManager cacheManager(CacheManager ehCacheCacheManager){ return new EhCacheCacheManager(ehCacheCacheManager);}注意,每种缓存技术都有很多的额外配置,但配置 cacheManager 是必不可少的。2、声明式缓存注解Spring 提供了 4 个注解来声明缓存规则(又是使用注解式的 AOP 的一个例子)。4 个注解如下表示:注解解释@Cacheable在方法执行前 Spring 先查看缓存中是否有数据,若有,则直接返回缓存数据;若无数据,调用方法将方法返回值放入缓存中@CachePut无论怎样,都会将方法的返回值放到缓存中。@CacheEvict将一条或多条数据从缓存中删除@Caching可以通过 @Caching 注解组合多个注解策略在一个方法上@Cacheable、@CachePut、@CacheEvict 都有 value 属性,指定的是要使用的缓存名称;key 属性指定的是数据在缓存中存储的键。3、开启声明式缓存支持开启声明式缓存很简单,只需在配置类上使用 @EnableCaching 注解即可,例如:@Configuration@EnableCachingpublic class AppConfig{}SpringBoot 的支持在 Spring 中使用缓存技术的关键是配置 CacheManager ,而 SpringBoot 为我们配置了多个 CacheManager 的实现。它的自动配置放在 org.springframework.boot.autoconfigure.cache 包中。在不做任何配置的情况下,默认使用的是 SimpleCacheConfiguration ,即使用 ConcurrentMapCacheManager。SpringBoot 支持以前缀来配置缓存。例如:spring.cache.type= # 可选 generic、ehcache、hazelcast、infinispan、jcache、redis、guava、simple、nonespring.cache.cache-names= # 程序启动时创建的缓存名称spring.cache.ehcache.config= # ehcache 配置文件的地址spring.cache.hazelcast.config= # hazelcast配置文件的地址spring.cache.infinispan.config= # infinispan配置文件的地址spring.cache.jcache.config= # jcache配置文件的地址spring.cache.jcache.provider= # 当多个 jcache 实现在类路径的时候,指定 jcache 实现# 等等。。。在 SpringBoot 环境下,使用缓存技术只需要在项目中导入相关缓存技术的依赖包,并在配置类中使用 @EnableCaching 开启缓存支持即可。代码实现本文将以 SpringBoot 默认的 ConcurrentMapCacheManager 作为缓存技术,演示 @Cacheable、@CachePut、@CacheEvict。1、准备工作IDEAJDK 1.8SpringBoot 2.1.32、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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> <relativePath/> <!– lookup parent from repository –> </parent> <groupId>com.nasus</groupId> <artifactId>cache</artifactId> <version>0.0.1-SNAPSHOT</version> <name>cache</name> <description>cache Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!– cache 依赖 –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!– JPA 依赖 –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!– web 启动类 –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!– mysql 数据库连接类 –> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!– lombok 依赖,简化实体 –> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!– 单元测试类 –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>注释很清楚,无需多言。不会就谷歌一下。3、Application.yaml 文件配置spring: # 数据库相关 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=true username: root password: 123456 # jpa 相关 jpa: hibernate: ddl-auto: update # ddl-auto: 设为 create 表示每次都重新建表 show-sql: true4、实体类package com.nasus.cache.entity;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.Id;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data@Entity@AllArgsConstructor@NoArgsConstructorpublic class Student { @Id @GeneratedValue private Integer id; private String name; private Integer age;}5、dao 层package com.nasus.cache.repository;import com.nasus.cache.entity.Student;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.stereotype.Repository;@Repositorypublic interface StudentRepository extends JpaRepository<Student,Integer> {}6、service 层package com.nasus.cache.service;import com.nasus.cache.entity.Student;public interface StudentService { public Student saveStudent(Student student); public void deleteStudentById(Integer id); public Student findStudentById(Integer id);}实现类:package com.nasus.cache.service.impl;import com.nasus.cache.entity.Student;import com.nasus.cache.repository.StudentRepository;import com.nasus.cache.service.StudentService;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cache.annotation.CacheEvict;import org.springframework.cache.annotation.CachePut;import org.springframework.cache.annotation.Cacheable;import org.springframework.stereotype.Service;@Servicepublic class StudentServiceImpl implements StudentService { // 使用 slf4j 作为日志框架 private static final Logger LOGGER = LoggerFactory.getLogger(StudentServiceImpl.class); @Autowired private StudentRepository studentRepository; @Override @CachePut(value = “student”,key = “#student.id”) // @CachePut 缓存新增的或更新的数据到缓存,其中缓存名称为 student 数据的 key 是 student 的 id public Student saveStudent(Student student) { Student s = studentRepository.save(student); LOGGER.info(“为id、key 为{}的数据做了缓存”, s.getId()); return s; } @Override @CacheEvict(value = “student”) // @CacheEvict 从缓存 student 中删除 key 为 id 的数据 public void deleteStudentById(Integer id) { LOGGER.info(“删除了id、key 为{}的数据缓存”, id); //studentRepository.deleteById(id); } @Override @Cacheable(value = “student”,key = “#id”) // @Cacheable 缓存 key 为 id 的数据到缓存 student 中 public Student findStudentById(Integer id) { Student s = studentRepository.findById(id).get(); LOGGER.info(“为id、key 为{}的数据做了缓存”, id); return s; }}代码讲解看注释,很详细。7、controller 层package com.nasus.cache.controller;import com.nasus.cache.entity.Student;import com.nasus.cache.service.StudentService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.DeleteMapping;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.Mapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/student”)public class StudentController { @Autowired private StudentService studentService; @PostMapping("/put”) public Student saveStudent(@RequestBody Student student){ return studentService.saveStudent(student); } @DeleteMapping("/evit/{id}”) public void deleteStudentById(@PathVariable(“id”) Integer id){ studentService.deleteStudentById(id); } @GetMapping("/able/{id}") public Student findStudentById(@PathVariable(“id”) Integer id){ return studentService.findStudentById(id); }}8、application 开启缓存功能package com.nasus.cache;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cache.annotation.EnableCaching;@EnableCaching // 开启缓存功能@SpringBootApplicationpublic class CacheApplication { public static void main(String[] args) { SpringApplication.run(CacheApplication.class, args); }}测试测试前,先看一眼数据库当前的数据,如下:1、测试 @Cacheable 访问 http://localhost:8080/student/able/2 控制台打印出了 SQL 查询语句,以及指定日志。说明这一次程序是直接查询数据库得到的结果。2019-02-21 22:54:54.651 INFO 1564 — [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 11 msHibernate: select student0_.id as id1_0_0_, student0_.age as age2_0_0_, student0_.name as name3_0_0_ from student student0_ where student0_.id=?2019-02-21 22:54:59.725 INFO 1564 — [nio-8080-exec-1] c.n.c.service.impl.StudentServiceImpl : 为id、key 为2的数据做了缓存postman 第一次测试结果 :再次访问 http://localhost:8080/student/able/2 结果如下图。但控制台无 SQL 语句打印,也无为id、key 为2的数据做了缓存这句话输出。说明 @Cacheable 确实做了数据缓存,第二次的测试结果是从数据缓存中获取的,并没有直接查数据库。2、测试 @CachePut如下图,postman 访问 http://localhost:8080/student/put 插入数据:下面是控制台打印出了 SQL Insert 插入语句,以及指定日志。说明程序做了缓存。Hibernate: insert into student (age, name, id) values (?, ?, ?)2019-02-21 23:12:03.688 INFO 1564 — [nio-8080-exec-8] c.n.c.service.impl.StudentServiceImpl : 为id、key 为4的数据做了缓存插入数据返回的结果:数据库中的结果:访问 http://localhost:8080/student/able/4 Postman 结果如下图。控制台无输出,验证了 @CachePut 确实做了缓存,下图数据是从缓存中获取的。3、测试 @CacheEvict postman 访问 http://localhost:8080/student/able/3 为 id = 3 的数据做缓存。postman 再次访问 http://localhost:8080/student/able/3 确认数据是从缓存中获取的。postman 访问 http://localhost:8080/student/evit/3 从缓存中删除 key 为 3 的缓存数据:Hibernate: select student0_.id as id1_0_0_, student0_.age as age2_0_0_, student0_.name as name3_0_0_ from student student0_ where student0_.id=?2019-02-21 23:26:08.516 INFO 8612 — [nio-8080-exec-2] c.n.c.service.impl.StudentServiceImpl : 为id、key 为3的数据做了缓存2019-02-21 23:27:01.508 INFO 8612 — [nio-8080-exec-4] c.n.c.service.impl.StudentServiceImpl : 删除了id、key 为3的数据缓存再次 postman 访问 http://localhost:8080/student/able/3 观察后台,重新做了数据缓存:Hibernate: select student0_.id as id1_0_0_, student0_.age as age2_0_0_, student0_.name as name3_0_0_ from student student0_ where student0_.id=?2019-02-21 23:27:12.320 INFO 8612 — [nio-8080-exec-5] c.n.c.service.impl.StudentServiceImpl : 为id、key 为3的数据做了缓存这一套测试流程下来,证明了 @CacheEvict 确实删除了数据缓存。源码下载https://github.com/turoDog/Demo/tree/master/springboot_cache_demo切换缓存技术切换缓存技术除了在 pom 文件加入相关依赖包配置以外,使用方式与上面的代码演示一样。1、切换 EhCache 在 pom 中添加 Encache 依赖:<!– EhCache 依赖 –><dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId></dependency>Ehcache 所需配置文件 ehcache.xml 只需放在类路径(resource 目录)下,SpringBoot 会自动扫描,如:<?xml version=“1.0” encoding=“UTF-8”><ehcache> <cache name=“student” maxElementsInMmory=“1000”><ehcache>SpringBoot 会自动配置 EhcacheManager 的 Bean。2、切换 Guava只需在 pom 中加入 Guava 依赖即可:<!– GuavaCache 依赖 –><dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>18.0</version></dependency>SpringBoot 会自动配置 GuavaCacheManager 的 Bean。3、切换 RedisCache与 Guava 一样,只需在 pom 加入依赖即可:<!– cache 依赖 –><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId></dependency>SpringBoot 会自动配置 RedisCacheManager 以及 RedisTemplate 的 Bean。此外,切换其他缓存技术的方式也是类似。这里不做赘述。后语以上为 SpringBoot 数据缓存的教程。如果本文对你哪怕有一丁点帮助,请帮忙点好看。你的好看是我坚持写作的动力。另外,关注之后在发送 1024 可领取免费学习资料。资料内容详情请看这篇旧文:Python、C++、Java、Linux、Go、前端、算法资料分享 ...

February 23, 2019 · 4 min · jiezi

SpringBoot 实战 (十) | 声明式事务

微信公众号:一个优秀的废人如有问题或建议,请后台留言,我会尽力解决你的问题。前言如题,今天介绍 SpringBoot 的 声明式事务。Spring 的事务机制所有的数据访问技术都有事务处理机制,这些技术提供了 API 用于开启事务、提交事务来完成数据操作,或者在发生错误时回滚数据。而 Spring 的事务机制是用统一的机制来处理不同数据访问技术的事务处理,Spring 的事务机制提供了一个 PlatformTransactionManager 接口,不同的数据访问技术的事务使用不同的接口实现,如下表:数据访问技术实现JDBCDataSourceTransactionManagerJPAJPATransactionManagerHibernateHibernateTransactionManagerJDOJdoTransactionManager分布式事务JtaTransactionManager声明式事务Spring 支持声明式事务,即使用注解来选择需要使用事务的方法,他使用 @Transactional 注解在方法上表明该方法需要事务支持。被注解的方法在被调用时,Spring 开启一个新的事务,当方法无异常运行结束后,Spring 会提交这个事务。如:@Transactionalpublic void saveStudent(Student student){ // 数据库操作}注意,@Transactional 注解来自于 org.springframework.transcation.annotation 包,而不是 javax.transaction。Spring 提供一个 @EnableTranscationManagement 注解在配置类上来开启声明式事务的支持。使用了 @EnableTranscationManagement 后,Spring 容器会自动扫描注解 @Transactional 的方法与类。@EnableTranscationManagement 的使用方式如下:@Configuration@EnableTranscationManagement public class AppConfig{}注解事务行为@Transactional 有如下表所示的属性来定制事务行为。属性含义propagation事务传播行为isolation事务隔离级别readOnly事务的读写性,boolean型timeout超时时间,int型,以秒为单位。rollbackFor一组异常类,遇到时回滚。(rollbackFor={SQLException.class})rollbackForCalssName一组异常类名,遇到回滚,类型为 string[]noRollbackFor一组异常类,遇到不回滚norollbackForCalssName一组异常类名,遇到时不回滚。类级别使用 @Transactional@Transactional 不仅可以注解在方法上,还可以注解在类上。注解在类上时意味着此类的所有 public 方法都是开启事务的。如果类级别和方法级别同时使用了 @Transactional 注解,则使用在类级别的注解会重载方法级别的注解。SpringBoot 的事务支持自动配置的事务管理器在使用 JDBC 作为数据访问技术时,配置定义如下:@Bean@ConditionalOnMissingBean@ConditionalOnBean(DataSource.class)public PlatformTransactionManager transactionManager(){ return new DataSourceTransactionManager(this.dataSource)}在使用 JPA 作为数据访问技术时,配置定义如下:@Bean@ConditionalOnMissingBean(PlatformTransactionManager.class)public PlatformTransactionManager transactionManager(){ return new JpaTransactionManager()}自动开启注解事务的支持SpringBoot 专门用于配置事务的类为 org.springframework.boot.autoconfigure.transcation.TransactionAutoConfiguration,此配置类依赖于 JpaBaseConfiguration 和 DataSourceTransactionManagerAutoConfiguration。而在 DataSourceTransactionManagerAutoConfiguration 配置里还开启了对声明式事务的支持,代码如下:@ConditionalOnMissingBean(AbstractTransactionManagementConfiguration.class)@Configuration@EnableTransactionManagementprotected static class TransactionManagementConfiguration{}所以在 SpringBoot 中,无须显式开启使用 @EnableTransactionManagement 注解。实战演示如何使用 Transactional 使用异常导致数据回滚与使用异常导致数据不回滚。准备工作:SpringBoot 2.1.3JDK 1.8IDEApom.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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> <relativePath/> <!– lookup parent from repository –> </parent> <groupId>com.nasus</groupId> <artifactId>transaction</artifactId> <version>0.0.1-SNAPSHOT</version> <name>transaction</name> <description>transaction Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!– JPA 相关 –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!– web 启动类 –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!– mysql 连接类 –> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!– lombok 插件,简化实体代码 –> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.20</version> </dependency> <!– 单元测试 –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>代码注释很清楚,没啥好说的。application.yaml 配置:spring: # \u6570\u636E\u5E93\u76F8\u5173 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=true username: root password: 123456 # jpa \u76F8\u5173 jpa: hibernate: ddl-auto: update # ddl-auto:\u8BBE\u4E3A create \u8868\u793A\u6BCF\u6B21\u90FD\u91CD\u65B0\u5EFA\u8868 show-sql: true实体类:import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.Id;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data@Entity@AllArgsConstructor@NoArgsConstructorpublic class Student { @Id @GeneratedValue private Integer id; private String name; private Integer age;}dao 层import com.nasus.transaction.domain.Student;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.stereotype.Repository;@Repositorypublic interface StudentRepository extends JpaRepository<Student, Integer> {}service 层import com.nasus.transaction.domain.Student;public interface StudentService { Student saveStudentWithRollBack(Student student); Student saveStudentWithoutRollBack(Student student);}实现类:import com.nasus.transaction.domain.Student;import com.nasus.transaction.repository.StudentRepository;import com.nasus.transaction.service.StudentService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;@Servicepublic class StudentServiceImpl implements StudentService { @Autowired // 直接注入 StudentRepository 的 bean private StudentRepository studentRepository; // 使用 @Transactional 注解的 rollbackFor 属性,指定特定异常时,触发回滚 @Transactional(rollbackFor = {IllegalArgumentException.class}) @Override public Student saveStudentWithRollBack(Student student) { Student s = studentRepository.save(student); if (“高斯林”.equals(s.getName())){ //硬编码,手动触发异常 throw new IllegalArgumentException(“高斯林已存在,数据将回滚”); } return s; } // 使用 @Transactional 注解的 noRollbackFor 属性,指定特定异常时,不触发回滚 @Transactional(noRollbackFor = {IllegalArgumentException.class}) @Override public Student saveStudentWithoutRollBack(Student student) { Student s = studentRepository.save(student); if (“高斯林”.equals(s.getName())){ throw new IllegalArgumentException(“高斯林已存在,数据将不会回滚”); } return s; }}代码注释同样很清楚,没啥好说的。controller 层import com.nasus.transaction.domain.Student;import com.nasus.transaction.service.StudentService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/student”)public class StudentController { // 注入 studentservice 的 bean @Autowired private StudentService studentService; // 测试回滚情况 @PostMapping("/withRollBack”) public Student saveStudentWithRollBack(@RequestBody Student student){ return studentService.saveStudentWithRollBack(student); } // 测试不回滚情况 @PostMapping("/withOutRollBack”) public Student saveStudentWithoutRollBack(@RequestBody Student student){ return studentService.saveStudentWithoutRollBack(student); }}Postman 测试结果为了更清楚地理解回滚,以 debug (调试模式) 启动程序。并在 StudentServiceImpl 的 saveStudentWithRollBack 方法上打上断点。测试前数据库结果:Postman 测试回滚debug 模式下可见数据已保存,且获得 id 为 1。:继续执行抛出异常 IllegalArgumentException,将导致数据回滚:测试后数据库结果:并无新增数据,回滚成功。Postman 测试不回滚测试前数据库结果:遇到 IllegalArgumentException 异常数据不会回滚:测试后数据库结果:新增数据,数据不回滚。源码下载https://github.com/turoDog/Demo/tree/master/springboot_transaction_demo后语以上为 SpringBoot 声明式事务的教程。最后,对 Python 、Java 感兴趣请长按二维码关注一波,我会努力带给你们价值,如果觉得本文对你哪怕有一丁点帮助,请帮忙点好看,让更多人知道。另外,关注之后在发送 1024 可领取免费学习资料。资料内容详情请看这篇旧文:Python、C++、Java、Linux、Go、前端、算法资料分享 ...

February 21, 2019 · 2 min · jiezi

SpringBoot 实战 (八) | 使用 Spring Data JPA 访问 Mysql 数据库

微信公众号:一个优秀的废人如有问题或建议,请后台留言,我会尽力解决你的问题。前言如题,今天介绍 Spring Data JPA 的使用。什么是 Spring Data JPA在介绍 Spring Data JPA 之前,首先介绍 Hibernate 。 Hibernate 使用 O/R 映射 (Object-Relation Mapping) 技术实现数据访问, O/R 映射即将领域模型类与数据库的表进行映射,通过程序操作对象而实现表数据操作的能力,让数据访问操作无需关注数据库相关技术。Hibernate 主导了 EJB 3.0 的 JPA 规范, JPA 即 Java Persistence API。JPA 是一个基于 O/R 映射的标准协议(目前最新版本是 JPA 2.1)。所谓规范即只定义标准规制(如注解、接口),不提供实现,软件提供商可以按照标准规范来实现,而使用者只需按照规范中定义的方式来使用,而不用和软件提供商的实现打交道。JPA 的主要实现由 Hibernate 、 EclipseLink 和 OpenJPA 等完成,我们只要使用 JPA 来开发,无论是哪一个开发方式都是一样的。Spring Data JPA 是 Spring Data 的一个子项目,它通过基于 JPA 的 Repository 极大地减少了 JPA 作为数据访问方案的代码量。简而言之,JPA 是一种 ORM 规范,但并未提供 ORM 实现,而 Hibernate 是一个 ORM 框架,它提供了 ORM 实现。准备工作IDEAJDK1.8SpringBoot 2.1.3pom.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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> <relativePath/> <!– lookup parent from repository –> </parent> <groupId>com.nasus</groupId> <artifactId>jpa</artifactId> <version>0.0.1-SNAPSHOT</version> <name>jpa</name> <description>jpa Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!– JPA 依赖 –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!– web 依赖 –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!– mysql 连接类 –> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!– lombok 依赖 –> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!– 单元测试依赖 –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>简单说下,加入 JPA 依赖;mysql 连接类用于连接数据;web 启动类,但凡是 web 应用都需要依赖它;lombok 用于简化实体类。不会的看这篇旧文介绍:SpringBoot 实战 (三) | 使用 LomBokapplication.yaml 配置文件spring:# 数据库相关 datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=true username: root password: 123456# JPA 相关 jpa: hibernate: ddl-auto: update #ddl-auto:设为 create 表示每次都重新建表 show-sql: truerepository (dao) 层package com.nasus.jpa.repository;import com.nasus.jpa.entity.Student;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.data.repository.CrudRepository;import org.springframework.stereotype.Repository;/** * Project Name:springboot_jpa_demo <br/> * Package Name:com.nasus.jpa.repository <br/> * Date:2019/2/19 21:37 <br/> * <b>Description:</b> TODO: 描述该类的作用 <br/> * @author <a href=“turodog@foxmail.com”>nasus</a><br/> /@Repositorypublic interface StudentRepository extends JpaRepository<Student,Integer>, CrudRepository<Student, Integer> {}从上图,可以看出 JpaRepository 继承于 PangingAndSortingRepository 继承于 CrudRepository 。CrudRepository 提供基本的增删改查PagingAndSortingRepository 提供分页和排序方法;JpaRepository 提供 JPA 需要的方法。在使用的时候,可以根据具体需要选中继承哪个接口。使用这些接口的好处有:继承这些接口,可以使Spring找到自定义的数据库操作接口,并生成代理类,后续可以注入到Spring容器中;可以不写相关的sql操作,由代理类生成service 层package com.nasus.jpa.service;import com.nasus.jpa.entity.Student;import java.util.List;/* * Project Name:springboot_jpa_demo <br/> * Package Name:com.nasus.jpa.service <br/> * Date:2019/2/19 21:41 <br/> * <b>Description:</b> TODO: 描述该类的作用 <br/> * @author <a href=“turodog@foxmail.com”>nasus</a><br/> /public interface StudentService { Student save(Student student); Student findStudentById(Integer id); void delete(Integer id); void updateStudent(Student student); List<Student> findStudentList();}实现类:package com.nasus.jpa.service.impl;import com.nasus.jpa.entity.Student;import com.nasus.jpa.repository.StudentRepository;import com.nasus.jpa.service.StudentService;import java.util.List;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;/* * Project Name:springboot_jpa_demo <br/> * Package Name:com.nasus.jpa.service.impl <br/> * Date:2019/2/19 21:43 <br/> * <b>Description:</b> TODO: 描述该类的作用 <br/> * @author <a href=“turodog@foxmail.com”>nasus</a><br/> /@Servicepublic class StudentServiceImpl implements StudentService { @Autowired private StudentRepository studentRepository; /* * 保存学生信息 * @param student * @return / @Override public Student save(Student student) { return studentRepository.save(student); } /* * 根据 Id 查询学生信息 * @param id * @return / @Override public Student findStudentById(Integer id) { return studentRepository.findById(id).get(); } /* * 删除学生信息 * @param id / @Override public void delete(Integer id) { Student student = this.findStudentById(id); studentRepository.delete(student); } /* * 更新学生信息 * @param student / @Override public void updateStudent(Student student) { studentRepository.save(student); } /* * 查询学生信息列表 * @return / @Override public List<Student> findStudentList() { return studentRepository.findAll(); }}controller 层构建 restful APIpackage com.nasus.jpa.controller;import com.nasus.jpa.entity.Student;import com.nasus.jpa.service.StudentService;import java.util.List;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.DeleteMapping;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.PutMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/* * Project Name:springboot_jpa_demo <br/> * Package Name:com.nasus.jpa.controller <br/> * Date:2019/2/19 21:55 <br/> * <b>Description:</b> TODO: 描述该类的作用 <br/> * @author <a href=“turodog@foxmail.com”>nasus</a><br/> */@RestController@RequestMapping("/student”)public class StudentController { @Autowired private StudentService studentService; @PostMapping("/save”) public Student saveStudent(@RequestBody Student student){ return studentService.save(student); } @GetMapping(”/{id}") public Student findStudentById(@PathVariable(“id”) Integer id){ return studentService.findStudentById(id); } @GetMapping("/list") public List<Student> findStudentList(){ return studentService.findStudentList(); } @DeleteMapping("/{id}") public void deleteStudentById(@PathVariable(“id”) Integer id){ studentService.delete(id); } @PutMapping("/update") public void updateStudent(@RequestBody Student student){ studentService.updateStudent(student); }}测试结果其他接口已通过 postman 测试,无问题。源码下载:https://github.com/turoDog/De…后语以上为 SpringBoot 使用 Spring Data JPA 访问 Mysql 数据库的教程。最后,对 Python 、Java 感兴趣请长按二维码关注一波,我会努力带给你们价值,如果觉得本文对你哪怕有一丁点帮助,请帮忙点好看,让更多人知道。另外,关注之后在发送 1024 可领取免费学习资料。资料内容详情请看这篇旧文:Python、C++、Java、Linux、Go、前端、算法资料分享 ...

February 20, 2019 · 3 min · jiezi

SpringBoot 实战 (二) | 第一个 SpringBoot 工程详解

微信公众号:一个优秀的废人如有问题或建议,请后台留言,我会尽力解决你的问题。前言哎呦喂,按照以往的惯例今天周六我的安排应该是待在家学学猫叫啥的。但是今年这种日子就可能一去不复返了,没法办法啊。前几天年轻,立下了一周至少更两篇文章的 flag 。废话少说,今天接着前文给你们带来了第一个 SpringBoot 工程的详解。第一个 SpringBoot 工程前文已经说过了 SpringBoot 工程的创建,这里不再赘述,还不会的朋友,请看下面这篇文章。如何使用 IDEA 构建 Spring Boot 工程学过编程的都知道,学习一门新语言的第一个项目肯定是 Hello World 。那这里也不例外,我们先创建一个非常简单的 Hello World 工程。给大家讲解 SpringBoot 的项目目录。创建信息如下:由于本文重点旨在讲解 SpringBoot 的项目目录。所以选择的依赖包非常简单,就选择 Web 足矣。SpringBoot 项目目录详解创建成功之后的 SpringBoot 项目目录如下,以下对各主要目录的作用进行讲解:src 是整个工程的根目录,基本上做 web 开发你的代码大部分都放在这里。其中 main 目录下放置的是你的 Java 代码;resource 目录,顾名思义就是放置配置文件、静态资源( static )以及前端模板( template )。test 目录就是放置你的单元测试代码。target 就是项目编译生成的目录,里面包含代码编译后的 class 文件以及一些静态资源和配置文件。External Libraries 这里放置了,pom.xml 导入的依赖包。其他没提到的目录都是不重要的。由上图项目目录,可以看到有几个文件,这些文件有些是我新建的,有些是项目生成的。下面我会讲解:pom.xml 这个文件是整个项目最重要的文件,它包含了整个项目的依赖包。Maven 会根据这个文件导入相关的我们开发需要的依赖包。代码如下:可以看到 pom.xml 中一共有 4 个依赖,其中只有 Junit 是我手动加入的,用于单元测试。其他的如 Spring Boot 启动父依赖、Spring Boot web依赖 、Spring Boot web test依赖都是创建项目时,勾选 web 选项卡而生成的。这几个依赖包的额作用就是 内嵌 Tomcat 容器,集成各 Spring 组件。比如 如果没有依赖 web 包 ,Spring 的两大核心功能 IOC 和 AOP 将不会生效。<?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.nasus</groupId> <artifactId>helloworld</artifactId> <version>0.0.1-SNAPSHOT</version> <name>helloworld</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <!– Spring Boot 启动父依赖 –> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.1.RELEASE</version> <relativePath/> <!– lookup parent from repository –> </parent> <dependencies> <!– Spring Boot web依赖 –> <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> <!– Junit –> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>HelloworldApplication.java 最为重要,它由项目生成,是整个工程的应用启动类 main 函数。代码由项目生成,代码如下:SpringApplication 引导应用,并将 HelloworldApplication 本身作为参数传递给 run 方法。具体 run 方法会启动嵌入式的 Tomcat 并初始化 Spring环境及其各 Spring 组件。需要注意的是,这个类必须放在其他类的上册目录,拿上述目录举个栗子, 若其他HelloWorldController.java 类位于 com.nasus.controller 下。则 HelloworldApplication.java 类必须放置在 com.nasus 下或者 com 下(层级必须大于其他 java 类)。否则启动项目访问会报 Whitelabel Error Page 错误,原因是项目扫描不到 @RestController、@RequestMapping 等注解配置的方法和类。package com.nasus.helloworld;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class HelloworldApplication { public static void main(String[] args) { SpringApplication.run(HelloworldApplication.class, args); }}HelloWorldController 是我手动编写的,代码如下:@RestController 和 @RequestMapping 注解是来自 SpringMVC 的注解,它们不是 SpringBoot 的特定部分。其中 @RestController 注解的作用是:提供实现了 REST API,可以服务 JSON、XML 或者其他。这里是以 String 的形式渲染出结果。 其中 @RestController 注解的作用是:提供路由信息,"/“路径的HTTP Request都会被映射到sayHello方法进行处理。 具体参考,Spring 官方的文档《Spring Framework Document》package com.nasus.controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/** * Project Name:helloworld <br/> * Package Name:com.nasus.controller <br/> * Date:2019/1/5 13:59 <br/> * <b>Description:</b> TODO: 描述该类的作用 <br/> * * @author <a href=“turodog@foxmail.com”>nasus</a><br/> * Copyright Notice ========================================================= * This file contains proprietary information of Eastcom Technologies Co. Ltd. * Copying or reproduction without prior written approval is prohibited. * Copyright (c) 2019 ======================================================= /@RestControllerpublic class HelloWorldController { @RequestMapping("/hello”) public String sayHello() { return “Hello,World!”; }}写完 Controller 层的代码,我们就可以启动此项目。点击 IDEA 项目启动按钮,效果如下:好的程序必须配备完善的单元测试。HelloWorldControllerTest.java 文件是由我编写的主要作用就是测试 HelloWorldController.java 中的方法。这里用的是 Junit 依赖包进行单元测试,代码如下:这里的逻辑就是测试 HelloWorldController.java 的 sayHello 方法输出的字符是否是 Hello,World!package com.nasus;import static org.junit.Assert.assertEquals;import com.nasus.controller.HelloWorldController;import org.junit.Test;/* * Project Name:helloworld <br/> * Package Name:com.nasus <br/> * Date:2019/1/5 14:01 <br/> * <b>Description:</b> TODO: 描述该类的作用 <br/> * * @author <a href=“turodog@foxmail.com”>nasus</a><br/> * Copyright Notice ========================================================= * This file contains proprietary information of Eastcom Technologies Co. Ltd. * Copying or reproduction without prior written approval is prohibited. * Copyright (c) 2019 ======================================================= */public class HelloWorldControllerTest { @Test public void testSayHello() { assertEquals(“Hello,World!",new HelloWorldController().sayHello()); }}编写完成之后,可以通过以下按钮启动单元测试类。测试结果如下:可以看到红圈框住的地方,出现这个绿色标志证明单元测试没问题。sayhello 方法的结果是对的。后语我为什么要写这种这么简单的教程?是这样的,我始终认为比我聪明的人有很多,但比我笨的人也不少。在中国有很多你认为众所周知的事,其实有一车人根本不知道,这篇文章哪怕只帮助到一个人,足矣。之后我打算出一个 SpringBoot 系列的教程,敬请关注与指正,本人也是一个小菜鸟在打怪升级中,如本文有不正确的地方,烦请指正。一起学习一起进步。以上就是我对 SpringBoot 工程的理解,希望对你们有帮助。最后,对 Python 、Java 感兴趣请长按二维码关注一波,我会努力带给你们价值,如果觉得本文对你哪怕有一丁点帮助,请帮忙点好看,让更多人知道。另外,关注之后在发送 1024 可领取免费学习资料。资料内容详情请看这篇旧文:Python、C++、Java、Linux、Go、前端、算法资料分享 ...

February 19, 2019 · 2 min · jiezi

SpringBoot 实战 (四) | 集成 Swagger2 构建强大的 RESTful API 文档

微信公众号:一个优秀的废人如有问题或建议,请后台留言,我会尽力解决你的问题。前言快过年了,不知道你们啥时候放年假,忙不忙。反正我是挺闲的,所以有时间写 blog。今天给你们带来 SpringBoot 集成 Swagger2 的教程。什么是 Swagger2Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。为什么使用 Swagger2 ?相信刚开始不熟悉 web 开发的时候,大家都有手写 Api 文档的时候。而手写 Api 文档主要有以下几个痛点:文档需要更新的时候,需要再次发送一份给前端,也就是文档更新交流不及时。接口返回结果不明确。不能直接在线测试接口,通常需要使用工具,比如 postman。接口文档太多,不好管理。这些痛点在前后端分离的大型项目上显得尤为烦躁。而 Swagger2 的出现恰好能个解决这些痛点。因为 Swagger2 有以下功能:文档自动更新,只要生成 Api 的网址没变,基本不需要跟前端沟通。接口返回结果非常明确,包括数据类型,状态码,错误信息等。可以直接在线测试文档,而且还有实例提供给你。只需要一次配置便可使用,之后只要会有一份接口文档,非常易于管理。集成演示首先新建一个 SpringBoot 项目,还不会的参考我这篇旧文—— 如何使用 IDEA 构建 Spring Boot 工程构建时,在选择依赖那一步勾选 Web、LomBok、JPA 和 Mysql 依赖。其中 Mysql 可以不勾,因为我这里用于操作实际的数据库,所以我勾选了。生成 SpringBoot 后的 Pom 文件依赖如下:这里使用的是 2.4.0 的 Swagger2 版本。<?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> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.2.RELEASE</version> <relativePath/> <!– lookup parent from repository –> </parent> <groupId>com.nasus</groupId> <artifactId>swagger2</artifactId> <version>0.0.1-SNAPSHOT</version> <name>swagger2</name> <description>Demo project for Swagger2</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.4.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.4.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>第二步,在 SpringBoot 启动类(Application)的同级目录新建一个 Swagger 配置类,注意 Swagger2 配置类必须与项目入口类 Application 位于同一级目录,否则生成 Api 文档失败,代码如下:package com.nasus;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import springfox.documentation.builders.ApiInfoBuilder;import springfox.documentation.builders.PathSelectors;import springfox.documentation.builders.RequestHandlerSelectors;import springfox.documentation.service.ApiInfo;import springfox.documentation.service.Contact;import springfox.documentation.spi.DocumentationType;import springfox.documentation.spring.web.plugins.Docket;import springfox.documentation.swagger2.annotations.EnableSwagger2;/** * Project Name:swagger2-demo <br/> * Package Name:com.nasus <br/> * Date:2019/1/22 22:52 <br/> * <b>Description:</b> TODO: 描述该类的作用 <br/> * * @author <a href=“turodog@foxmail.com”>nasus</a><br/> * Copyright Notice ========================================================= * This file contains proprietary information of Eastcom Technologies Co. Ltd. * Copying or reproduction without prior written approval is prohibited. * Copyright (c) 2019 ======================================================= /@Configuration// 启用 Swagger2@EnableSwagger2public class Swagger2 { @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) // 文档信息对象 .apiInfo(apiInfo()) .select() // 被注解的包路径 .apis(RequestHandlerSelectors.basePackage(“com.nasus.controller”)) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() // 标题 .title(“springboot 利用 swagger 构建 API 文档”) // Api 文档描述 .description(“简单优雅的 restful 风格,https://blog.csdn.net/turodog/”) .termsOfServiceUrl(“https://blog.csdn.net/turodog/") // 文档作者信息 .contact(new Contact(“陈志远”, “https://github.com/turoDog", “turodog@foxmail.com”)) // 文档版本 .version(“1.0”) .build(); }}第三步,配置被注解的 Controller 类,编写各个接口的请求参数,返回结果,接口描述等等,代码如下:package com.nasus.controller;import com.nasus.entity.Student;import com.nasus.service.StudentService;import io.swagger.annotations.Api;import io.swagger.annotations.ApiImplicitParam;import io.swagger.annotations.ApiImplicitParams;import io.swagger.annotations.ApiOperation;import java.util.List;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.DeleteMapping;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.PutMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RestController;import springfox.documentation.annotations.ApiIgnore;/* * Project Name:swagger2-demo <br/> * Package Name:com.nasus.controller <br/> * Date:2019/1/22 22:07 <br/> * <b>Description:</b> TODO: 描述该类的作用 <br/> * * @author <a href=“turodog@foxmail.com”>nasus</a><br/> * Copyright Notice ========================================================= * This file contains proprietary information of Eastcom Technologies Co. Ltd. * Copying or reproduction without prior written approval is prohibited. * Copyright (c) 2019 ======================================================= */@RestController@RequestMapping("/student”)// @Api:修饰整个类,描述Controller的作用@Api(“StudentController Api 接口文档”)public class StudentController { @Autowired private StudentService studentService; // @ApiOperation:描述一个类的一个方法,或者说一个接口 @ApiOperation(value=“获取所有学生列表”, notes=“获取所有学生列表”) @RequestMapping(value={””}, method= RequestMethod.GET) public List<Student> getStudent() { List<Student> list = studentService.findAll(); return list; } @ApiOperation(value=“添加学生信息”, notes=“添加学生信息”) // @ApiImplicitParam:一个请求参数 @ApiImplicitParam(name = “student”, value = “学生信息详细实体”, required = true, dataType = “Student”) @PostMapping("/save”) public Student save(@RequestBody Student student){ return studentService.save(student); } @ApiOperation(value=“获学生信息”, notes=“根据url的id来获取学生详细信息”) @ApiImplicitParam(name = “id”, value = “ID”, required = true, dataType = “Integer”,paramType = “path”) @GetMapping(”/{id}") public Student findById(@PathVariable(“id”) Integer id){ return studentService.findById(id); } @ApiOperation(value=“删除学生”, notes=“根据url的id来指定删除的学生”) @ApiImplicitParam(name = “id”, value = “学生ID”, required = true, dataType = “Integer”,paramType = “path”) @DeleteMapping("/{id}") public String deleteById(@PathVariable(“id”) Integer id){ studentService.delete(id); return “success”; } @ApiOperation(value=“更新学生信息”, notes=“根据url的id来指定更新学生信息”) // @ApiImplicitParams:多个请求参数 @ApiImplicitParams({ @ApiImplicitParam(name = “id”, value = “学生ID”, required = true, dataType = “Integer”,paramType = “path”), @ApiImplicitParam(name = “student”, value = “学生实体student”, required = true, dataType = “Student”) }) @PutMapping(value="/{id}") public String updateStudent(@PathVariable Integer id, @RequestBody Student student) { Student oldStudent = this.findById(id); oldStudent.setId(student.getId()); oldStudent.setName(student.getName()); oldStudent.setAge(student.getAge()); studentService.save(oldStudent); return “success”; } // 使用该注解忽略这个API @ApiIgnore @RequestMapping(value = “/hi”, method = RequestMethod.GET) public String jsonTest() { return " hi you!"; }}第四步,启动项目,访问 http://localhost:8080/swagger-ui.html 地址,结果如下图:项目源代码github图解接口Swagger2 常用注解简介@ApiOperation:用在方法上,说明方法的作用 1.value: 表示接口名称 2.notes: 表示接口详细描述 @ApiImplicitParams:用在方法上包含一组参数说明@ApiImplicitParam:用在@ApiImplicitParams注解中,指定一个请求参数的各个方面 1.paramType:参数位置 2.header 对应注解:@RequestHeader 3.query 对应注解:@RequestParam 4.path 对应注解: @PathVariable 5.body 对应注解: @RequestBody 6.name:参数名 7.dataType:参数类型 8.required:参数是否必须传 9.value:参数的描述 10.defaultValue:参数的默认值@ApiResponses:用于表示一组响应@ApiResponse:用在@ApiResponses中,一般用于表达一个错误的响应信息 1.code:状态码 2.message:返回自定义信息 3.response:抛出异常的类@ApiIgnore: 表示该接口函数不对swagger2开放展示@Api:修饰整个类,描述Controller的作用@ApiParam:单个参数描述@ApiModel:用对象来接收参数@ApiProperty:用对象接收参数时,描述对象的一个字段@ApiIgnore:使用该注解忽略这个API@ApiError :发生错误返回的信息注意事项@ApiImplicitParam 注解下的 paramType 属性,会影响接口的测试,如果设置的属性跟spring 的注解对应不上,会获取不到参数,例如 paramType=path ,函数内却使用@RequestParam 注解,这样,可能会获取不到传递进来的参数,需要按照上面进行对应,将 @RequestParam 注解改为 @PathVariable 才能获取到对应的参数。后语以上就是我对 Swagger2 的理解与使用,以上只是教大家入门 Swagger2 ,想要深入使用还是希望自行查阅官方文档。最后,对 Python 、Java 感兴趣请长按二维码关注一波,我会努力带给你们价值,如果觉得本文对你哪怕有一丁点帮助,请帮忙点好看,让更多人知道。另外,关注之后在发送 1024 可领取免费学习资料。资料内容详情请看这篇旧文:Python、C++、Java、Linux、Go、前端、算法资料分享 ...

February 19, 2019 · 3 min · jiezi

SpringBoot 实战 | 用 JdbcTemplates 访问 Mysql

微信公众号:一个优秀的废人如有问题或建议,请后台留言,我会尽力解决你的问题。前言如题,今天介绍 springboot 通过jdbc访问关系型mysql,通过 spring 的 JdbcTemplate 去访问。准备工作SpringBoot 2.xjdk 1.8maven 3.0ideamysql构建 SpringBoot 项目,不会的朋友参考旧文章:如何使用 IDEA 构建 Spring Boot 工程项目目录结构pom 文件引入依赖<dependencies>xml <!– jdbcTemplate 依赖 –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!– 开启web: –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!– mysql连接类 –> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!– https://mvnrepository.com/artifact/com.alibaba/druid –> <!– druid 连接池–> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.13</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency></dependencies>application.yaml 配置数据库信息spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=true username: 数据库用户名 password: 数据库密码实体类package com.nasus.domain;/** * Project Name:jdbctemplate_demo <br/> * Package Name:com.nasus.domain <br/> * Date:2019/2/3 10:55 <br/> * <b>Description:</b> TODO: 描述该类的作用 <br/> * * @author <a href=“turodog@foxmail.com”>nasus</a><br/> * Copyright Notice ========================================================= * This file contains proprietary information of Eastcom Technologies Co. Ltd. * Copying or reproduction without prior written approval is prohibited. * Copyright (c) 2019 ======================================================= /public class Student { private Integer id; private String name; private Integer age; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return “Student{” + “id=” + id + “, name=’” + name + ‘'’ + “, age=” + age + ‘}’; }}dao 层package com.nasus.dao;import com.nasus.domain.Student;import java.util.List;/* * Project Name:jdbctemplate_demo <br/> * Package Name:com.nasus.dao <br/> * Date:2019/2/3 10:59 <br/> * <b>Description:</b> TODO: 描述该类的作用 <br/> * @author <a href=“turodog@foxmail.com”>nasus</a><br/> /public interface IStudentDao { int add(Student student); int update(Student student); int delete(int id); Student findStudentById(int id); List<Student> findStudentList();}具体实现类:package com.nasus.dao.impl;import com.nasus.dao.IStudentDao;import com.nasus.domain.Student;import java.util.List;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.BeanPropertyRowMapper;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.stereotype.Repository;/* * Project Name:jdbctemplate_demo <br/> * Package Name:com.nasus.dao.impl <br/> * Date:2019/2/3 11:00 <br/> * <b>Description:</b> TODO: 描述该类的作用 <br/> * * @author <a href=“turodog@foxmail.com”>nasus</a><br/> /@Repositorypublic class IStudentDaoImpl implements IStudentDao{ @Autowired private JdbcTemplate jdbcTemplate; @Override public int add(Student student) { return jdbcTemplate.update(“insert into student(name, age) values(?, ?)”, student.getName(),student.getAge()); } @Override public int update(Student student) { return jdbcTemplate.update(“UPDATE student SET NAME=? ,age=? WHERE id=?”, student.getName(),student.getAge(),student.getId()); } @Override public int delete(int id) { return jdbcTemplate.update(“DELETE from TABLE student where id=?",id); } @Override public Student findStudentById(int id) { // BeanPropertyRowMapper 使获取的 List 结果列表的数据库字段和实体类自动对应 List<Student> list = jdbcTemplate.query(“select * from student where id = ?”, new Object[]{id}, new BeanPropertyRowMapper(Student.class)); if(list!=null && list.size()>0){ Student student = list.get(0); return student; }else{ return null; } } @Override public List<Student> findStudentList() { // 使用Spring的JdbcTemplate查询数据库,获取List结果列表,数据库表字段和实体类自动对应,可以使用BeanPropertyRowMapper List<Student> list = jdbcTemplate.query(“select * from student”, new Object[]{}, new BeanPropertyRowMapper(Student.class)); if(list!=null && list.size()>0){ return list; }else{ return null; } }}service 层package com.nasus.service;import com.nasus.domain.Student;import java.util.List;/* * Project Name:jdbctemplate_demo <br/> * Package Name:com.nasus.service <br/> * Date:2019/2/3 11:17 <br/> * <b>Description:</b> TODO: 描述该类的作用 <br/> * * @author <a href=“turodog@foxmail.com”>nasus</a><br/> /public interface IStudentService { int add(Student student); int update(Student student); int delete(int id); Student findStudentById(int id); List<Student> findStudentList();}实现类:package com.nasus.service.impl;import com.nasus.dao.IStudentDao;import com.nasus.domain.Student;import com.nasus.service.IStudentService;import java.util.List;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Repository;/* * Project Name:jdbctemplate_demo <br/> * Package Name:com.nasus.service.impl <br/> * Date:2019/2/3 11:18 <br/> * <b>Description:</b> TODO: 描述该类的作用 <br/> * * @author <a href=“turodog@foxmail.com”>nasus</a><br/> * Copyright Notice ========================================================= * This file contains proprietary information of Eastcom Technologies Co. Ltd. * Copying or reproduction without prior written approval is prohibited. * Copyright (c) 2019 ======================================================= /@Repositorypublic class IStudentServiceImpl implements IStudentService { @Autowired private IStudentDao iStudentDao; @Override public int add(Student student) { return iStudentDao.add(student); } @Override public int update(Student student) { return iStudentDao.update(student); } @Override public int delete(int id) { return iStudentDao.delete(id); } @Override public Student findStudentById(int id) { return iStudentDao.findStudentById(id); } @Override public List<Student> findStudentList() { return iStudentDao.findStudentList(); }}controller 构建 restful apipackage com.nasus.controller;import com.nasus.domain.Student;import com.nasus.service.IStudentService;import java.util.List;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.DeleteMapping;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.PutMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/* * Project Name:jdbctemplate_demo <br/> * Package Name:com.nasus.controller <br/> * Date:2019/2/3 11:21 <br/> * <b>Description:</b> TODO: 描述该类的作用 <br/> * * @author <a href=“turodog@foxmail.com”>nasus</a><br/> */@RestController@RequestMapping("/student”)public class StudentController { @Autowired private IStudentService iStudentService; @PostMapping("") public int addStudent(@RequestBody Student student){ return iStudentService.add(student); } @PutMapping("/{id}") public String updateStudent(@PathVariable Integer id, @RequestBody Student student){ Student oldStudent = new Student(); oldStudent.setId(id); oldStudent.setName(student.getName()); oldStudent.setAge(student.getAge()); int t = iStudentService.update(oldStudent); if (t == 1){ return student.toString(); }else { return “更新学生信息错误”; } } @GetMapping("/{id}") public Student findStudentById(@PathVariable Integer id){ return iStudentService.findStudentById(id); } @GetMapping("/list") public List<Student> findStudentList(){ return iStudentService.findStudentList(); } @DeleteMapping("/{id}") public int deleteStudentById(@PathVariable Integer id){ return iStudentService.delete(id); }}演示结果其他的 api 测试可以通过 postman 测试。我这里已经全部测试通过,请放心使用。源码下载:https://github.com/turoDog/De…后语以上SpringBoot 用 JdbcTemplates 访问Mysql 的教程。最后,对 Python 、Java 感兴趣请长按二维码关注一波,我会努力带给你们价值,如果觉得本文对你哪怕有一丁点帮助,请帮忙点好看,让更多人知道。另外,关注之后在发送 1024 可领取免费学习资料。资料内容详情请看这篇旧文:Python、C++、Java、Linux、Go、前端、算法资料分享 ...

February 17, 2019 · 4 min · jiezi

获取当前Tomcat实例的端口

有时需要在当前代码中获取当前Server实例的端口号, 通过HttpServletRequest请求可以, 但有时也需要在没有请求的情况下获取到端口号.用以下方法是可以获取到的:public int getHttpPort() { try { MBeanServer server; if (MBeanServerFactory.findMBeanServer(null).size() > 0) { server = MBeanServerFactory.findMBeanServer(null).get(0); } else { log.error(“no MBeanServer!”); return -1; } Set names = server.queryNames(new ObjectName(“Catalina:type=Connector,*”), Query.match(Query.attr(“protocol”), Query.value(“HTTP/1.1”))); Iterator iterator = names.iterator(); if (iterator.hasNext()) { ObjectName name = (ObjectName) iterator.next(); return Integer.parseInt(server.getAttribute(name, “port”).toString()); } } catch (Exception e) { log.error(“getHttpPort”, e); } return -1;}

February 13, 2019 · 1 min · jiezi

SpringBoot实战 | 配置文件详解

微信公众号:一个优秀的废人如有问题或建议,请后台留言,我会尽力解决你的问题。前言如题,今天解析下 SpringBoot 的配置文件。自定义属性加载首先构建 SpringBoot 项目,不会的看这篇旧文 使用 IDEA 构建 Spring Boot 工程。首先在项目根目录下加入以下自定义属性:# 防止读取乱码spring.http.encoding.charset=UTF-8# 项目启动端口server.port=9999# 自定义配置com.nasus.author.name=一个优秀的废人com.nasus.article.title=SpringBoot配置文件详解使用 @value 注解读取配置文件属性:package com.nasus.bean;import lombok.Data;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;/** * Project Name:springboot_properties_demo <br/> * Package Name:com.nasus.properties <br/> * Date:2019/1/28 20:59 <br/> * <b>Description:</b> TODO: 描述该类的作用 <br/> * @author <a href=“turodog@foxmail.com”>nasus</a><br/> /@Data@Componentpublic class PropertiesBean { @Value("${com.nasus.author.name}") private String name; @Value("${com.nasus.article.title}") private String title; @Value("${com.nasus.doing}") private String desc;}之后新建 controller 测试自定义属性加载,代码如下:package com.nasus.controller;import com.nasus.bean.PropertiesBean;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/* * Project Name:springboot_properties_demo <br/> * Package Name:com.nasus.controller <br/> * Date:2019/1/28 21:41 <br/> * <b>Description:</b> TODO: 测试自定义属性加载<br/> * * @author <a href=“turodog@foxmail.com”>nasus</a><br/> */@RestController@RequestMapping("/test")public class TestController { @Autowired private PropertiesBean propertiesBean; @GetMapping("/getInfo") public PropertiesBean getInfo(){ return propertiesBean; }}访问 http://localhost:8080/test/getInfo 查看加载结果:可以看到,加入 @value 注解之后,配置文件的属性都被读取出来了。以前,或许我们还需要专门写一个读取配置文件的工具类才能把属性读取出来,现在有了 Spring ,我们可以直接使用 @value 就能读取了,简直不能太方便。本例源码在这:github 地址参数间的引用配置文件代码如下:# 防止读取乱码spring.http.encoding.charset=UTF-8# 项目启动端口server.port=9999# 自定义配置com.nasus.author.name=一个优秀的废人com.nasus.article.title=SpringBoot配置文件详解com.nasus.doing=${com.nasus.author.name}写文章《${com.nasus.article.title}》可以看到最后一个参数配置使用了前两个的参数配置,测试结果如下:使用随机数有时项目需求,可能我们需要配置一些随机数,比如说为了安全而随机配置的服务器端口,以及登录密钥。这时我们就可以用 SpringBoot 的 random 属性来配置随机数,比如:# 随机字符串com.nasus.article.value=${random.value}# 随机intcom.nasus.article.number=${random.int}# 随机longcom.nasus.article.bignumber=${random.long}# 10以内的随机数com.nasus.article.test1=${random.int(10)}# 10-20的随机数com.nasus.article.test2=${random.int[10,20]}使用多配置文件很多时候我们开发项目都需要很多套环境,比如有测试环境,开发环境以及生产环境。不同的环境就需要使用不同的配置文件,为此我们可以根据这 3 个环境分别新建 以下 3 个配置文件。application-dev.properties:开发环境application-test.properties:测试环境application-prod.properties:生产环境项目中默认的配置文件是 application.properties 。这时我们可以根据自己的环境去使用相应的配置文件,比如说,项目各个环境的端口必须不一样。那我们可以这样配置:application-dev.properties:开发环境server.port=6666application-test.properties:测试环境server.port=7777application-prod.properties:生产环境server.port=8888假如,现在我打包上线,那就必须用生产环境的配置文件了,这时我们可以在 默认的配置文件 application.properties 中加入以下配置即可spring.profiles.active=prod配置数据库SpringBoot 的配置文件有两种格式,一种是 .properties 格式(以上栗子都是用的这种)还有一种用的是 .yaml 格式。以下是用 yaml 方式配置。这两种格式并无好坏之分,纯看个人使用习惯。我就比较喜欢 yaml 格式,因为看起来比较简洁。spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=true username: 你的数据库名称 password: 你的数据库密码 jpa: hibernate: ddl-auto: update #ddl-auto:设为update 表示每次都重新建表 show-sql: true注意事项使用 yaml 格式需要注意一点就是 键值对冒号后面,必须空一格。application.properties 配置中文值的时候,读取出来的属性值会出现乱码问题。但是 application.yml 不会出现乱码问题。原因是,Spring Boot 是以 iso-8859 的编码方式读取 application.properties 配置文件。解决第二点,只需加入 spring.http.encoding.charset=UTF-8 配置即可。后语以上就是我对 SpringBoot 配置文件的理解与使用,当然以上只是介绍了一下 SpringBoot 配置文件的几个用法,它的用法还有非常多,想要深入使用还是需要各位多多深入实践。最后,对 Python 、Java 感兴趣请长按二维码关注一波,我会努力带给你们价值,如果觉得本文对你哪怕有一丁点帮助,请帮忙点好看,让更多人知道。另外,关注之后在发送 1024 可领取免费学习资料。资料内容详情请看这篇旧文:Python、C++、Java、Linux、Go、前端、算法资料分享 ...

January 29, 2019 · 1 min · jiezi

tomcat设置默认路径致使项目url冲突解决方法

前言tomcat作为java容器非常出色,但是依然会有一些避之不及的小坑,在此记录一笔.START问题部署多个项目后url路径冲突情景描述1.webapps下有两个项目 projectA,projectB.两个项目除开管理信息接口,其余都有安全验证机制.2.projectA由于未做前后端分离,因此静态资源也存在java项目中.在做静态资源中的接口请求时未写包名,比如登录,js代码会拼接服务器ip+端口+当前设置的url(/login),而未在/login前加上/projectA,所以在本机上测试没有问题,只有当部署的时候才会存在这样的问题.这也是个问题,下面解决.3.projectB是运行正常的项目对于情景2解决办法打开tomcat中的配置文件,在<Host>标签里添加上<Context>设置为服务器的默认访问路劲,如此便避开了包名,但此方法极不正规,不推荐使用.<Host name=“localhost” appBase=“webapps” unpackWARs=“true” autoDeploy=“true”> <!– SingleSignOn valve, share authentication between web applications Documentation at: /docs/config/valve.html –> <!– <Valve className=“org.apache.catalina.authenticator.SingleSignOn” /> –> <!– Access log processes all example. Documentation at: /docs/config/valve.html Note: The pattern used is equivalent to using pattern=“common” –> <Valve className=“org.apache.catalina.valves.AccessLogValve” directory=“logs” prefix=“localhost_access_log” suffix=".txt" pattern="%h %l %u %t &quot;%r&quot; %s %b" /> <Context path="" docBase="/usr/tomcat8.6/webapps/sc_edu" debug=“0” reloadable=“true”/> </Host>代码解释<Context path="" docBase="/usr/tomcat8.6/webapps/sc_edu" debug=“0” reloadable=“true”/>path和doBase一起表示指定包路径,为了简便可以直接卸载docBase中.重启tomcat,测试直接ip+port能访问到该包中的资源,然而当访问其它包中资源时,会出现url歧义.本来是想访问projectB,然而映射到projectA项目下.只是部分url会出现这样的问题.然后我们的解决方法是再装一个tomcat,只部署需要直接路径映射的项目如此便回到tomcat所在目录,cp命令拷贝$> cp -r tomcat8.5/ tomcat8.6/然后移动tomcat8.5中的projectA到tomcat8.6中.删除tomcat8.5中的<Context path="" docBase="/usr/tomcat8.6/webapps/sc_edu" debug=“0” reloadable=“true”/>在tomcat8.6中需要对service.xml做如下改动.改动 shutdown对应的port为8006,只要与tomcat8.5不同且端口不冲突就行.<Server port=“8006” shutdown=“SHUTDOWN”>改动 请求对应的端口,原则同上<Connector port=“8081” protocol=“HTTP/1.1” connectionTimeout=“20000” redirectPort=“8443” /><Host>标签中的内容复制过来的,需要修改成对应的映射路径.如此两个tomcat才能同时运行,启动与关闭互不影响.END ...

January 20, 2019 · 1 min · jiezi

vue项目路由在history模式下布置在Tomcat下解决刷新404问题

1、首先打包之前需要修改config文件夹下的index.js2、其次记得要添加404路由页面3、在tomcat的webapps下的前端文件夹下新建WEB-INF文件夹,并在该文件夹下建立web.xml文件,具体内容如下<?xml version=“1.0” encoding=“UTF-8”?><web-app xmlns=“http://xmlns.jcp.org/xml/ns/j...; xmlns:xsi=“http://www.w3.org/2001/XMLSch...; xsi:schemaLocation=“http://xmlns.jcp.org/xml/ns/j… http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version=“3.1” metadata-complete=“true”><display-name>Router for Tomcat</display-name><error-page> <error-code>404</error-code> <location>/index.html</location></error-page></web-app>

January 18, 2019 · 1 min · jiezi

Maven配置覆盖内嵌tomcat虚拟映射路径

Maven配置覆盖内嵌tomcat虚拟映射路径直接配置报错,错误提示如下: Caused by: java.lang.IllegalArgumentException: addChild: Child name ‘/store’ is not unique原因分析:pom.xml的配置并没有覆盖tomcat/conf/server.xml中的配置,导致配置中存在多个相同配置解决方案下载tomcat7-maven-plugin-2.2.jar.zip文件,解压并覆盖本地仓库中的tomcat7-maven-plugin-2.2.jar,比如我的本地仓库在:D:\M2REPO\org\apache\tomcat\maven\tomcat7-maven-plugin\2.2下,那么我们只需要解压并此目录下的tomcat7-maven-plugin-2.2.jar覆盖此文件就ok.pom.xml配置<plugins> <!– 指定jdk1.7编译,否则maven update 可能调整jdk –> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.7</source> <target>1.7</target> <encoding>UTF-8</encoding> </configuration> </plugin> <!– tomcat7插件。使用方式:tomcat7:run –> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <update>true</update> <port>8080</port> <uriEncoding>UTF-8</uriEncoding> <server>tomcat7</server> <!– tomcat虚拟映射路径 –> <staticContextPath>/store</staticContextPath> <staticContextDocbase>d:/file/store/</staticContextDocbase> <contextReloadable>false</contextReloadable> <useTestClasspath>true</useTestClasspath> </configuration> </plugin></plugins>参考地址

January 17, 2019 · 1 min · jiezi

spring security使用详解

spring security使用详解由于公司新的后台项目独立,和其他服务没有关系,所以考虑单独实现自己的用户登录模块。 所以,我将对spring security基于 “springboot” 框架的使用方式总结如下: (1)当我们在pom文件中,添加依赖后 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> 启动服务,访问 http://localhost:8080这个地址,端口可以自己设置通过server.port=进行配置,下面弹出如下页面: 此时,系统已经处于受保护状态,这是spring security默认的登录页面。系统生成默认用户名为 “user”,密码打印到控制台总,如下: 我们输入用户名和密码便可以登录进去,我们也可以application.properties文件中自定义用户名和密码如下: 到此,最简单的使用方式已经完成,但是这还远远不能满足我们的需要,截下来介绍自定义表单登录。(2)spring security 表单登录 具体流程如下图: 本文参考地址:https://blog.csdn.net/mj86534…

January 11, 2019 · 1 min · jiezi

Tomcat日志乱码问题

昨天本来准备更新一下Tomcat版本,但是发现新版本的日志打印中文会出现乱码(Tomcat自身打印的日志),不管是使用bat脚本启动还是在Idea中启动,都是乱码。研究了一个晚上,百度上的那些方式都试遍了,都是设置各种JVM启动参数,发现并没有卵用。在使用bat文件启动Tomcat时,Tomcat目录下的logs文件夹会生成相应的日志文件,发现旧版本生成的日志文件编码是GBK,而Windows控制台的编码也是GBK,所以不会乱码。而新版本生成的日志文件编码是UTF-8,所以就造成了中文乱码问题定位到问题以后,就去看Tomcat的日志配置文件,tomcat/conf/logging.properties这个文件就是tomcat的日志配置文件,通过使用BCompare对新老版本的配置文件进行对比,发现tomcat在新版的日志配置文件中加了指定编码为UTF-8的配置。这就是乱码的根源了。解决方法:将配置UTF-8那一行配置删除(这样应该就是采用操作系统默认编码,Windows下即为GBK)将UTF-8改为GBK若文章有任何问题,欢迎留言指出——作者博客:桔子笔记

January 3, 2019 · 1 min · jiezi

Maven+Tomcat7实现项目热部署

热部署能够在不关闭Tomcat的情况下直接将war包部署到服务器上。修改Tomcat配置文件 需要修改tomcat的conf/tomcat-users.xml配置文件。添加用户名、密码、权限。<role rolename=“manager-gui” /><role rolename=“manager-script” /><user username=“tomcat” password=“tomcat” roles=“manager-gui, manager-script”/>启动服务器点击这个链接进入后台管理功能这里输入刚才设置的密码就可以进去了在这里选择自己的war包就可以直接部署。使用maven的tomcat插件实现热部署如果在上面选择的话,还是有点麻烦,这里我们直接在自己的代码中就通过Maven就可以直接部署。 在web项目中的pom.xml文件中配置,这里需要注意的是目录必须是/manager/text<!– 配置tomcat插件 –> <build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <configuration> <path>/</path> <port>8080</port> <url>http://192.168.25.134:8080/manager/text</url> <username>tomcat</username> <password>tomcat</password> </configuration> </plugin> </plugins> </build>使用Maven部署项目,因为Tomcat中有原来的项目所以需要使用redeploy这个命令。-DskipTests的意思跳过测试。clean tomcat7:redeploy -DskipTestsok,项目部署成功:

December 31, 2018 · 1 min · jiezi

dubbo源码解析(十四)远程通信——Http

远程通讯——Http目标:介绍基于Http的来实现的远程通信、介绍dubbo-remoting-http内的源码解析。前言本文我们讲解的是如何基于Tomcat或者Jetty实现HTTP服务器。Tomcat和Jetty都是一种servlet引擎,Jetty要比Tomcat的架构更简单一些。关于它们之间的比较,我觉得google一些更加方便,我就不多废话了 。下面是dubbo-remoting-http包下的类图:可以看到这个包下只提供了服务端实现,并没有客户端实现。源码分析(一)HttpServerpublic interface HttpServer extends Resetable { /** * get http handler. * 获得http的处理类 * @return http handler. / HttpHandler getHttpHandler(); /* * get url. * 获得url * @return url / URL getUrl(); /* * get local address. * 获得本地服务器地址 * @return local address. / InetSocketAddress getLocalAddress(); /* * close the channel. * 关闭通道 / void close(); /* * Graceful close the channel. * 优雅的关闭通道 / void close(int timeout); /* * is bound. * 是否绑定 * @return bound / boolean isBound(); /* * is closed. * 服务器是否关闭 * @return closed / boolean isClosed();}该接口是http服务器的接口,定义了服务器相关的方法,都比较好理解。(二)AbstractHttpServer该类实现接口HttpServer,是http服务器接口的抽象类。/* * url /private final URL url;/* * http服务器处理器 /private final HttpHandler handler;/* * 该服务器是否关闭 /private volatile boolean closed;public AbstractHttpServer(URL url, HttpHandler handler) { if (url == null) { throw new IllegalArgumentException(“url == null”); } if (handler == null) { throw new IllegalArgumentException(“handler == null”); } this.url = url; this.handler = handler;}该类中有三个属性,关键是看在后面怎么用到,因为该类是抽象类,所以该类中的方法并没有实现具体的逻辑,我们继续往下看它的三个子类。(三)TomcatHttpServer该类是基于Tomcat来实现服务器的实现类,它继承了AbstractHttpServer。1.属性/* * 内嵌的tomcat对象 /private final Tomcat tomcat;/* * url对象 /private final URL url;该类的两个属性,关键是内嵌的tomcat。2.构造方法 public TomcatHttpServer(URL url, final HttpHandler handler) { super(url, handler); this.url = url; // 添加处理器 DispatcherServlet.addHttpHandler(url.getPort(), handler); // 获得java.io.tmpdir的绝对路径目录 String baseDir = new File(System.getProperty(“java.io.tmpdir”)).getAbsolutePath(); // 创建内嵌的tomcat对象 tomcat = new Tomcat(); // 设置根目录 tomcat.setBaseDir(baseDir); // 设置端口号 tomcat.setPort(url.getPort()); // 给默认的http连接器。设置最大线程数 tomcat.getConnector().setProperty( “maxThreads”, String.valueOf(url.getParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS)));// tomcat.getConnector().setProperty(// “minSpareThreads”, String.valueOf(url.getParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS))); // 设置最大的连接数 tomcat.getConnector().setProperty( “maxConnections”, String.valueOf(url.getParameter(Constants.ACCEPTS_KEY, -1))); // 设置URL编码格式 tomcat.getConnector().setProperty(“URIEncoding”, “UTF-8”); // 设置连接超时事件为60s tomcat.getConnector().setProperty(“connectionTimeout”, “60000”); // 设置最大长连接个数为不限制个数 tomcat.getConnector().setProperty(“maxKeepAliveRequests”, “-1”); // 设置将由连接器使用的Coyote协议。 tomcat.getConnector().setProtocol(“org.apache.coyote.http11.Http11NioProtocol”); // 添加上下文 Context context = tomcat.addContext("/", baseDir); // 添加servlet,把servlet添加到context Tomcat.addServlet(context, “dispatcher”, new DispatcherServlet()); // 添加servlet映射 context.addServletMapping("/", “dispatcher”); // 添加servlet上下文 ServletManager.getInstance().addServletContext(url.getPort(), context.getServletContext()); try { // 开启tomcat tomcat.start(); } catch (LifecycleException e) { throw new IllegalStateException(“Failed to start tomcat server at " + url.getAddress(), e); } }该方法的构造函数中就启动了tomcat,前面很多都是对tomcat的启动参数以及配置设置。如果有过tomcat配置经验的朋友应该看起来很简单。3.close@Overridepublic void close() { super.close(); // 移除相关的servlet上下文 ServletManager.getInstance().removeServletContext(url.getPort()); try { // 停止tomcat tomcat.stop(); } catch (Exception e) { logger.warn(e.getMessage(), e); }}该方法是关闭服务器的方法。调用了tomcat.stop(四)JettyHttpServer该类是基于Jetty来实现服务器的实现类,它继承了AbstractHttpServer。1.属性/** * 内嵌的Jetty服务器对象 /private Server server;/* * url对象 /private URL url;该类的两个属性,关键是内嵌的sever,它是内嵌的etty服务器对象。2.构造方法 public JettyHttpServer(URL url, final HttpHandler handler) { super(url, handler); this.url = url; // TODO we should leave this setting to slf4j // we must disable the debug logging for production use // 设置日志 Log.setLog(new StdErrLog()); // 禁用调试用的日志 Log.getLog().setDebugEnabled(false); // 添加http服务器处理器 DispatcherServlet.addHttpHandler(url.getParameter(Constants.BIND_PORT_KEY, url.getPort()), handler); // 获得线程数 int threads = url.getParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS); // 创建线程池 QueuedThreadPool threadPool = new QueuedThreadPool(); // 设置线程池配置 threadPool.setDaemon(true); threadPool.setMaxThreads(threads); threadPool.setMinThreads(threads); // 创建选择NIO连接器 SelectChannelConnector connector = new SelectChannelConnector(); // 获得绑定的ip String bindIp = url.getParameter(Constants.BIND_IP_KEY, url.getHost()); if (!url.isAnyHost() && NetUtils.isValidLocalHost(bindIp)) { // 设置主机地址 connector.setHost(bindIp); } // 设置端口号 connector.setPort(url.getParameter(Constants.BIND_PORT_KEY, url.getPort())); // 创建Jetty服务器对象 server = new Server(); // 设置线程池 server.setThreadPool(threadPool); // 设置连接器 server.addConnector(connector); // 添加DispatcherServlet到jetty ServletHandler servletHandler = new ServletHandler(); ServletHolder servletHolder = servletHandler.addServletWithMapping(DispatcherServlet.class, “/”); servletHolder.setInitOrder(2); // dubbo’s original impl can’t support the use of ServletContext// server.addHandler(servletHandler); // TODO Context.SESSIONS is the best option here? Context context = new Context(server, “/”, Context.SESSIONS); context.setServletHandler(servletHandler); // 添加 ServletContext 对象,到 ServletManager 中 ServletManager.getInstance().addServletContext(url.getParameter(Constants.BIND_PORT_KEY, url.getPort()), context.getServletContext()); try { // 启动jetty服务器 server.start(); } catch (Exception e) { throw new IllegalStateException(“Failed to start jetty server on " + url.getParameter(Constants.BIND_IP_KEY) + “:” + url.getParameter(Constants.BIND_PORT_KEY) + “, cause: " + e.getMessage(), e); } }可以看到它跟TomcatHttpServer中构造函数不同的是API的不同,不过思路差不多,先设置启动参数和配置,然后启动jetty服务器。3.close@Overridepublic void close() { super.close(); // 移除 ServletContext 对象 ServletManager.getInstance().removeServletContext(url.getParameter(Constants.BIND_PORT_KEY, url.getPort())); if (server != null) { try { // 停止服务器 server.stop(); } catch (Exception e) { logger.warn(e.getMessage(), e); } }}该方法是关闭服务器的方法,调用的是server的stop方法。(五)ServletHttpServer该类继承了AbstractHttpServer,是基于 Servlet 的服务器实现类。public class ServletHttpServer extends AbstractHttpServer { public ServletHttpServer(URL url, HttpHandler handler) { super(url, handler); // /把 HttpHandler 到 DispatcherServlet 中,默认端口为8080 DispatcherServlet.addHttpHandler(url.getParameter(Constants.BIND_PORT_KEY, 8080), handler); }}该类就一个构造方法。就是把服务器处理器注册到DispatcherServlet上。(六)HttpBinder@SPI(“jetty”)public interface HttpBinder { /** * bind the server. * 绑定到服务器 * @param url server url. * @return server. / @Adaptive({Constants.SERVER_KEY}) HttpServer bind(URL url, HttpHandler handler);}该接口是http绑定器接口,其中就定义了一个方法就是绑定方法,并且返回服务器对象。该接口是一个可扩展接口,默认实现JettyHttpBinder。它有三个实现类,请往下看。(七)TomcatHttpBinderpublic class TomcatHttpBinder implements HttpBinder { @Override public HttpServer bind(URL url, HttpHandler handler) { // 创建一个TomcatHttpServer return new TomcatHttpServer(url, handler); }}一眼就看出来,就是创建了个基于Tomcat实现的服务器TomcatHttpServer对象。具体的就往上看TomcatHttpServer里面的实现。(八)JettyHttpBinderpublic class JettyHttpBinder implements HttpBinder { @Override public HttpServer bind(URL url, HttpHandler handler) { // 创建JettyHttpServer实例 return new JettyHttpServer(url, handler); }}一眼就看出来,就是创建了个基于Jetty实现的服务器JettyHttpServer对象。具体的就往上看JettyHttpServer里面的实现。(九)ServletHttpBinderpublic class ServletHttpBinder implements HttpBinder { @Override @Adaptive() public HttpServer bind(URL url, HttpHandler handler) { // 创建ServletHttpServer对象 return new ServletHttpServer(url, handler); }}创建了个基于servlet实现的服务器ServletHttpServer对象。并且方法上加入了Adaptive,用到了dubbo SPI机制。(十)BootstrapListenerpublic class BootstrapListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent servletContextEvent) { // context创建的时候,把ServletContext添加到ServletManager ServletManager.getInstance().addServletContext(ServletManager.EXTERNAL_SERVER_PORT, servletContextEvent.getServletContext()); } @Override public void contextDestroyed(ServletContextEvent servletContextEvent) { // context销毁到时候,把servletContextEvent移除 ServletManager.getInstance().removeServletContext(ServletManager.EXTERNAL_SERVER_PORT); }}该类实现了ServletContextListener,是 启动监听器,当context创建和销毁的时候对ServletContext做处理。不过需要配置BootstrapListener到web.xml,通过这样的方式,让外部的 ServletContext 对象,添加到 ServletManager 中。(十一)DispatcherServletpublic class DispatcherServlet extends HttpServlet { private static final long serialVersionUID = 5766349180380479888L; /* * http服务器处理器 / private static final Map<Integer, HttpHandler> handlers = new ConcurrentHashMap<Integer, HttpHandler>(); /* * 单例 / private static DispatcherServlet INSTANCE; public DispatcherServlet() { DispatcherServlet.INSTANCE = this; } /* * 添加处理器 * @param port * @param processor / public static void addHttpHandler(int port, HttpHandler processor) { handlers.put(port, processor); } public static void removeHttpHandler(int port) { handlers.remove(port); } public static DispatcherServlet getInstance() { return INSTANCE; } @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获得处理器 HttpHandler handler = handlers.get(request.getLocalPort()); // 如果处理器不存在 if (handler == null) {// service not found. // 返回404 response.sendError(HttpServletResponse.SC_NOT_FOUND, “Service not found.”); } else { // 处理请求 handler.handle(request, response); } }}该类继承了HttpServlet,是服务请求调度servlet类,主要是service方法,根据请求来调度不同的处理器去处理请求,如果没有该处理器,则报错404(十二)ServletManagerpublic class ServletManager { /* * 外部服务器端口,用于 servlet 的服务器端口 / public static final int EXTERNAL_SERVER_PORT = -1234; /* * 单例 / private static final ServletManager instance = new ServletManager(); /* * ServletContext 集合 / private final Map<Integer, ServletContext> contextMap = new ConcurrentHashMap<Integer, ServletContext>(); public static ServletManager getInstance() { return instance; } /* * 添加ServletContext * @param port * @param servletContext / public void addServletContext(int port, ServletContext servletContext) { contextMap.put(port, servletContext); } /* * 移除ServletContext * @param port / public void removeServletContext(int port) { contextMap.remove(port); } /* * 获得ServletContext对象 * @param port * @return / public ServletContext getServletContext(int port) { return contextMap.get(port); }}该类是servlet的管理器,管理着ServletContext。(十三)HttpHandlerpublic interface HttpHandler { /* * invoke. * HTTP请求处理 * @param request request. * @param response response. * @throws IOException * @throws ServletException */ void handle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException;}该接口是HTTP 处理器接口,就定义了一个处理请求的方法。后记该部分相关的源码解析地址:https://github.com/CrazyHZM/i…该文章讲解了内嵌tomcat和jetty的来实现的http服务器,关键需要对tomcat和jetty的配置有所了解。下一篇我会讲解基于mina实现远程通信部分。 ...

December 25, 2018 · 5 min · jiezi

缓存使用

1,使用nginx代理缓存2,使用304状态码,springboot项目使用shadowEtagFilter3,使用springboot的enableCacheing注解实现缓存

December 24, 2018 · 1 min · jiezi

Tomcat优化笔记

一千个人眼中就有一千个哈姆雷特。——伪西方谚语关于Tomcat的优化点之多,我估计没有上万,也有成千。不同的应用场景,不同的架构,不同的需求,都会对优化设置有不同要求。在这里我所记述的只是我自己在一些Tomcat应用中所设置的优化项,以备不时之需,并不是放之四海而皆准的准则。pom.xml对于maven项目来说,pom.xml设置是整个设置的核心,如果pom.xml设置不当,虽然有时候也可以编译运行,但总是会出现一些令人讨厌的警告。为了消除这些警告,还需要根治pom.xml。重复依赖首先要解决的是重复依赖问题,有时候我们会在编译项目时遇到下面的这样的警告:[WARNING][WARNING] Some problems were encountered while building the effective model for com.qiban.supplier:saas-supplier:war:1.0-SNAPSHOT[WARNING] ‘dependencies.dependency.(groupId:artifactId:type:classifier)’ must be unique: commons-codec:commons-codec:jar -> duplicate declaration of version 1.9 @ line 264, column 21[WARNING][WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.[WARNING][WARNING] For this reason, future Maven versions might no longer support building such malformed projects.[WARNING]解决的方法很简单:在pom.xml中搜索出现重复依赖的jar包名称,你肯定会发现对于同一个jar包,重复引用了多次,也许版本相同,也许版本不同,只要删除掉那些重复的就可以了。maven编译器版本有时候会遇到下面这样的错误:[WARNING][WARNING] Some problems were encountered while building the effective model for com.qiban.supplier:saas-supplier:war:1.0-SNAPSHOT[WARNING] ‘build.plugins.plugin.version’ for org.apache.maven.plugins:maven-compiler-plugin is missing. @ line 297, column 21[WARNING][WARNING] It is highly recommended to fix these problems because they threaten the stability of your build.[WARNING][WARNING] For this reason, future Maven versions might no longer support building such malformed projects.[WARNING]这意思是说你没有在pom.xml里指定maven的版本,在pom.xml里添加maven的版本就可以了: <build> <finalName>saas-supplier</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.5.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build>重点是上面那个<version>3.5.1</version>。jdom有的时候会遇到一行简单的警告:[WARNING] The artifact jdom:jdom🫙1.1 has been relocated to org.jdom:jdom🫙1.1这个的意思是说在你的pom.xml里,你需要把jdom的groupId改为org.jdom: <dependency> <groupId>org.jdom</groupId> <artifactId>jdom</artifactId> <version>1.1</version> </dependency>activemq-spring下面这个警告不会在编译时出现,但是会在运行时出现,也非常恶心:SLF4J: Class path contains multiple SLF4J bindings我们需要把pom.xml里的activemq-all改成activemq-spring: <dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-spring</artifactId> <version>5.11.1</version> </dependency>详细解释可以看我的这篇文章。log4j.properties终于改完了pom.xml,我们开始处理log4j。xmemcached如果你使用了xmemcached,那么日志里会不断地出现xmemcached的警告,而这些警告对我们来说根本就不是警告,毫无意义,并且会掩盖真正的错误,所以我们通过修改log4j.properties文件屏蔽它:# xmemcachedlog4j.logger.com.google.code=OFFlog4j.logger.net.rubyeye.xmemcached=OFF我这里比较野蛮粗暴地直接使用了OFF选项,如果你不放心,可以改成ERROR选项,效果是一样的。webapp名称+%c如果你有多个webapp,为了准确显示到底是哪个webapp的哪个class报的错,我们需要在log4j.properties文件里注明我们的webapp名称,再加上一个%c符号:log4j.appender.STDOUT.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss}] %-5p - activity - %c - %m%n这样下回再有任何错误,我们可以第一时间迅速定位到到底是哪个webapp的哪个class出的错误。logrotate如果我们不管不顾的话,Tomcat的日志文件几乎会无限制增长,最终会耗尽我们的硬盘空间,所以我们需要用logrotate来限制它一下,在/etc/logrotate.d文件夹下创建一个文件tomcat:/opt/tomcat1/logs/catalina.out/opt/tomcat2/logs/catalina.out{ copytruncate daily rotate 7 compress missingok size 10M}setenv.shTomcat不问青红皂白,上来就要占领我们主机整个物理内存的四分之一,我们需要限制它的大小,宁可浪费一些CPU和硬盘的时间去让它不断地垃圾回收,也不想让它占用这么多的内存,所以我们需要在/opt/tomcat/bin下建立一个setenv.sh文件,强制让它最多占用1G内存:export CATALINA_OPTS="$CATALINA_OPTS -Xms512m"export CATALINA_OPTS="$CATALINA_OPTS -Xmx1024m"这样我们一台16G内存的主机,可以同时运行16个Tomcat,而不像以前,最多只能同时运行4个Tomcat。jarsToSkipTomcat启动时会不断地扫描所有.jar文件,并且报一些不知所谓的警告:09-Dec-2017 20:03:14.289 FINE [localhost-startStop-1] org.apache.jasper.servlet.TldScanner$TldScannerCallback.scan No TLD files were found in [file:/home/apache-tomcat-8.5.4/lib/tomcat-redis-session-manager-master-2.0.0.jar]. Consider adding the JAR to the tomcat.util.scan.StandardJarScanFilter.jarsToSkip property in CATALINA_BASE/conf/catalina.properties file.直接在/opt/tomcat/conf/catalina.properties文件里把这一句话改成:tomcat.util.scan.StandardJarScanFilter.jarsToSkip=*.jar这个世界终于清静了,扫什么扫,有什么可扫的!startStopThreads当你有多个webapp的时候,Tomcat缺省会启完一个webapp再启下一个,这样太慢了,不可忍受,我们在/opt/tomcat/conf/server.xml文件里把它的启动线程数直接干到20个:<Host name=“localhost” appBase=“webapps” unpackWARs=“true” autoDeploy=“true” startStopThreads=“20” />Dubbo有时候Dubbo也会跳出来捣乱,在每一个不同的webapp下的consumer.xml文件里指定file:<dubbo:registry address=“zookeeper://${zookeeper.address}” file="${dubbo.cache}" />每个webapp的file名称各不相同,它们再也不会互相打架了。结语以上所述也不过只是冰山之一角,好记性不如烂笔头,记录下来作为以后每次优化时的依据,也许对遇到类似问题的你也略有启发吧。 ...

September 24, 2018 · 1 min · jiezi