关于java-web:每日一篇727ThreadLocal

首先要晓得什么是ThreadLocal.在多线程的环境中,每次进行线程切换都须要进行上下文切换,须要横跨若干办法调用十分的麻烦。Java规范库提供了一个非凡的ThreadLocal,它能够在一个线程中传递同一个对象。以把ThreadLocal看成一个全局Map<Thread, Object>:每个线程获取ThreadLocal变量时,总是应用Thread本身作为key: Object threadLocalValue = threadLocalMap.get(Thread.currentThread());因而,ThreadLocal相当于给每个线程都开拓了一个独立的存储空间,各个线程的ThreadLocal关联的实例互不烦扰。 通过HashCode标识每个线程。能够打消在雷同线程应用间断结构的 ThreadLocals 的常见状况下的抵触。 private final int threadLocalHashCode = nextHashCode();以下是ThreadLocal的API: set()办法,设置以后线程中变量的正本。get()办法,获取 ThreadLocal在以后线程中保留的变量正本。remove()办法,清空以后线程中变量的正本。initialValue()是一个 protected办法,个别是用来重写的,如果在没有set的时候就调用 get,会调用 initialValue办法初始化内容。

July 27, 2022 · 1 min · jiezi

关于java-web:JavaWeb学习记录2JDBC

一、疾速入门 // 1.注册驱动 能够省略不写 //Class.forName("com.mysql.jdbc.Driver"); //2.获取连贯 String url = "jdbc:mysql://127.0.0.1:3306/db1?useSSL=true"; String username = "root"; String password = "root"; Connection conn = DriverManager.getConnection(url, username, password); //3.定义sql String sql = "update dept set loc = '厦门' where id = 10"; //4.获取执行sql的对象statement 其实就是编译 Statement stmt = conn.createStatement(); //5.执行sql int count = stmt.executeUpdate(sql); //6.处理结果 System.out.println(count); //7,开释资源 stmt.close(); conn.close();二、JDBC API详解2.1、DriverManager 作用: 1、用于注册驱动 MySQL 5之后的驱动包,能够省略1注册驱动的步骤,因为程序会主动加载。 2、获取数据库连贯 参数阐明:url:示意连贯的数据库的门路。 语法:jdbc:mysql://ip地址(域名):端口号/数据库名称?参数键值对1&参数键值对2… 示例:jdbc:mysql://127.0.0.1:3306/db1 留神:1、如果连贯的是本机mysql服务器,并且mysql服务默认端口是3306,则url能够简写为:jdbc:mysql:///数据库名称?参数键值对。 2、配置 useSSL=false 参数,禁用平安连贯形式,解决正告提醒 示例:jdbc:mysql://127.0.0.1:3306/db1?useSSL=false;2.2、Connection ...

March 23, 2022 · 2 min · jiezi

关于java-web:JavaWeb学习记录1MySQL基础

一:SQL分类1:DDL(Data Definition Language)数据定义语言:用来操作数据库和表,次要就是对数据库的增删查操作和对表进行增(Create)删(Retrieve)改(Update)查(Delete)。数据库1、查问所有的数据库SHOW DATABASES;2、创立数据库CREATE DATABASE 数据库名称;3、创立数据库CREATE DATABASE 数据库名称;CREATE DATABASE IF NOT EXISTS 数据库名称; (判断,如果不存在则创立)4、删除数据库DROP DATABASE 数据库名称;DROP DATABASE IF EXISTS 数据库名称; (判断,如果存在则删除)5、应用数据库USE 数据库名称;数据表1、查问表 查问以后数据库下所有表名称 SHOW TABLES; 查问表构造 DESC 表名称;2、创立表 CREATE TABLE 表名 ( 字段名1 数据类型1, 字段名2 数据类型2, … 字段名n 数据类型n (留神:最初一行开端,不能加逗号) );3、数据类型,MySQL 反对多种类型,能够分为三类: 3.1、数值 tinyint :小整数型,占一个字节 int 大整数类型,占四个字节 eg :age int double 浮点类型 应用格局:字段名 double(总长度,小数点后保留的位数) eg :score double(5,2) 3.2、日期 date :日期值。只蕴含年月日 eg :birthday date : datetime :混合日期和工夫值。蕴含年月日时分秒 3.3、字符串 char :定长字符串。 长处:存储性能高 毛病:节约空间 eg : name char(10) 如果存储的数据字符个数有余10个,也会占10个的空间 varchar : 变长字符串。 长处:节约空间 毛病:存储性能低 eg : name varchar(10) 如果存储的数据字符个数有余10个,那就数据字符个数是几就占几个的空间 4 删除表 删除表 DROP TABLE 表名; 删除表时判断表是否存在 DROP TABLE IF EXISTS 表名; 5 批改表 批改表名 ALTER TABLE 表名 RENAME TO 新的表名; -- 将表名student批改为stu alter table student rename to stu; 增加一列 ALTER TABLE 表名 ADD 列名 数据类型; -- 给stu表增加一列address,该字段类型是varchar(50) alter table stu add address varchar(50); 批改数据类型 ALTER TABLE 表名 MODIFY 列名 新数据类型; -- 将stu表中的address字段的类型改为 char(50) alter table stu modify address char(50); 批改列名和数据类型 ALTER TABLE 表名 CHANGE 列名 新列名 新数据类型; -- 将stu表中的address字段名改为 addr,类型改为varchar(50) alter table stu change address addr varchar(50); 删除列 ALTER TABLE 表名 DROP 列名; -- 将stu表中的addr字段 删除 alter table stu drop addr;2:DML(Data Manipulation Language)数据操作语言:DML次要是对数据进行增(insert)删(delete)改(update)操作。1、增加数据 给指定列增加数据 INSERT INTO 表名(列名1,列名2,…) VALUES(值1,值2,…); 给全部列增加数据 INSERT INTO 表名 VALUES(值1,值2,…); 批量增加数据 INSERT INTO 表名(列名1,列名2,…) VALUES(值1,值2,…),(值1,值2,…),(值1,值2,…)…; INSERT INTO 表名 VALUES(值1,值2,…),(值1,值2,…),(值1,值2,…)…;2、批改表数据 UPDATE 表名 SET 列名1=值1,列名2=值2,… [WHERE 条件]; 留神: 1. 批改语句中如果不加条件,则将所有数据都批改! 2. 像下面的语句中的中括号,示意在写sql语句中能够省略这部分3、删除数据 DELETE FROM 表名 [WHERE 条件];3:DQL(Data Query Language)数据查询语言:用来查询数据库中表的记录(数据),DQL简略了解就是对数据进行查问操作。从数据库表中查问到咱们想要的数据。1、查问的残缺语法: SELECT 字段列表 FROM 表名列表 WHERE 条件列表 GROUP BY 分组字段 HAVING 分组后条件 ORDER BY 排序字段 LIMIT 分页限定2、根底查问2.1 语法查问多个字段SELECT 字段列表 FROM 表名;SELECT * FROM 表名; -- 查问所有数据去除重复记录SELECT DISTINCT 字段列表 FROM 表名;起别名AS: AS 也能够省略3、条件查问3.1、语法SELECT 字段列表 FROM 表名 WHERE 条件列表; ...

February 25, 2022 · 2 min · jiezi

关于java-web:JavaWeb学习笔记03-JSP

JSP原理浏览器向服务器发送申请,不论拜访什么资源,其实都是在拜访Servlet JSP 实质也是Servlet,实现了HttpServlet 判断申请内置对象(pageContext,session,application,config,out,page,request,response)输入页背后减少的代码在JSP中,JAVA代码会一成不变的输入,HTML会被转为out.write("<html>");的格局输入根底语法引入JSTL表达式依赖包javax.servlet.jsp.jstl 和standard标签库 <dependency> <groupId>javax.servlet.jsp.jstl</groupId> <artifactId>jstl-api</artifactId> <version>1.2</version></dependency><dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version></dependency>表达式:<%= 变量或表达式 %>用来将程序输入到客户端<%= new java.util.Date()%> //Tue Nov 23 21:19:01 CST 2021脚本片段:<% java代码 %><% out.println("test");%>代码嵌入HTML元素: ${}<% for(int i=0;i<10;i++){%> <p>${i}</p><% }%>申明:<%! %>JSP的申明,会被编译到JSP生成的类中;其余会生成到_jspService办法中<%! static { System.out.println("test"); }%>正文:<%-- 正文 --%><%-- JSP的正文,不会在客户端显示 --%>指令:<%@ page %> <%@ include %><%@ page errorPage="error/500.jsp" %> //指定500页面标签 <jsp:include /><jsp:include page="/common/header.jsp"/><%@include %>和<jsp:include /> 的区别:1会合并页面 2不会 内置对象PageContextRequestResponseSessionApplication (ServletContext)Config (ServletConfig)outpageException<% pageContext.setAttribute("name1","Name1");//在一个页面中无效 request.setAttribute("name2","Name2"); //在一次申请中无效 session.setAttribute("name3","Name3"); //在一次对话中无效 application.setAttribute("name4","Name4");//在服务器一次开关中无效%><% //从底层到高层(作用域):page > request > session > application //双亲委派机制: String name1 = (String) pageContext.findAttribute("name1"); String name2 = (String) pageContext.findAttribute("name2"); String name3 = (String) pageContext.findAttribute("name3"); String name4 = (String) pageContext.findAttribute("name4");%><h1>${name1}</h1><h2>${name2}</h2><h3>${name3}</h3><h4>${name4}</h4>EL表达式EL表达式:获取数据,执行运算,获取web开发的罕用对象<jsp:forward page="/test.jsp"> <jsp:param name="name" value="xiaoma"/> <jsp:param name="phone" value="13511112222"/></jsp:forward>EL表达式获取参数:<%= request.getParameter("name")%>EL表达式获取表单数据:${param.参数名}JSTL表达式JSP标签集货,封装了JSP利用的通用外围性能援用外围标签库: ...

November 25, 2021 · 2 min · jiezi

关于java-web:spingmvc入门篇-认识servlet

概述Servlet是javax.servlet.Servlet接口的实现,在web容器中,负责接管、解决、响应。每个Servlet仅有一个实例,第一次解决申请时候被创立。 实现实现javax.servlet.Servlet接口继承javax.servlet.GenericServlet继承javax.servlet.http.HttpServletHttpServlethttp服务,通常都继承HttpServlet,实现了很多http的办法和协定。 个别重写doGet、doPost办法,来别离解决get申请和post申请。办法的参数HttpServletRequest、HttpServletResponse,都是对http的申请和响应的封装。 HttpServletRequestHttpServletResponseServletContextServletContextListenerSpringMVC之servletSpringMVC的servlet实现是,DispatchServlet SpringMVC之容器SpringMVC,实现了ServletContextListener接口,org.springframework.web.context.ContextLoaderListener。监听ServletContext初始化事件,基于创立spring容器(WebApplicationContext)。

June 3, 2021 · 1 min · jiezi

关于java-web:关于Web开发编码那些事儿

一个残缺的申请地址http://[username:password@]www.host.com[/contextPath]/servletPath/pathInfo?name=中国contextPath上下文门路,如果部署在根目录下,则省略上下文门路。 例如:http://localhost/上下文门路/servlet门路/门路信息?name=中国 Request URL: http://localhost/**%E4%B8%8A%E4%B8%8B%E6%96%87%E8%B7%AF%E5%BE%84**/*servlet%E8%B7%AF%E5%BE%84*/**%E8%B7%AF%E5%BE%84%E4%BF%A1%E6%81%AF**?name=*%E4%B8%AD%E5%9B%BD* 办法名称原始值浏览器编码后服务器是否解码解码后getContextPath()contextPath上下文门路%E4%B8%8A%E4%B8%8B%E6%96%87%E8%B7%AF%E5%BE%84否 getServletPath()servletPathservlet门路servlet%E8%B7%AF%E5%BE%84是/servlet门路getPathInfo()pathInfo门路信息%E8%B7%AF%E5%BE%84%E4%BF%A1%E6%81%AF是/门路信息getQueryString()queryStringname=中国name=%E4%B8%AD%E5%9B%BD是name=中国经Firefox 和chrome 测试,中文字符浏览器采纳UTF-8编码并加%。

May 21, 2021 · 1 min · jiezi

关于java-web:Offer收割机阿里P7大牛甩出JSP实战笔记网友信息量过大

Hello,明天给各位童鞋们分享JSP,连忙拿出小本子记下来吧! 应用eclipse开发JavaWeb我的项目为新建Javaweb_yq工作站减少Server实例window > preference>server>Runtime Environment > add 增加本地tomcat所在装置目录 为我的项目MyJspProject 增加 tomcat和jdk反对创立好MyJspProject我的项目 为该我的项目增加tomcat和jdk反对。我的项目右键build path add library 退出以下反对:JRE System Library和APache Tomcat 增加jdk是因为tomcat的字节码文件须要在JVM上运行,而jsp须要tomcat/lib中的servlet-api.jar。该jar可将jsp转为servlet 3.部署tomcat 留神:个别倡议 将eclipse中的tomcat与 本地tomcat的配置信息放弃同步:即 将eclipse中的tomcat设置为托管模式。 操作形式是:【第一次】创立tomcat实例之后, 双击,抉择Server Location的第二项 否则默认是将tomcat备份到我的项目中,配置信息是与tomcat独立的。 Jsp对立字符编码 设置jsp文件的编码,该编码用于Jsp编译成Java文件时,通过jsp文件中的pageEncoding属性设置。 设置浏览器读取jsp文件编码,通过jsp文件中的content属性设置。 个别将上述设置成统一的编码,举荐应用utf-8 文本编码,jsp文件以什么编码模式保留。可有以下三种形式设置: eclipse全工作站设置。如下: 设置某个我的项目 设置独自文件 Jsp的页面元素Jsp页面元素包含:HTML、Java代码(也就是嵌套在脚本中的javadiamante)、指令、正文1.脚本<% 局部变量、java语句 %> 2. <%! 全局变量、定义方法 %> 3. <%= 输入表达式> 源代码: f12所看到的: 留神,out.println()不能回车; 要想回车:“”,即out.print()、 <%= %> 能够间接解析html代码。 指令page指令 language属性:jsp页面应用的脚本语言 import : 导入类 pageEncoding: Jsp 文件翻译成java文件时的编码 contentType:浏览器解析jsp时的编码 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="java.util.Date" %>正文html正文<!-- --> ,能够被客户 通过浏览器查看源码 所察看到 ...

May 8, 2021 · 2 min · jiezi

关于java-web:mybatis-plus内置Service接口方法Ehcache缓存无效

springboot+mybatis 配置缓存Ehcache1.pom.xml 2.ehcache.xmlresources文件夹下ehcache.xml文件 3.mappers.xml自定义缓存ehcache,增加查询方法 4.测试内置Service接口办法和本人增加的查询方法 5.测试后果,内置Service接口办法不走缓存,增加的查询方法,走缓存。

December 24, 2020 · 1 min · jiezi

关于java-web:JavaWeb中实现文件上传的方式有哪些

上回咱们说了下文件下载的形式有哪些,这次咱们从不同的环境下简略来说说文件上传的形式有哪些。 文件上传的形式Servlet2.5 形式Servlet3.0 形式SpringMVC 形式案例实操Servlet2.5 形式文件上传波及到前台页面的编写和后盾服务器端代码的编写,前台发送文件,后盾接管并保留文件,这才是一个残缺的文件上传。 1) 前台页面 在做文件上传的时候,会有一个上传文件的界面,首先咱们须要一个表单,并且表单的申请形式为 POST;其次咱们的 form 表单的 enctype 必须设为”multipart/form-data”即 enctype="multipart/form-data" 意思是设置表单的 MIME 编码。默认状况下这个编码格局是 ”application/x-www-form-urlencoded”,不能用于文件上传;只有应用了 multipart/form-data 能力残缺地传递文件数据。 <!DOCTYPE html><html><head><meta charset="UTF-8"><title>上传文件</title></head><body> <form action="uploadServlet" method="post" enctype="multipart/form-data"> 文件:<input type="file" name="myfile"/> <input type="submit" value="上传" /> </form></body></html> 2) 后盾 commons-fileupload 的应用 首先须要导入第三方jar包,http://commons.apache.org/ 下载 commons-io 和 commons-fileupload 两个jar的资源。解压并导入到我的项目中。commons-fileupload.jar 是文件上传的外围包 commons-io.jar 是 fileupload 的依赖包,同时又是一个工具包。 介绍一下应用到的几个外围类 DiskFileItemFactory – 设置磁盘空间,保留临时文件。只是一个工具类 ServletFileUpload – 文件上传的外围类,此类接管 request,并解析 ServletFileUpload.parseRequest(request); – List 解析 request 1、创立一个 DiskFileItemFactory 工厂类,并制订临时文件和大小 2、创立 ServletFileUpload 外围类,接管临时文件,做申请的转换 3、通过 ServletFileUpload 类转换原始申请,失去 FileItem 汇合 ...

December 22, 2020 · 3 min · jiezi

关于java-web:JavaWeb中实现文件上传的方式有哪些

问题:JavaWeb中实现文件上传的形式有哪些?文件上传的形式Servlet2.5 形式Servlet3.0 形式SpringMVC 形式案例实操Servlet2.5 形式文件上传波及到前台页面的编写和后盾服务器端代码的编写,前台发送文件,后盾接管并保留文件,这才是一个残缺的文件上传。 1) 前台页面 在做文件上传的时候,会有一个上传文件的界面,首先咱们须要一个表单,并且表单的申请形式为 POST;其次咱们的 form 表单的 enctype 必须设为”multipart/form-data”即 enctype=“multipart/form-data” 意思是设置表单的 MIME 编码。默认状况下这个编码格局是 ”application/x-www-form-urlencoded”,不能用于文件上传;只有应用了 multipart/form-data 能力残缺地传递文件数据。 <!DOCTYPE html><html><head><meta charset="UTF-8"><title>上传文件</title></head><body> <form action="uploadServlet" method="post" enctype="multipart/form-data"> 文件:<input type="file" name="myfile"/> <input type="submit" value="上传" /> </form></body></html> 2) 后盾 commons-fileupload 的应用 首先须要导入第三方jar包,http://commons.apache.org/ 下载 commons-io 和 commons-fileupload 两个jar的资源。解压并导入到我的项目中。commons-fileupload.jar 是文件上传的外围包 commons-io.jar 是 fileupload 的依赖包,同时又是一个工具包。 介绍一下应用到的几个外围类 DiskFileItemFactory – 设置磁盘空间,保留临时文件。只是一个工具类 ServletFileUpload – 文件上传的外围类,此类接管 request,并解析 ServletFileUpload.parseRequest(request); – List 解析 request 1、创立一个 DiskFileItemFactory 工厂类,并制订临时文件和大小 2、创立 ServletFileUpload 外围类,接管临时文件,做申请的转换 ...

December 7, 2020 · 3 min · jiezi

关于java-web:第五阶段-第二模块

getmethod第一个参数为办法名,前面两个参数为参数的字节码invoke 第一个参数为调用的对象 前面两个为参数 where 1=1这个条件次要用于 对于不定的拼接项 都是为and结尾 如果没有where 则会产生间接and 这种语法错误 枚举类 SUCCESS(0,"success"),FAIL(1,"fail"); 为返回的两个字段 外面对应的值为成员变量 beanutils这个工具类是用来在servlet中接管前端传过来的数据封装到javabean中的 首先new BeanListHandler<Course>这个会查出全副字段 除了select以外的为null 参数汇合要转为数组

September 27, 2020 · 1 min · jiezi

关于java-web:第五阶段-第一模块

getmethod第一个参数为办法名,前面两个参数为参数的字节码invoke 第一个参数为调用的对象 前面两个为参数 where 1=1这个条件次要用于 对于不定的拼接项 都是为and结尾 如果没有where 则会产生间接and 这种语法错误 枚举类 SUCCESS(0,"success"),FAIL(1,"fail"); 为返回的两个字段 外面对应的值为成员变量

September 25, 2020 · 1 min · jiezi

关于java-web:第四阶段-第二模块

顺次

September 19, 2020 · 1 min · jiezi

关于java-web:案例采用-MVC-设计模式基于三层架构实现学生信息管理系统

需要阐明应用前端、数据库、JavaWeb 等技术并采纳 MVC 设计模式,基于三层架构实现学生信息管理系统其中学生信息有:学号、姓名、性别、出生日期要求:a. 实现用户登陆性能 (判断用户输出的用户名明码和数据库存储的用户名明码是否雷同) 当登陆胜利 跳转到首页显示登陆胜利(页面、数据库表自行设计实现)b.实现用户增加性能(首先跳转到增加页面,将页面表单中填写的内容保留到数据库中)c.应用过滤器对增加用户的申请进行拦挡,判断是否是未登录状态 未登录:跳转到登陆页面 已登陆:放行到指标资源,实现增加操作 提醒:用户登陆时,将用户信息存到session中,过滤器中判断session中有没有用户信息思路整顿 代码目录 注意事项jar包须要放在tomcat工程下的lib文件夹中, 即web->WEB-INF->lib(没有就本人创立,并鼠标右键点击,抉择Add as Library). 肯定不能放在External Libraries. 否则非常容易造成找不到包提交表单时, <form action="">action属性的值须要是 tomcat的工程门路 / 指标Servlet的门路(也就是web.xml中的url-pattern)我的项目地址

September 17, 2020 · 1 min · jiezi

关于java-web:JavaWeb-Servlet生命周期

思维介绍生命周期: 指的是一个对象从生(创立)到死(销毁)的一个过程// 1. Servlet对象创立时, 调用此办法public void init(ServletConfig servletConfig){}// 2. 用户拜访Servlet时, 调用此办法public void service(ServletRequest servletRequest, ServletResponse servletResponse){}// 3. Servlet对象销毁时, 调用此办法public void destroy(){}代码演示LifeServletpublic class LifeServlet implements Servlet { @Override public void init(ServletConfig servletConfig) throws ServletException { System.out.println("Servlet的init办法调用了,该对象被初始化...."); } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { System.out.println("Servlet的service办法调用了, 开始执行业务逻辑...."); } @Override public String getServletInfo() { return null; } @Override public void destroy() { System.out.println("Servlet的destroy办法调用了, 对象被销毁了..."); }}配置web.xml<!-- LifeSevlet --><servlet> <servlet-name>LifeServlet</servlet-name> <servlet-class>com.bigdata.life.LifeServlet</servlet-class> <!-- 配置上该标签,servlet就会在启动服务器的时候去实现实例化, 并进行初始化操作 标签外部须要写数值. 正整数1-3在tomcat的web.xml被应用了, 所以倡议从4开始应用 --> <load-on-startup>4</load-on-startup> <!-- 服务器加载时就进行servlet的初始化操作 --></servlet><servlet-mapping> <servlet-name>LifeServlet</servlet-name> <url-pattern>/lifeservlet</url-pattern></servlet-mapping>

September 17, 2020 · 1 min · jiezi

关于java-web:第四阶段-第一模块

September 14, 2020 · 0 min · jiezi

关于java-web:从零开始学习-JavaWeb-05-Servlet

References: javaweb学习总结(五)——Servlet开发(一) Servlet 概念:上面是搬运,下面的 References 中的链接,该一系列的JavaWeb入门曾经很清晰,如果感觉不能了解自己写的,能够去看看下面JavaWeb系列的文章,编写这个系列文章的大佬写得很清晰了。 Servlet简介 Servlet是sun公司提供的一门用于开发动静web资源的技术。 Sun公司在其API中提供了一个servlet接口,用户若想用发一个动静web资源(即开发一个Java程序向浏览器输入数据),须要实现以下2个步骤: 1、编写一个Java类,实现servlet接口。 2、把开发好的Java类部署到web服务器中。 依照一种约定俗成的称说习惯,通常咱们也把实现了servlet接口的java程序,称之为ServletServlet的运行过程 Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet拜访申请后: ①Web服务器首先查看是否曾经装载并创立了该Servlet的实例对象。如果是,则间接执行第④步,否则,执行第②步。 ②装载并创立该Servlet的一个实例对象。 ③调用Servlet实例对象的init()办法。 ④创立一个用于封装HTTP申请音讯的HttpServletRequest对象和一个代表HTTP响应音讯的HttpServletResponse对象,而后调用Servlet的service()办法并将申请和响应对象作为参数传递进去。 ⑤WEB应用程序被进行或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()办法。 占个地位,我前面补全

September 4, 2020 · 1 min · jiezi

关于java-web:从零开始学习-JavaWeb-04-创建JavaWeb

简述: 在后面的文章中,咱们曾经下载并装置 IDEA、Tomcat 和 Maven了。当初来通过IDEA 创立带有 Maven 的 JavaWeb。 创立 JavaWeb:1.创立简略的JavaWeb模板: 1.关上IDEA,抉择上面的选项创立目录. 2.呈现上面的页面,抉择 maven -> 勾选 Create from archetype -> 抉择 org.apache.maven.archetypes:maven-archetype-webapp -> Next. 留神图下第二步下面咱们能够抉择 JDK 版本。蓝色框 的也是一个 webapp, 框出来是为了揭示别选错 webapp. 3.抉择我的项目寄存的目录地位,上面红色框柱的 GroupId、ArtifactId、Version 这几个,我前面补全,如果本人想晓得的,能够先去查别的文档。因为是学习Demo,我间接用默认的。 4.抉择Maven 的版本后,抉择右下角的 FINSH 实现按键,而后上面的配置,我也是前面补全。 5.实现后呈现上面的是因为要装置 依赖/插件,这个时候,咱们能够看到目录构造中是没有 src 文件夹的。装置可能比拟久,不能心急。 6.依赖/插件装置后后,会呈现 src 这个文件夹如果没有,能够按上面进行 ????按住上面图片第一右键文件夹,选中 Reload from Disk, 刷新一下。 ????src的目录构造 2.webapp的创立: 因为这里我不想将 webapp 放在 main 外面,我这里会把 webapp 删除掉,而后在 src 的同级目录下创立先的 webapp. 如果不想看这里的话,能够跳到看下一步。 1.关上 Project Structure, Project Structure 在菜单 Flie 选项上面,咱们能够用快捷键 ⌘ + ; 关上。 ...

September 4, 2020 · 2 min · jiezi

如何在backoffice里创建Hybris-image-container以及分配给product

登录backoffice,在media container视图点击新建按钮: Catalog选择Product Catalog: 在Properties界面,可以选择media实例放入该container: 同步到online catalog: 同步之后,就可以把这个media container分配给product了: 在product的Administration标签页,Gallery images字段里分配media container: 要获取更多Jerry的原创文章,请关注公众号"汪子熙":

November 4, 2019 · 1 min · jiezi

hybris-backoffice创建product遇到的synchronization问题和解答

我从product DSC-H20_MD clone了一个新的product,code为DSC-H20_MD1 因为它的状态有个红灯: 所以我点了这个sync按钮: 结果报这个错: 之后这个clone出来的product就无法从backoffice里搜索到了。请教一下这种情况该如何解决呢? 后台这个clone出来的product也无法从product表里读取出来了: Jerry请教了兄弟团队的Hybris专家Kevin,得到了解答: 因为我直接clone的online版本,而stage版本里是没有这个product的,同步的意思是以stage为基准去更新online,所以我一同步就会把online里的删掉。所以我选择从一个stage版本的product clone就好了。 要获取更多Jerry的原创文章,请关注公众号"汪子熙":

November 4, 2019 · 1 min · jiezi

如何在Hybris-commerce里创建一个media对象

进入backoffice的Media中心,首先新建一个文件夹,用于存放即将创建的media对象:取名为jerryimage: 然后创建一个新的media对象,取名jerryproductimage:上传图片: 选择这个media对象存放的文件夹: 从staged catalog同步到online catalog: 同步成功:要获取更多Jerry的原创文章,请关注公众号"汪子熙":

November 4, 2019 · 1 min · jiezi

hybris-commerce-storefront的产品搜索功能

在Hybris Commerce Cloud的storefront的搜索栏键入一些字母,每次键入,会触发一个发送到后台的http请求实现live search的功能: http url如下:https://&lt;host>/electronics/en/search/autocomplete/SearchBox?term=DSC-H20_MD 注意我们键入的是产品名称,而非产品code,后者只能从Chrome开发者工具的http response里观察到: 这里能清楚地看到,DSC-H20_MD是产品的名称。 从backoffice也能清楚看到product code和name这两个字段的区分: 要获取更多Jerry的原创文章,请关注公众号"汪子熙":

November 4, 2019 · 1 min · jiezi

Hybris做增强的两种方式In-App-Extension和Side-by-Side-Extension

传统的扩展方式,即In-App增强方式,Hybris开发顾问通过Extensions的方式进行二次开发,生成的Custom Extensions同Hybris标准的Extensions一起参加构建,构建结束后新功能方可使用。在构架过程中,Hybris实例暂时无法访问(down time)。这种方式允许Hybris顾问以较高的灵活度在Custom Extensions里编写代码来实现增强需求。<img width="657" alt="clipboard" src="https://user-images.githubuse...;> 借助SAP Cloud Platform Extension Factory实现的Side-by-side增强,不需要修改Hybris实例(图二虽然标注的是Commerce Cloud,但对Hybris Commerce On-Premises版本仍然适用)本身的代码,而只需在Extension Factory上编写针对Hybris标准程序发布事件的响应处理函数。例如客户需求是当Hybris Storefront上有新用户注册,或者新订单生成时,实现一段自定义逻辑——这类事件驱动的增强需求,采用Extension Factory增强,开发效率较In-App增强更高,实现更轻量,但前提是Hybris标准应用在需要被增强的业务流程上对事件发布有完善的支持。<img width="791" alt="clipboard2" src="https://user-images.githubuse...;>

November 4, 2019 · 1 min · jiezi

Spring5源码解析4refresh方法之invokeBeanFactoryPostProcessors

invokeBeanFactoryPostProcessors(beanFactory);方法源码如下: protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) { // getBeanFactoryPostProcessors 获取的是 this.beanFactoryPostProcessors; //this.beanFactoryPostProcessors 只能通过 AbstractApplicationContext.addBeanFactoryPostProcessor 方法添加 PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()); // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime // (e.g. through an @Bean method registered by ConfigurationClassPostProcessor) if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) { beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory)); beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader())); }}getBeanFactoryPostProcessors()方法获取的是AbstractApplicationContext#beanFactoryPostProcessors这个成员变量。 这个成员变量只能通过代码中手动编码调用AbstractApplicationContext#addBeanFactoryPostProcessor方法来添加新的元素。很明显,我们这里为空。 invokeBeanFactoryPostProcessors(beanFactory)方法的主要的逻辑在PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors方法中: //PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors())源码public static void invokeBeanFactoryPostProcessors( ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) { // Invoke BeanDefinitionRegistryPostProcessors first, if any. Set<String> processedBeans = new HashSet<>(); if (beanFactory instanceof BeanDefinitionRegistry) { BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>(); List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>(); //beanFactoryPostProcessors是传进来里的对象,把传入的对象分类放入 BeanFactoryPostProcessor 和 BeanDefinitionRegistryPostProcessor //BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor ,是一个特殊的 BeanFactoryPostProcessor for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) { if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) { BeanDefinitionRegistryPostProcessor registryProcessor = (BeanDefinitionRegistryPostProcessor) postProcessor; //如果传入的beanFactoryPostProcessors是它的子类,即:BeanDefinitionRegistryPostProcessor //则执行传入的BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法 registryProcessor.postProcessBeanDefinitionRegistry(registry); registryProcessors.add(registryProcessor); } else { regularPostProcessors.add(postProcessor); } } // Do noitialize FactoryBeans here: We need to leave all regular beans // uninitialized to let the bean factory post-processors apply to them! // Separate between BeanDefinitionRegistryPostProcessors that implement // PriorityOrdered, Ordered, and the rest. List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>(); // First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered. //这里只能拿到spring内部的BeanDefinitionRegistryPostProcessor, //因为到这里spring还没有去扫描Bean,获取不到我们通过@Component标识的自定义BeanDefinitionRegistryPostProcessor //一般默认情况下,这里只有一个,BeanName:org.springframework.context.annotation.internalConfigurationAnnotationProcessor //对应的BeanClass:ConfigurationClassPostProcessor String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); for (String ppName : postProcessorNames) { if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) { //beanFactory.getBean, 这里开始创建BeanDefinitionRegistryPostProcessor bean 了 currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); processedBeans.add(ppName); } } //排序 sortPostProcessors(currentRegistryProcessors, beanFactory); // registryProcessors 中放的是 BeanDefinitionRegistryPostProcessor // 因为这里只执行eanDefinitionRegistryPostProcessor中独有的方法,而不会执行其父类即BeanFactoryProcessor的方法 // 所以这里需要把处理器放入一个集合中,后续统一执行父类的方法 registryProcessors.addAll(currentRegistryProcessors); // 执行BeanDefinitionRegistryPostProcessor,currentRegistryProcessors中放的是spring内部的BeanDefinitionRegistryPostProcessor // 默认情况下,只有 org.springframework.context.annotation.ConfigurationClassPostProcessor // ConfigurationClassPostProcessor 里面就是在执行扫描Bean,并且注册BeanDefinition invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); // 清空这个临时变量,方便后面再使用 currentRegistryProcessors.clear(); // Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered. // 这里已经可以获取到我们通过注册到Spring容器的 BeanDefinitionRegistryPostProcessor 了 postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); for (String ppName : postProcessorNames) { // 之前优先处理的是实现PriorityOrdered接口的,而PriorityOrdered接口也实现了Ordered接口 // 所有这里需要把之前已经处理过的给过滤掉 if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) { //之前这个临时变量已经被清空了,现在又开始放东西了 currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); processedBeans.add(ppName); } } //排序 sortPostProcessors(currentRegistryProcessors, beanFactory); registryProcessors.addAll(currentRegistryProcessors); // 执行BeanDefinitionRegistryPostProcessor invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); //清空临时变量 currentRegistryProcessors.clear(); // Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear. boolean reiterate = true; while (reiterate) { reiterate = false; postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); for (String ppName : postProcessorNames) { //执行没有实现Ordered接口的BeanDefinitionRegistryPostProcessor if (!processedBeans.contains(ppName)) { currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); processedBeans.add(ppName); reiterate = true; } } sortPostProcessors(currentRegistryProcessors, beanFactory); registryProcessors.addAll(currentRegistryProcessors); invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); currentRegistryProcessors.clear(); } // Now, invoke the postProcessBeanFactory callback of all processors handled so far. // List<BeanDefinitionRegistryPostProcessor> registryProcessors // 之前已经执行过BeanDefinitionRegistryPostProcessor独有方法,现在执行其父类方法 invokeBeanFactoryPostProcessors(registryProcessors, beanFactory); // List<BeanFactoryPostProcessor> regularPostProcessors // 执行 BeanFactoryPostProcessor 方法 invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory); } else { // Invoke factory processors registered with the context instance. invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory); } // Do not initialize FactoryBeans here: We need to leave all regular beans // uninitialized to let the bean factory post-processors apply to them! // 获取 BeanFactoryPostProcessor 的 beanName String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false); // Separate between BeanFactoryPostProcessors that implement PriorityOrdered, // Ordered, and the rest. List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>(); List<String> orderedPostProcessorNames = new ArrayList<>(); List<String> nonOrderedPostProcessorNames = new ArrayList<>(); for (String ppName : postProcessorNames) { // 如果已经被执行过了,就不在执行 // 因为一开始先获取的BeanDefinitionRegistryPostProcessor,而BeanDefinitionRegistryPostProcessor继承了BeanFactoryPostProcessor if (processedBeans.contains(ppName)) { // skip - already processed in first phase above } else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) { priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class)); } else if (beanFactory.isTypeMatch(ppName, Ordered.class)) { orderedPostProcessorNames.add(ppName); } else { nonOrderedPostProcessorNames.add(ppName); } } // First, invoke the BeanFactoryPostProcessors that implement PriorityOrdered. // 根据不同的优先级,按序执行 BeanFactoryPostProcessor sortPostProcessors(priorityOrderedPostProcessors, beanFactory); invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory); // Next, invoke the BeanFactoryPostProcessors that implement Ordered. List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<>(); for (String postProcessorName : orderedPostProcessorNames) { orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class)); } sortPostProcessors(orderedPostProcessors, beanFactory); invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory); // Finally, invoke all other BeanFactoryPostProcessors. List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<>(); for (String postProcessorName : nonOrderedPostProcessorNames) { nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class)); } invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory); // Clear cached merged bean definitions since the post-processors might have // modified the original metadata, e.g. replacing placeholders in values... beanFactory.clearMetadataCache();}源码超级长,我们慢慢来看。 ...

October 4, 2019 · 3 min · jiezi

Servlet技术requestrespone详解

Servlet之request、respone详解 Request(一) 概述request是Servlet.service()方法的一个参数,在客户端发出每个请求时,服务器都会创建一个request对象,并把请求数据封装到request中,然后在调用Servlet.service()方法时传递给service()方法 HttpServletRequest对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中,开发人员通过这个对象的方法,可以获得客户这些信息 (二) 常用方法(1) 域方法存储 //用来存储一个对象,也可以称之为存储一个域属性void setAttribute(String name, Object value) Eg:servletContext.setAttribute(“xxx”, “XXX”)//在ServletContext中保存了一个域属性,域属性名称为xxx,域属性的值为XXX获取 //用来获取ServletContext中的数据Object getAttribute(String name)//获取名为xx的域属性Eg:String value = (String)servletContext.getAttribute(“xxx”);//获取所有域属性的名称;Enumeration getAttributeNames()移除 //用来移除ServletContext中的域属性void removeAttribute(String name)(2) 获取请求头数据//获取指定名称的请求头String getHeader(String name) //获取所有请求头名称Enumeration getHeaderNames() //获取值为int类型的请求头int getIntHeader(String name)(3) 获取请求相关的其他方法//获取请求体的字节数,GET请求没有请求体,没有请求体返回-1;int getContentLength()/* 获取请求类型,如果请求是GET,那么这个方法返回null;如果是POST请求,那么默认 为application/x-www-form-urlencoded,表示请求体内容使用了URL编码;*/String getContentType()//返回请求方法,例如:GET/POSTString getMethod() //返回当前客户端浏览器的Locale。java.util.Locale表示国家和言语,这个东西在国际化中很有用;Locale getLocale() /* 获取请求编码,如果没有setCharacterEncoding(),那么返回null,表示使用 ISO-8859-1编码;*/String getCharacterEncoding() /* 设置请求编码,只对请求体有效!注意,对于GET而言,没有请求体!!!所以此方法 只能对POST请求中的参数有效!*/void setCharacterEncoding(String code) //返回上下文路径,例如:/Dmoe1String getContextPath() //返回请求URL中的参数,例如:username=zhangSanString getQueryString() //返回请求URI路径,例如:/Demo1/ServletDemo1String getRequestURI() /* 返回请求URL路径,例如:http://localhost/Demo1/ServletDemo1即返回除了参数 以外的路径信息;*/StringBuffer getRequestURL() //返回Servlet路径,例如:/ServletDemo1String getServletPath() //返回当前客户端的IP地址String getRemoteAddr() //返回当前客户端的主机名,但这个方法的实现还是获取IP地址String getRemoteHost() //返回请求协议,例如:httpString getScheme()//返回主机名,例如:localhostString getServerName() //返回服务器端口号,例如:8080int getServerPort()为了方便记忆,我们画一张图辅助记忆 ...

August 28, 2019 · 3 min · jiezi

Week-2-Java-容器-详细剖析-List-之-ArrayList-Vector-LinkedList

前言学习情况记录 时间:week 2SMART子目标 :Java 容器记录在学习Java容器 知识点中,关于List的需要重点记录的知识点。 知识点概览: ArrayList 与 LinkedList对比ArrayList 中的 RandomAccess 接口 是什么?LinkedList 中的 Deque 接口 是什么?老调常谈 之 ArrayList 扩容机制ArrayList 与 Vector 对比 ArrayList 与 LinkedList对比底层数据结构: ArrayList 底层使用的Object数组,默认大小 10。** LinkedList 底层使用的是双向链表数据结构(JDK1.6之前为循环链表,JDK1.7取消了循环。注意双向链表和双向循环链表的区别)。LinkedList 包含了3个重要的成员:size、first、last。size是双向链表中节点的个数,first和last分别指向第一个和最后一个节点的引用。 插入和删除是否受元素位置的影响: 由于底层实现的影响,ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。比如:执行add(E e)方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element))时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。实际就是近似O(n)。而LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)是否支持快速随机访问:这个也是由底层实现决定的,LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。内存空间占用:ArrayList的空间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放prev 指针和next 指针以及数据)。ArrayList 中的 RandomAccess 接口 是什么? 前面的图中我们可以看到ArrayList 继承了三个接口,后面两个都是比较熟悉的,分别是标识对象可复制和可序列化。 那么RandomAccess 接口代表什么呢? 前面的关于ArrayList 与 LinkedList 的对比当中,有一点就是, ...

July 15, 2019 · 1 min · jiezi

webxml详解

web.xml详解一、web.xml简介在java工程中,web.xml用来初始化工程配置信息,比如说welcome页面,filter,listener,servlet,servlet-mapping,启动加载级别等等。每一个xml文件都有定义格式规范的schema文件,web.xml所对应的xml Schema文件中定义了多少种标签元素,web.xml中就可以出现它所定义的标签元素,也就具备哪些特定的功能。web.xml的模式文件是由Sun 公司定义的,每个web.xml文件的根元素为<web-app>中,必须标明这个web.xml使用的是哪个模式文件。 web.xml的根元素定义如下所示: <?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> </web-app>二、web.xml标签介绍下面就来介绍一下web.xml中常用的标签及其功能: 1. description:项目描述2. display-name:项目名称3. icon :web图标包含两个子元素small-icon和large-icon,指定web站台中小图标和大图标的路径 <display-name>Develop Example</display-name> <description>JSP 2.0 Tech Book's Examples</description> <icon> <small-icon>/images/small.gif</small-icon> <large-icon>/images/large.gir</large-icon> </icon>4. context-param:初始化参数元素含有一对参数名和参数值,用作应用的servlet上下文初始化参数。参数名在整个Web应用中必须是惟一的。包含两个子元素param-name和param-value <context-param> <param-name>param_name</param-name> <param-value>param_value</param-value></context-param>此所设定的参数,在JSP网页中可以使用下列方法来取得:${initParam.param_name} 若在Servlet可以使用下列方法来获得:String param_name=getServletContext().getInitParamter("param_name"); 5. filter:过滤器指定Web容器中的过滤器,请求和响应对象被servlet处理之前或之后,可以使用过滤器对这两个对象进行操作。包含子元素: init-param:与context-param 元素具有相同的作用filter-name:用来定义过滤器的名称,该名称在整个应用中都必须是惟一的filter-class:指定过滤器类的完全限定的名称6. filter-mapping:映射filter和filter一起使用,指定过滤器被映射到一个servlet或一个URL模式。和filter必须具有相同的名称。过滤是按照部署描述符的filter-mapping元素出现的顺序执行的。包含子元素: filter-name:定义filter名称url-pattern:filter所对应的urlservlet-name:定义filter的名称dispatcher:定Filter对应的请求方式,有RQUEST,INCLUDE,FORWAR,ERROR四种,默认为REQUEST <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>7. servlet:处理请求响应用于处理及响应客户端的需求,动态生成web内容。 8. srvlet-mapping:映射servlet用来定义servlet所对应URL,和servlet必须具有相同的名称。 <servlet> <servlet-name>ServletName</servlet-name> <servlet-class>xxxpackage.xxxServlet</servlet-class> <!--Servlet的类--> <init-param> <!--初始化一个变量,可看成全局变量,可省略--> <param-name>参数名称</param-name> <!--变量名称--> <param-value>参数值</param-value> <!--变量值--> </init-param></servlet><servlet-mapping> <servlet-name>ServletName</servlet-name> <url-pattern>/aaa/xxx</url-pattern> <!--映射的url路径 --></servlet-mapping>9. listener:监听器可以收到事件什么时候发生以及用什么作为响应的通知 <listener> <listener-class>com.foo.hello</listener-class></listener>10. welcome-file-list:首页包含一个子元素welcome-file,用来定义首页,服务器会依照设定的顺序来找首页 ...

July 4, 2019 · 1 min · jiezi

前后端分离ssm配置swagger接口文档

之前配置过springboot,相比ssm要简单很多,现在记录一下ssm的配置 在pom.xml中加入依赖<!--swagger本身不支持spring mvc的,springfox把swagger包装了一下,让他可以支持springmvc--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.6.1</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.6.1</version> </dependency>添加配置类SwaggerConfig.java@WebAppConfiguration@EnableSwagger2@EnableWebMvc@ComponentScan(basePackages = "com.maxcore.controller")public class SwaggerConfig { @Bean public Docket customDocket() { // return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.any()) .build() .apiInfo(apiInfo()); } private ApiInfo apiInfo() { Contact contact = new Contact("娜", "https://www.baidu.me", "baidu_666@icloud.com"); return new ApiInfo("仿简书前台API接口",//大标题 title "Swagger测试demo",//小标题 "0.0.1",//版本 "www.baidu.com",//termsOfServiceUrl contact,//作者 "Blog",//链接显示文字 "https://www.baidu.me"//网站链接 ); }}在dispatcher-servlet.xml(springmvc的配置文件)中加入如下配置 <bean class="com.maxcore.config.SwaggerConfig" /> <mvc:resources mapping="swagger-ui.html" location="classpath:/META-INF/resources/" /> <mvc:resources mapping="/webjars/**" location="classpath:/META-INF/resources/webjars/" />要在controller层添加注解 最后启动项目,访问swagger接口文档的路径一定要对,不然一直报404,你以为你没配置对,其实是你路径不对,笔者在这里表示有很痛的领悟笔者的本地的访问路径是 http://localhost/jianShuSSM_w... 一般都是http://ip地址:端口(默认80,不显示)/项目名/swagger-ui.html ...

June 22, 2019 · 1 min · jiezi

java中RedirectAttributes类的使用

一、RedirectAttributes类简介RedirectAttributes是Spring mvc 3.1版本之后出来的一个功能,专门用于重定向之后还能带参数跳转的工具类使用此类引入包:import org.springframework.web.servlet.mvc.support.RedirectAttributes;划重点:用于重定向携带参数二、类中常用方法介绍addAttributie方法 redirectAttributes.addAttributie("param1",value1); redirectAttributes.addAttributie("param2",value2); return "redirect:/path/list" ;注意:这个方法是用来跳转的时候,将参数直接暴露在url中,等同于重定向到:return "redirect:/path/list?prama1=value1&param2=value2 " addFlashAttributie方法 redirectAttributes.addFlashAttributie("prama1",value1); redirectAttributes.addFlashAttributie("prama2",value2);

June 14, 2019 · 1 min · jiezi

从零入门系列3Sprint-Boot-之-数据库操作类

文章系列【从零入门系列-0】Sprint Boot 之 Hello World【从零入门系列-1】Sprint Boot 之 程序结构设计说明【从零入门系列-2】Sprint Boot 之 数据库实体类前言前一章简述了如何设计实现数据库实体类,本篇文章在此基础上进行开发,完成对该数据库表的常用操作,主要包括使用Spring Data JPA进行简单的增删改查和复杂查询操作。 Spring Data JPA是Spring提供的一套简化JPA开发的框架,按照约定好的【方法命名规则】写dao层接口,就可以在不写接口实现的情况下,实现对数据库的访问和操作,同时提供了很多除了CRUD之外的功能,如分页、排序、复杂查询等等,Spring Data JPA 可以理解为 JPA 规范的再次封装抽象,底层还是使用了 Hibernate 的 JPA 技术实现。通过引入Spring Data JPA后,我们可以基本不用写代码就能实现对数据库的增删改查操作。 此外,由于Spring Data JPA自带实现了很多内置的后台操作方法,因此在调用方法时必须根据其规范使用,深刻理解规范和约定。 表的基本操作实现(CRUD)在这里,先介绍一下JpaRepository,这是类型为interface的一组接口规范,是基于JPA的Repository接口,能够极大地减少访问数据库的代码编写,是实现Spring Data JPA技术访问数据库的关键接口。 编写数据操作接口在使用时,我们只需要定义一个继承该接口类型的接口即可实现对表的基本操作方法,在此我们需要对实体类Book进行操作,因此在Dao目录上右键New->Java Class,然后设置名称为BookJpaRepository,kind类型选Interface即可,然后添加注解及继承自JpaRepository,文件BookJpaRepository.java内容如下所示: package com.arbboter.demolibrary.Dao;import com.arbboter.demolibrary.Domain.Book;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.stereotype.Repository;@Repositorypublic interface BookJpaRepository extends JpaRepository<Book, Integer> {}@Repository持久层组件,用于标注数据访问组件,即DAO组件,此时配合上上一篇文章中的JPA配置,我们就可以进行增删改查啦,不用添加任何其他代码,因为JpaRepository已经帮我们实现好了。 编写测试用例代码打开框架自动生成的测试代码文件DemoLibraryApplicationTests.java编写测试用例,测试增删改查效果,测试代码如下: @RunWith(SpringRunner.class)@SpringBootTestpublic class DemoLibraryApplicationTests { /** * @Autowired 注释,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 * 通过 @Autowired的使用来消除 set ,get方法,简化程序代码 * 此处自动装配我们实现的BookJpaRepository接口,然后可以直接使用bookJpaRepository操作数据库 * 如果不加@Autowired,直接使用bookJpaRepository,程序运行会抛出异常 */ @Autowired private BookJpaRepository bookJpaRepository; @Test public void contextLoads() { Book book = new Book(); // 增 book.setName("Spring Boot 入门学习实践"); book.setAuthor("arbboter"); book.setImage("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2656353677,2997395625&fm=26&gp=0.jpg"); bookJpaRepository.save(book); System.out.println("保存数据成功:" + book); // 查 book = bookJpaRepository.findById(book.getId()).get(); System.out.println("新增后根据ID查询结果:" + book); // 修改 book.setName("Spring Boot 入门学习实践(修改版)"); bookJpaRepository.save(book); System.out.println("修改后根据ID查询结果:" + book); // 删除 bookJpaRepository.deleteById(book.getId()); }}注意在测试代码用需要对属性bookJpaRepository使用@Autowired 自动注入实现初始化,@Autowired 注解,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 ...

May 14, 2019 · 5 min · jiezi

小白教程一小时上手最流行的前端框架vue

前言 vue是现在很火的一个前端MVVM框架,它以数据驱动和组件化的思想构建,与angular和react并称前端三大框架。相比angular和react,vue更加轻巧、高性能、也很容易上手。大家也可以移步vue官网,看一下它的介绍和核心功能介绍。简单粗暴的理解就是:用vue开发的时候,就是操作数据,然后vue就会处理,以数据驱动去改变DOM。使用vue,我们可以集中精力于如何处理数据上,数据改变后,页面显示也会随之改变。相比jquery那种操作DOM元素的开发方式,能有效提高开发效率,个人觉得有接近两三倍的提升。 一、 安装 我们可以通过npm或者直接引入script标签两种方式来安装vue。这里为了方便说明,采用第二种方式,我们只需要在html页面引入标签即可。个人测试开发可以使用bootcdn的资源。 <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>二、核心思想 “数据绑定”是vue的核心思想,这里笔者举一个hello world例子来说明这种思想。 html代码 <div id="app"> <p>{{ message }}</p> <input v-model="message"></div>javascript代码 new Vue({ el: '#app', data: { message: 'Hello Vue!' }})页面效果 我们在html代码里面设置了一个id为“app”的div,然后在javascript里面实例化了一个属性el为“#app”的vue对象,表示这个vue对象用来处理该div的显示。 接着在vue对象的data属性里面设置了一个message字段,把这个字段和页面的p元素和input元素双向绑定起来。 这样只要message字段改变,p元素的内容就会改变。只要input的输入内容改变,message字段就会改变,从而导致p元素的内容改变。所以我们改变页面中输入框的值,p元素里面的内容也随之改变。 三、vue实例基本组成 new Vue({ el: '#app', data: { message: 'Hello Vue!', url: 'www.salasolo.com' }, methods:{ showMsg: function(){ alert(this.message) }, jumpUrl: function(){ location.href = this.url } },})可以看到,一个vue实例有三个基本的属性,el属性用来指定绑定的页面容器,data属性里面存放页面展示的数据,methods放置页面调用的一些方法。 四、数据绑定 使用下面的语法可以将页面元素的内容和vue实例的data属性里面的字段绑定起来。 1.文本 <span>消息: {{ message }}</span>2.原始html <span v-html="htmlCode"></span>3.列表 <span v-for="item in list">{{item}}</span>4.条件 ...

May 10, 2019 · 1 min · jiezi

Java-Web基础ActionService-Dao三层的功能划分

Action/Service/DAO简介:      Action是管理业务(Service)调度和管理跳转的。       Service是管理具体的功能的。       Action只负责管理,而Service负责实施。       DAO只完成增删改查,虽然可以1-n,n-n,1-1关联,模糊、动态、子查询都可以。但是无论多么复杂的查询,dao只是封装增删改查。至于增删查改如何去实现一个功能,dao是不管的。       总结这三者,通过例子来解释:       Action像是服务员,顾客点什么菜,菜上给几号桌,都是ta的职责;       Service是厨师,action送来的菜单上的菜全是ta做的;       Dao是厨房的小工,和原材料打交道的事情全是ta管。       相互关系是,小工(dao)的工作是要满足厨师(service)的要求,厨师要满足服务员(action)转达的客户(页面用户)的要求,服务员自然就是为客户服务喽。       现在最基本的分层方式,结合了SSH架构。Model层就是对应的数据库表的实体类。Dao层是使用了Hibernate连接数据库、操作数据库(增删改查)。Service层:引用对应的Dao数据库操作。Action层:引用对应的Service层,在这里结合Struts的配置文件,跳转到指定的页面,当然也能接受页面传递的请求数据,也可以做些计算处理。       以上的Hibernate, Struts,都需要注入到Spring的配置文件中,Spring把这些联系起来,成为一个整体。 三大框架Struts/Hibernate/Spring      简单地说:      Struts——控制用的;      Hibernate——操作数据库的;      Spring——解耦用的。      详细地说:       Struts在SSH框架中起控制的作用,其核心是Controller,即ActionServlet,而ActionServlet的核心就是Struts-config.xml,主要控制逻辑关系的处理。       Hibernate是数据持久化层,是一种新的对象、关系的映射工具,提供了从Java类到数据表的映射,也提供了数据查询和恢复等机制,大大减少数据访问的复杂度。把对数据库的直接操作,转换为对持久对象的操作。 ...

May 4, 2019 · 1 min · jiezi

javaweb开发之servlet初始

servlet概述Servlet是sun公司提供的一门用于开发动态web资源的技术。Sun公司在其API中提供了一个servlet接口,用户若想用发一个动态web资源(即开发一个Java程序向浏览器输出数据),需要完成以下2个步骤: (1)编写一个Java类,实现servlet接口。(2)把开发好的Java类部署到web服务器中。(3)按照一种约定俗成的称呼习惯,通常我们也把实现了servlet接口的java程序,称之为Servletservlet运行过程: (1)Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后: ①Web服务器首先检查是否已经装载并创建了该Servlet的实例对象。如果是,则直接执行第④步,否则,执行第②步。 ②装载并创建该Servlet的一个实例对象。 ③调用Servlet实例对象的init()方法。 ④创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。 ⑤WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。 IDEA创建第一个servlet程序选中自己的project,打开Project Structure界面》》选中Libraries》》点击"+"并选择java》》选择并选中自己的tomcat/lib/servlet-api.jar》》Apply》》OK列表项目

April 28, 2019 · 1 min · jiezi

微信公众号批量爬取系统完整实现(Java)

要想实现微信公众号文章的爬取,需要做两部分系统处理。一、公众号文章的自动化浏览处理一个是移动端的公众号文章自动浏览实现,逐个访问浏览公众号的历史文章,在浏览公众号文章的时候会请求公众号的文章链接地址,通过AnyProxy中间人代理解析工具,可以获取到永久的文章地址链接。在获取到真实的文章地址链接之后,就可以转发到自己搭建的服务器,逐个保存这些公众号文章的链接地址。详细实现步骤文章和Github源码资源见个人博文:微信公众号文章采集之:微信自动化二、服务端公众号文章内容爬取在通过移动端的自动化浏览获取到公众号文章的地址链接之后,就可以通过简单的爬虫,来爬取对应链接地址的公众号文章内容。在爬取到内容之后,逐个解析请求到的文章内容字段,把需要的字段匹配摘取出来,保存到数据库即可。详细实现步骤文章和Github源码资源见个人博文:微信公众号文章采集之:服务端数据采集

April 13, 2019 · 1 min · jiezi

3分钟干货之什么是并发容器的实现?

何为同步容器:可以简单地理解为通过synchronized来实现同步的容器,如果有多个线程调用同步容器的方法,它们将会串行执行。比如Vector,Hashtable,以及Collections.synchronizedSet,synchronizedList等方法返回的容器。 可以通过查看Vector,Hashtable等这些同步容器的实现代码,可以看到这些容器实现线程安全的方式就是将它们的状态封装起来,并在需要同步的方法上加上关键字synchronized。并发容器使用了与同步容器完全不同的加锁策略来提供更高的并发性和伸缩性,例如在ConcurrentHashMap中采用了一种粒度更细的加锁机制,可以称为分段锁,在这种锁机制下,允许任意数量的读线程并发地访问map,并且执行读操作的线程和写操作的线程也可以并发的访问map,同时允许一定数量的写操作线程并发地修改map,所以它可以在并发环境下实现更高的吞吐量。

April 13, 2019 · 1 min · jiezi

3分钟干货之Spring Boot注解

1、 @SpringBootApplication这是 Spring Boot 最最最核心的注解,用在 Spring Boot 主类上,标识这是一个 Spring Boot 应用,用来开启 Spring Boot 的各项能力。其实这个注解就是 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 这三个注解的组合,也可以用这三个注解来代替 @SpringBootApplication 注解。2、@EnableAutoConfiguration允许 Spring Boot 自动配置注解,开启这个注解之后,Spring Boot 就能根据当前类路径下的包或者类来配置 Spring Bean。如:当前类路径下有 Mybatis 这个 JAR 包,MybatisAutoConfiguration 注解就能根据相关参数来配置 Mybatis 的各个 Spring Bean。3、@Configuration这是 Spring 3.0 添加的一个注解,用来代替 applicationContext.xml 配置文件,所有这个配置文件里面能做到的事情都可以通过这个注解所在类来进行注册。

April 12, 2019 · 1 min · jiezi

JavaWeb 乱码问题终极解决方案!

经常有读者在公众号上问 JavaWeb 乱码的问题,昨天又有一个小伙伴问及此事,其实这个问题很简单,但是想要说清楚却并不容易,因为每个人乱码的原因都不一样,给每位小伙伴都把乱码的原因讲一遍也挺费时间的,因此,松哥今天决定写一篇文章,和大伙好好捋捋 JavaWeb 中的乱码问题。 对于一些老司机而言,其实并不太容易遇到乱码问题,但是对于一些新手来说,乱码几乎是家常便饭,而且每当乱码时,网上搜了一大堆解决方案,发现自己的问题还是没能解决,其实这就是平时研究代码不求甚解导致的,乱码问题,也要去分析,然后才能对症下药,才能药到病除。整体思路首先出现乱码之后,要先去确认乱码的地方,当一个网页上出现乱码,有可能是浏览器显示问题,也有可能是 Java 编码问题,也有可能数据库中的数据本身就是乱码的,所以我们要做的第一件事就是确认乱码发生的位置,缩小 bug 范围,通过打印日志或者 debug 首先去确认乱码发生的位置,然后再去进一步解决,一般来说,乱码的原因大致上可以分为两类:请求乱码响应乱码请求乱码,可能是因为参数放在 URL 地址中乱码,也有可能是参数放在请求体中乱码,不同传参方案也对应了不同的乱码解决方案。如果是响应乱码,那么原因就会比较多了,一般来说,有如下几种可能的原因:数据库本身乱码数据在 Java 代码中乱码数据在浏览器显示的时候乱码数据在从 Java 应用传到数据库的过程中乱码对于不同的乱码原因,会有不同的解决方案,对症下药,才能药到病除,所以当出现乱码时,大家要做的第一件事就是分析乱码发生的原因,找到原因了,才能找到解决方案。基本原则发生乱码是因为各自编码不同导致的,所以,大家首先要有一个良好的开发习惯,项目编码,文件编码都要统一起来,松哥有个同事就因为 Freemarker 乱码,找了半天没找到原因,后来在松哥建议下修改了项目编码,乱码问题才解决了,一般来说,公司制度稍微成熟一些,都会对项目编码,文件编码有硬性规定的。在Eclipse 中,设置项目编码方式如下(工程的编码要提前设置,如果项目已经开发一半再去设置,已有的中文就会乱码): Window->Preferences->General 然后对于 JSP 文件也需要提前设置好编码方式,如下: 这是在 Eclipse 中设置文件编码,如果是在 IntelliJ IDEA中,则不需要设置JSP文件编码,因为默认就是 UTF-8,只需要提前设置下工程编码即可: 除了开发工具的编码,数据库的编码也要统一,一般来说,主要是设置一下数据库的编码和数据表的编码,如下: 设置数据库编码:CREATE DATABASE vhr DEFAULT CHARACTER SET utf8;设置数据表编码:DROP TABLE IF EXISTS adjustsalary;CREATE TABLE adjustsalary ( id int(11) NOT NULL AUTO_INCREMENT, eid int(11) DEFAULT NULL, PRIMARY KEY (id),) ENGINE=InnoDB DEFAULT CHARSET=utf8;这些是准备工作,这些工作做好了,还是有可能会遇到乱码问题,接下来我们就具体问题具体分析。请求乱码请求乱码,就是说数据在浏览器中显示是正常的,但是传到 Java 后端之后,就乱码了,这种乱码一般来说,分为两种:参数放在 URL 地址中导致的乱码参数放在请求体中导致的乱码两种乱码原因,对应了两种不同的解决方案。分别来看。URL 地址中的参数乱码这种乱码主要发生在 GET 请求中,因为在 GET 请求中我们一般通过 URL 来传递参数,这个问题可以在代码中解决,但是太过于麻烦,因此一般我们直接在Tomcat配置中解决,修改 Tomcat的conf/server.xml 文件,修改 URL 编码格式,如下: 这样就可以搞定 URL 地址中的参数乱码。请求体中的参数乱码请求体中的参数乱码,我们可以在解析参数之前通过设置 HttpServletRequest 的编码来解决,如下:request.setCharacterEncoding(“UTF-8”);但是一样也太过于麻烦,所以如果是普通的 Servlet/JSP 项目,我们就可以直接定义一个过滤器来处理,如下:public class EncodingFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding(“UTF-8”); chain.doFilter(request, response); }}过滤器配置: <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.sang.filter.EncodingFilter</filter-class> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/</url-pattern> </filter-mapping>在工程编码和JSP/HTML编码都没问题的情况下,请求乱码基本上就是这两种情况。响应乱码如果在浏览器上加载页面看到了乱码,大家首先要确认在从服务端往浏览器写数据的前一刻,这个数据还没有乱码(即数据库中查询出来的数据是OK的,没有发生乱码的问题),那么对于这种乱码,我们只需要设置响应数据的 ContentType 就可以了,如下:response.setContentType(“text/html;charset=UTF-8”);如果从数据库中查询出来的数据就是乱码的,那么就需要去确认数据库中的编码是否 OK 。框架处理前面提到的方案,都是在 Servlet/JSP 项目中我们可以采用的方案,在 SSM 框架中当然也可以使用,但是,SpringMVC 框架本身也提供了一个过滤器,我们可以借用这个过滤器更加高效的解决响应乱码问题,如下:<filter> <filter-name>encoding</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceRequestEncoding</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>forceResponseEncoding</param-name> <param-value>true</param-value> </init-param></filter><filter-mapping> <filter-name>encoding</filter-name> <url-pattern>/</url-pattern></filter-mapping>当然,上面这段配置并不能代替 Tomcat 中 conf/server.xml 中的编码配置,如果是在 Spring Boot 中,配置可以更加简单,只需要在 application.properties 中添加如下配置即可:server.tomcat.uri-encoding=UTF-8spring.http.encoding.force-request=truespring.http.encoding.force-response=true其他乱码其他乱码主要是指使用一些第三方框架导致的乱码,例如使用 Alibaba 的 fastjson,开发者就需要在配置 HttpMessageConverter 时指定编码格式,否则就有可能出现乱码,这种第三方框架的乱码松哥没法穷举,大伙在使用时需要注意看官方文档,fastjson 的 HttpMessageConverter 配置如下:@BeanFastJsonHttpMessageConverter fastJsonHttpMessageConverter() { FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter(); FastJsonConfig config = new FastJsonConfig(); config.setCharset(Charset.forName(“UTF-8”)); converter.setFastJsonConfig(config); converter.setDefaultCharset(Charset.forName(“UTF-8”)); return converter;}一个隐蔽的乱码除了前面介绍的这几种乱码之外,还有一个比较隐蔽的乱码,容易被很多初学者忽略的地方,就是数据在从 Java 应用传递到 MySQL 的过程中,发生了乱码,这种问题一般在 Windows 上不易发生,如果数据库装在 Linux 上,则这个问题就很容易发生,数据在代码中命名没有乱码,存到 MySQL 上就乱码了,但是如果直接使用 Navicat 等工具往 MySQL 上存储数据,又不会乱码,或者 MySQL 中数据没有乱码,但是用 Java 查询出来就乱码了,这种都是数据在 应用 和 数据库 之间传递时发生了乱码,解决方式很简单,在数据库连接地址上指定编码即可,如下:db.url=jdbc:mysql:///yuetong?useUnicode=true&characterEncoding=UTF-8大致就这些,还有一些非常偶尔的情况可能会用到 @RequestMapping 注解中的 produces 属性,在这里指定数据类型即可。 好了,差不多就这些,下次有人问你为啥我的又乱码了,直接把这篇文章甩给他。大伙有什么解决乱码的独门密器也可以一起来讨论。 关注松哥,公众号内回复 牧码小子,获取松哥私藏多年的Java干货哦! ...

April 9, 2019 · 1 min · jiezi

CentOS7-MySQL排坑历程

PS:如果觉得文章有什么地方写错了,哪里写得不好,或者有什么建议,欢迎指点。一、报错及起因今天在 CentOS7 安装了 mysql5.7,然后为了测试数据库环境是否配置成功,便写了个基于 mybatis+Spring 的 java web 程序连接操作 mysql 数据库,于是就一些发生了令人感到很烦的报错和故事:当涉及到关于数据库的操作如查询、插入等操作时,首先浏览器访问会很慢,进度条一直旋转,然后页面会报 500 错误: org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exception 。然而我在 CentOS7 服务端和 Windows 本地的 Navicat 连接 mysql 都没问题。。。二、排错历程1.检查 sql 语句看着这似乎是 mybatis 引起的错误,所有先检查 mapper 的 xml 中 sql 语句是否有错误,例如参数的格式转化、resultType 为 JavaBean、resultMap 需要定义、JavaBean 和 dao 的引入等。检查中并没有发现什么问题,而且错误仍然存在。2. MySql Host is blocked because of many connection errors; unblock with ‘mysqladmin flush-hosts’ 报错原因分析:查看 tomcat 的日志文件,发现在报错开始部分出现了这个错误。经过查询,发现这个错误的原因是:同一个 ip 在短时间内产生太多(超过 mysql 数据库 max_connection_errors 的最大值)中断的数据库连接而导致的阻塞。解决方法:进入 CentOS7 服务器:方法一:提高允许的max_connection_errors数量(治标不治本):进入Mysql数据库查看max_connection_errors: show variables like ‘%max_connection_errors%’;修改max_connection_errors的数量为1000: set global max_connect_errors = 1000;查看是否修改成功:show variables like ‘%max_connection_errors%’;方法二:使用 mysqladmin flush-hosts 命令清理一下 hosts 文件:查找 mysqladmin 的路径:whereis mysqladmin执行命令,如:/usr/local/mysql5.5.35/bin/mysqladmin -uroot -pyourpwd flush-hosts注: 方法二清理 hosts 文件,也可以直接进入 mysql 数据库执行命令:flush hosts;3.com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure 异常解决了 hosts 的问题后,在 tomcat 的日志文件,发现在报错开始部分又出现了这个错误: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failureThe last packet successfully received from the server was 32 milliseconds ago. The last packet sent successfully to the server was 32 milliseconds ago. at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at com.mysql.jdbc.Util.handleNewInstance(Util.java:425) at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:990) ……原因分析:表示程序与 MySQL 通讯失败了,即连接失败了。The last packet successfully received from the server was 32 milliseconds ago 表示 mysql 重连,连接丢失。此为数据库连接空闲回收问题,程序打开数据库连接后,等待做数据库操作时,发现连接被 MySQL 关闭掉了。一开始认为是连接等待超时问题。在 mysql 数据库的配置中,其连接的等待时间(wait_timeout)缺省值为 8 小时。在 mysql 中可以查看:mysql﹥ mysql﹥ show global variables like ‘wait_timeout’; +—————+———+ | Variable_name | Value | +—————+———+ | wait_timeout | 28800 | +—————+———+ 1 row in set (0.00 sec) 28800 seconds,也就是8小时。如果在 wait_timeout 秒期间内,数据库连接(java.sql.Connection)一直处于等待状态,mysql 就将该连接关闭。这时,Java 应用的连接池仍然合法地持有该连接的引用。当用该连接来进行数据库操作时,就碰到上述错误。MySQL 连接一次连接需求会经过 6 次「握手」方可成功,任何一次「握手」失败都可能导致连接失败。前三次握手可以简单理解为 TCP 建立连接所必须的三次握手,MySQL 无法控制,更多的受制于 tcp 协议的不同实现,后面三次握手过程超时与 connect_timeout 有关。解决方法:改变数据库参数是最简单的处理方式(修改 /etc/my.cnf 中的 wait_timeout 值),但是需要重启数据库,影响较大。在不修改数据库参数的前提下,可以做已下处理:如果使用的是 jdbc ,在 jdbc url 上添加 autoReconnect=true ,如:dataSource.url=jdbc:mysql://132.231.xx.xxx:3306/shop?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true如果是在Spring中使用DBCP连接池,在定义datasource增加属性 validationQuery 和 testOnBorrow ,如:<bean id=“vrsRankDataSource” class=“org.apache.commons.dbcp.BasicDataSource” destroy-method=“close”> <property name=“driverClassName” value="${dataSource.driver}"/> <property name=“url” value="${dataSource.url}"/> <property name=“username” value="${dataSource.user}"/> <property name=“password” value="${dataSource.password}"/> <property name=“validationQuery” value=“SELECT 1”/> <property name=“testOnBorrow” value=“true”/></bean>如果是在Spring中使用c3p0连接池,则在定义datasource的时候,添加属性 testConnectionOnCheckin 和 testConnectionOnCheckout ,如:<bean name=“cacheCloudDB” class=“com.mchange.v2.c3p0.ComboPooledDataSource”> <property name=“driverClass” value="${dataSource.driver}"/> <property name=“jdbcUrl” value="${dataSource.url}"/> <property name=“user” value="${dataSource.user}"/> <property name=“password” value="${dataSource.password}"/> <property name=“initialPoolSize” value=“10”/> <property name=“maxPoolSize” value=“10”/> <property name=“testConnectionOnCheckin” value=“false”/> <property name=“testConnectionOnCheckout” value=“true”/> <property name=“preferredTestQuery” value=“SELECT 1”/></bean>4. 远程连接 Mysql 太慢问题尝试解决了一下上面的连接超时问题,但是发现并没有什么用,还是会出现上面的问题。于是便怀疑是不是远程连接 Mysql 太慢导致了连接超时?因为我在 CentOS7 服务端和 Windows 本地的 Navicat 连接 mysql 都没问题。在网上查询了下,发现在 mysql 的配置文件 /etc/my.cnf 中增加如下配置参数:# 注意该配置是加在[mysqld]下面[mysqld]skip-name-resolve然后需要重启 mysql 服务。因为根据说明,如果 mysql 主机查询和解析 DNS 会导致缓慢或是有很多客户端主机时会导致连接很慢。同时,请注意在增加该配置参数后,mysql的授权表中的host字段就不能够使用域名而只能够使用ip地址了,因为这是禁止了域名解析的结果。5. 终极解决:Could not create connection to database server. Attempted reconnect 3 times. Giving up 报错原因分析:经过上面的配置后,重新测试程序,就又出现了这个错误。经过查询,发现这是由于 SSL 引起的错误。因为我是在 CentOS7 上新装的 MySQL5.7,默认是没有配置 MySQL SSL 的。而我以前一直使用的 Ubuntu16.04 上的 mysql 数据库,它默认是配置了 MySQL SSL 的,所以我习惯在连接数据库的 jdbc 的 url 里面加上 &useSSL=false ,即使用 SSL 加密。导致我在连接当前 mysql 的时候,一直连接不上。查看 tomcat 日志的报错末尾,会有如下报错:Caused by: javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Path does not chain with any of the trust anchors at sun.security.ssl.Alerts.getSSLException(Alerts.java:192) at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1946) at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:316) at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:310) at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1639) at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:223) at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1037) at sun.security.ssl.Handshaker.process_record(Handshaker.java:965) at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1064) at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367) at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1395) at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1379) at com.mysql.jdbc.ExportControlled.transformSocketToSSLSocket(ExportControlled.java:186) … 24 moreCaused by: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Path does not chain with any of the trust anchors at com.mysql.jdbc.ExportControlled$X509TrustManagerWrapper.checkServerTrusted(ExportControlled.java:302) at sun.security.ssl.AbstractTrustManagerWrapper.checkServerTrusted(SSLContextImpl.java:1091) at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1621) … 32 moreCaused by: java.security.cert.CertPathValidatorException: Path does not chain with any of the trust anchors at sun.security.provider.certpath.PKIXCertPathValidator.validate(PKIXCertPathValidator.java:154) at sun.security.provider.certpath.PKIXCertPathValidator.engineValidate(PKIXCertPathValidator.java:80) at java.security.cert.CertPathValidator.validate(CertPathValidator.java:292) at com.mysql.jdbc.ExportControlled$X509TrustManagerWrapper.checkServerTrusted(ExportControlled.java:295) … 34 more遂恍然大悟。。。解决方法:在配置 JDBC URL 时使用 &useSSL=false ,即不使用 SSL 加密,配置后连接正常:dataSource.url = jdbc:mysql://132.231.xx.xxx:3306/shop?useUnicode=true&characterEncoding=utf8&useSSL=false&autoReconnect=true如果安全性较高的数据建议在 MySQL 端还是把 SSL 配上。三、总结由于 MySQL 的 SSL 问题引起了一连串的问题。由于 SSL 加密的问题,导致程序向 mysql 的连接超时,然后一直向 mysql 发送连接,导致了同一个 ip 在短时间内产生太多中断的数据库连接而导致的阻塞。以后看报错日志,除了看报错的最开始部分,也要看看报错的结尾部分,弄不好会有一些其他的发现的。欢迎您的点赞、收藏和评论!(完) ...

April 8, 2019 · 3 min · jiezi

使用maven创建简单的多模块 Spring Web项目

第一次写技术文章,主要内容是使用maven创建一个简单的SpringMVC WEB 项目,如有操作或理解错误请务必指出,当谦虚学习。做这一次的工作主要是因为想加强一下自己对Spring Web 项目的理解,因为平时都是直接写业务代码,我觉得还是有必要自己了解一下创建项目的过程。后续会基于这个项目写更多的SpringWeb开发过程,希望能帮助到有需要的人。总的来说是一个相当精简的多模块springWeb项目搭建过程,让我们进入正题吧我们知道单体应用,就写在一个project里面的话,业务一旦庞大起来非常难以管理。把各个模块单独抽出来可以方便的对jar包进行版本管理(尽管我还没经历过这个),维护项目,团队开发也会方便许多。基本思想其实就是一个java web项目引用别的模块Jar包,最终web项目被打成war包发布。而所有的war包项目,jar包项目都是在同一个父模块下管理的(它们都是Maven项目)(如果你有IDE,装好插件就用IDE创建吧,我个人不喜欢手动命令行创建)1. 创建父项目下图中:框起来打勾这个会让你跳过项目模式选择,勾选对于创建项目没有什么影响,以后也许会转一下Maven这方面的文章POM包才能做父项目,谨记!!!!! 2. 子项目结构和创建以下是我的结构分层,你也可以按你的想法来,最终目的是要方便自己开发。test_parent (父项目) |—-test_web (web项目) |—-test_service (业务内容) |—-test_framework (工具,框架封装、配置) |—-test_dao (数据持久层,DO也放这) |—-test_controller (处理映射) 创建子项目直接右键父项目然后新建maven module ,也就是子模块我们先创建web模块,这里你可以勾选第一条然后创建简单项目,如果没有勾选,那么你要在下一步里选择 maven-achetype-webapp,这里以简单项目为例子Group Id 和 version 都是继承父项目的一定要选择war包打包,不然要重新把他构建成web项目。如果你没选war包:https://www.cnblogs.com/leonk…最后点finish完成点击生成Web描述文件 (web.xml)这样就完成了Web模块的创建,剩下的其他项目都是同样的步骤创建,都是选择jar包,参考下图:3. 配置各模块的pom.xmlpom.xml记录所需的jar包,模块联系,包信息,打包参数等等信息,在多模块里我们要理清关系,不要重复引用首先毫无疑问的是让parent加载spring的jar包是最方便开发的,因为默认所有模块都继承parent,所以子模块引用spring内容也方便。其次配置文件我们统一放在framework中进行管理。那么先来写入web.xml配置吧<?xml version=“1.0” encoding=“UTF-8”?><web-app xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xmlns=“http://java.sun.com/xml/ns/javaee" xsi:schemaLocation=“http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id=“WebApp_ID” version=“3.0”> <display-name>test_web</display-name> <context-param> <!– 配置地址 –> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring-.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <!– spring-mvc.xml 配置地址 –> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/</url-pattern> </filter-mapping></web-app>这里可以看到我们写了classpath,原因是它能搜索到项目目录以外的Jar包下文件相关:http://www.cnblogs.com/wlgqo/…web.xml详解:https://blog.csdn.net/qq_3557…web.xml是对WEB项目来说是必须的配置文件,写好了spring配置文件的位置以后,就来新建2个spring配置文件,新建的配置放在test_framework模块里,路径如下图 spring-context.xml spring-mvc.xml一个是spring-context.xml 也叫applicationContext.xml,是webApp的上下文配置,也可以理解为配置dao、service 通用bean的地方,但我们这里使用的是注解扫描方式配置bean,所以就简单许多,即便有工具存在,写改xml真的很讨厌啊!<?xml version=“1.0” encoding=“UTF-8”?><beans xmlns=“http://www.springframework.org/schema/beans" xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc=“http://www.springframework.org/schema/mvc" xmlns:context=“http://www.springframework.org/schema/context" xmlns:tx=“http://www.springframework.org/schema/tx" xmlns:util=“http://www.springframework.org/schema/util" xmlns:aop=“http://www.springframework.org/schema/aop" xsi:schemaLocation=“http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"> <!– 注解注册 –> <!– <context:annotation-config /> –> <context:component-scan base-package=“com.test” > <context:exclude-filter type=“annotation” expression=“org.springframework.stereotype.Controller” /> <context:exclude-filter type=“annotation” expression=“org.springframework.web.bind.annotation.RestController” /> </context:component-scan> </beans>这里要去掉对controller的扫描,applicationContext初始化的上下文加载的Bean是对于整个应用程序共享的,不管是使用什么表现层技术,一般如DAO层、Service层Bean; DispatcherServlet (下一个要配置的东西) 初始化的上下文加载的Bean是只对Spring Web MVC有效的Bean,如Controller、HandlerMapping、HandlerAdapter等等,该初始化上下文应该只加载Web相关组件。context:component-scan 的 base-package 值用来决定我们需要扫描的包的基础名,具体配置相关可以看:https://www.cnblogs.com/exe19…而context:annotation-config/ 呢?其实也是spring为了方便我们开发者给我们提供的一个自动识别注解的配置,相关细节如下:解释说明:https://www.cnblogs.com/_popc…两条配置的区别和诠释:https://www.cnblogs.com/leiOO…下面是第二个配置文件 spring-mvc.xml<?xml version=“1.0” encoding=“UTF-8” standalone=“no”?><beans xmlns=“http://www.springframework.org/schema/beans" xmlns:context=“http://www.springframework.org/schema/context" xmlns:aop=“http://www.springframework.org/schema/aop" xmlns:mvc=“http://www.springframework.org/schema/mvc" xmlns:p=“http://www.springframework.org/schema/p" xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=“http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"> <!– 自动扫描的包名 –> <context:component-scan base-package=“com.test.*.controller” > <context:include-filter type=“annotation” expression=“org.springframework.stereotype.Controller” /> </context:component-scan> <!– 默认的注解映射的支持 –> <mvc:annotation-driven> <mvc:message-converters> <bean class=“org.springframework.http.converter.StringHttpMessageConverter”> <constructor-arg value=“UTF-8” /> </bean> <bean class=“org.springframework.http.converter.ResourceHttpMessageConverter” /> </mvc:message-converters> </mvc:annotation-driven></beans>包扫描没什么好说的,这里还强调include了注解controller。mvc:annotation-driven 是spring默认的注解驱动,这个配置项一口气把一堆东西都给我们加进来了,但主要还是针对controller和处理请求的,具体的在下面文章中,因为加的内容有点多,所以这个留到后面研究,稍微理解作用就好:相关文章 : https://blog.csdn.net/vicroad...mvc:message-converters 顾名思义,就是用于处理请求消息的,request content-header 会记录请求的内容类型,根据这些类型,spring会把内容转化成服务器操作的对象,这里的字符串转化是为了避免乱码,我们指定了编码格式。相关文章:https://www.jianshu.com/p/2f6…以上,我们就已经把最简约的配置写好了。接下来我们随便写一个controller试试4.写个Controller吧根据之前写好的controller的扫描包名,去我们test_controller模块里创建一个controllerpackage com.test.hello.controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/test”)public class HelloController{ @RequestMapping("/get”) public String helloGet(@RequestParam String str) throws Exception { return str; }}很简单的一段返回请求字符串的代码,现在一切就绪可以启动服务器了,配置好Tomcat就可以启动了,右键test_web –> run as –> run on server 选择创建好的tomcat容器,就可以启动了。接下来,访问: localhost:8080/test/test/get?str=helloWorld 如果你使用eclipse启动且没有正常启动,特别是出现严重错误时,请先检查web.xml配置命名有没有问题,然后再检查test_web项目的assembly,这个会影响项目的发布文件,下图所示,右键项目点properties,没有test_framework的话就加入framework项目。网站无响应,检查一下tomcat的端口,默认是8080。404检查代码的映射路径。5.结束语第一次写技术文章,记录一下自己的学习过程,日后会以当前项目作为基础,继续记录下自己遇到的问题和分享的知识,希望能帮助到一部分新手,此外,本篇文章中若有错误,欢迎指出,我会不断更新文章误点,不吝赐教。 ...

April 4, 2019 · 2 min · jiezi

详解服务器端的项目框架

导读我一直相信这句话,他山之石可以攻玉。在自己能力不够时,多学习别人的东西。这样,对自己只有好处,没有坏处。因而,经过将近一年的工作,研读了公司所使用的框架。我本想往架构师的方向靠近,但,自己的能力可能还不够,因而,不断地给自己充电。公司的项目是前后端分离的,前端使用HTML5,css3、jquery、vue.js、bootstrap等,以SVN做代码托管。后端采用maven构建项目,以git lab做代码托管。肯定有人会问,这两个都是版本库,但它们有什么区别?如果想要了解的话,可以参考该文档:Svn与Git的区别。现在几乎所有的公司都采用maven构建项目,很少会采用导入jar包的方式依赖第三方框架。maven介绍maven构建的项目有很多好处,首先其可以统一管理jar包,也就是说,我们在项目中不用手动导入jar包,我们只要添加依赖即可,如代码所示:<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${jdbc.version}</version></dependency>添加依赖之后,maven就会导入该jar包,导入jar包的顺序为:首先查找本地仓库,如果本地仓库没有,进入下面步骤。maven settings profile中的repository;pom.xml中profile中定义的repository。profile激活配置文件,比如正式环境的配置文件prd.properties和开发环境的Dev.properties文件。这也是打包的依据,是打开发环境的包,还是打正式环境的包,如图所示:pom.xml中的repositorys(定义多个repository,按定义顺序找);如果经过上面的步骤,没有找到相应的jar包,最后到我们的镜像(mirror)中查找。如果mirror中存在该jar包,从mirror中拷贝下来,存储到本地仓库中,进入到最初一步。如果mirror中也没有,maven就会报相应的错误。maven报出相应的错误时,也许,是我们本地没有该jar包,远程仓库也没有该jar包,我们可以参考这篇博客:在maven的pom.xml中添加本地jar包。它会教你如何创建本地仓库,并导入创建好的本地仓库。【备注】这篇博客以架构师的角度来讲解maven,所以,不具体讲解maven各个标签的含义。如果你想了解pom的各个标签的含义,可以参考这篇文档:pom.xml详解,或者,参考这篇教程:maven教程|菜鸟教程上面解说只是配置jar文件,但是,maven的功能远不止这些,从我们创建maven项目时,就已经进入到maven的开发环境中。maven项目有人和我说过,学一项知识,什么方式最快?那就是通过做项目。你在做项目的过程中,肯定会遇到很多问题,而你不得不去查找资料,从根源上认识到这个问题。因而,我以公司所做的某个项目为例,来讲解maven的依赖、继承、聚合等关系。我们所说的maven中的关系,其实就是pom的关系,即项目对象模型(Project Object Model)的简称。maven聚合首先,我们在创建cloudCodeSale项目时,就已经创建了父pom文件,如图所示:上图就是我们的父pom文件,你很清楚的看到gav坐标。同时,你从这张图上,也能得到其他信息,其打包方式是 pom,其还关联其他module,module名称和左边的列表名称一样。这就是我们所说的maven的聚合。父类同时聚合其子类。聚合的条件有两个:修改被聚合项目的pom.xml中的packaging元素的值为pom在被聚合项目的pom.xml中的modules元素下指定它的子模块项目既然所有的子模块的pom都继承父pom,为什么父pom要聚合子模块的pom文件?这个问题很好。因为对于聚合而言,当我们在被聚合的项目上使用Maven命令时,实际上这些命令都会在它的子模块项目上使用。这就是Maven中聚合的一个非常重要的作用。在实际开发的过程中,我们只需要打包(mvn install)父pom文件。我们在父pom上使用mvn celan、mvn compile和mvn package,其会自动对子模块:platform-core、platform-core-controller、portal/member-portal、portal/platform-portal、platform-cms、platform-cms-controller、platform-custom执行mvn celan、mvn compile和mvn package。没必要一个一个地打包,这样极容易出现错误,如图所示:maven继承机制如果很多模块都需要相同的jar包,我们可以单独写在一个pom中,其他模块使用该模块的公共部分,这就是我们常说的父类。不论是java语言,还是c++语言,或者现在的pom,其都体现这个思想。我们在上文也提到了子模块,现在模块platform-core讲解。继承父类的结构一般是这样的:<parent> <groupId>parent.groupId</groupId> <artifactId>parent.artifactId</artifactId> <version>parent.version</version> <relativePath>../pom.xml</relativePath> </parent> relativePath是父pom.xml文件相对于子pom.xml文件的位置,针对被继承的父pom与继承pom的目录结构是不是父子关系。如果是父子关系,则不用该标签;如果不是,那么就用该标签。因为在当前项目中,platform-core模块的目录的pom在父目录的pom中,其和父pom的目录结构是父子关系,因而可以省略relativePath该标签,如图所示:parent标签中的groupId、artifactId、version要和父pom中的标签中一致。maven的依赖关系正如我们所知道的,maven构建项目,不仅是因为其能够管理jar包,其还使模块化开发更简单了。因而,我们在开发的过程中,一般都分模块化开发。模块与模块之间的信息是不通的,但是,我们想要模块间的之间能够通信,这时,我们就想到了java中的依赖关系。比如,我们有一个模块,这个模块封装好了微信支付、支付宝支付、处理json格式、操作文件、ResultUtil、lambdaUtil、commonUtil等工具类,还有附件、头像、用户等实体类。这些工具类在任何项目中不会轻易改变,如果为了满足某些需求而不得不得修改,需要得到架构师的同意。因而,我们可以把它拿出来,单独定义为一个模块,也就是platform-core模块。但是,我们还有一个模块,在这个模块中,根据不同的项目,其定义不同的实体类、dao层类、事务层类、枚举类、接收前端传来参数封装成的query类、从数据库中取出的数据封装成的data类,到事务层可能会调用模块plateform-core中的方法,比如调用第三方系统接口的HTTPClientUtil.doPost(String url, Map<String, String> param),判断处理lambda表达式的LambdaUtil.ifNotBlankThen(String value, Consumer<String> function) ,等等。这个自定义类的模块,我们可定义为plateform-custom。plateform-custom需要用到platform-core中的方法,因而,这时,我们就需要考虑依赖关系,怎么添加对platform-core的依赖呢?如代码所示:<dependency> <groupId>com.zfounder.platform</groupId> <artifactId>platform-core</artifactId></dependency>我们这边是前后台分离的,后台用来录入数据,前台用来展示数据,因而,我们有一个portal目录,该目录下有两个子模块。一个是member-portal模块,一个是platform-portal模块,前者接收前台的接口参数,后者接收后台的接口参数。但不论哪个模块,都需要依赖plateform-custom中的事务层方法,同时,我们传的参数,可能信息分发的platform-cms-controller中的接口,也可能是核心接口platform-core-controller中的接口。因而,我们这里以member-portal模块来举例,依赖其他模块的代码如下:<dependencies> <dependency> <groupId>com.zfounder.platform</groupId> <artifactId>platform-core-controller</artifactId> </dependency> <dependency> <groupId>com.zfounder.platform</groupId> <artifactId>platform-cms-controller</artifactId> </dependency> <dependency> <groupId>com.zfounder.platform</groupId> <artifactId>platform-custom</artifactId> <version>1.0-SNAPSHOT</version> </dependency></dependencies>这些模块你会在上面的图片找得到。同时,我们来看member-portal的pom文件继承的父pom是怎么写的:补充上面的继承关系。这里面用到了<relativePath>../../pom.xml</relativePath>你会奇怪的是,为什么这里面用到了呢?其和父pom不是父子关系,而是孙子关系。这里使用到了两次点点,这是什么意思呢? ..表示上级目录。举个例子说明:比如,在我的服务器上的www目录中,有三个文件,分别是rsa_private_key.pem, rsa_private_key_pkcs8.pem, rsa_public_key.pem,还有一个testDir目录,testDir目录中还有目录testDir,现在我们通过cd ../testDir/testDir进入到子目录中,现在,我们想返回到www的根目录中,并查看rsa_public_key.pem文件的内容,因而,我们可以用cat ../../rsa_public_key.pem命令,其首先返回两级目录,然后找到rsa_public_key.pem文件并打开该文件。“被继承的父pom与继承pom的目录结构是不是父子关系”也不是绝对的,主要是选择继承者的pom中的子目录和父目录之间的关系,其中间隔了几层目录。maven激活文件激活文件在上文也提到了,我们为什么需要激活文件?如下面的两个配置文件,一个是测试环境的配置文件,名为platform-dev.properties,一个是正式环境的配置文件,名为platform-prd.properties。两个配置文件中都存在与数据库的连接,但是呢,数据库的ip地址是不一样的。如一下的代码所示:正式服的platform-prd.properties配置文件jdbc.url=jdbc:mysql://localhost/prd_databasejdbc.username=prd_usernamejdbc.password=prd_passwordjdbc.validationQuery=select 1 from dualjdbc.removeAbandonedTimeout=180jdbc.initialSize=10jdbc.minIdle=30jdbc.maxActive=100jdbc.maxWait=30000。。。测试服的platform-dev.properties配置文件jdbc.url=jdbc:mysql://intranet_ip/dev_databasejdbc.username=dev_usernamejdbc.password=dev_passwordjdbc.validationQuery=select 1 from dualjdbc.removeAbandonedTimeout=180jdbc.initialSize=10jdbc.minIdle=30jdbc.maxActive=100jdbc.maxWait=30000。。。我们的在配置文件中配置好了数据项,但是呢,我们怎么切换不同的配置文件呢?换句话说,我们怎么想要打正式服的包放到正式服上,怎么选择platform-prd.properties的配置文件呢?反之,怎么选择platform-dev.properties配置文件?这时,我们就用到了maven当中的profile标签,如下代码所示: <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>这些配置文件时写在member-portal、platform-portal、plateform-core和plateform-cms、plateform-customer模块的pom中的。但是,plateform-core和plateform-cms的配置中的filter和上面连个略有差异,其filter是这样的 <filter>../platform-dev.properties</filter>和 <filter>../platform-prd.properties</filter>,这就涉及到目录点的问题。maven依赖第三方包maven项目除了依赖本项目的,其还会依赖第三方包,比如自动生成set和get方法的lombok包,处理json格式的阿里巴巴下的fastjson包等等,我们也可以使用这种格式的依赖:<!–mysql jdbc驱动包 开始–><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${jdbc.version}</version></dependency><!–mysql jdbc驱动包 结束–>开发常用的jar包lombok<!– lombok驱动包 开始–><dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.10</version></dependency><!– lombok驱动包 开始–>我们在没有使用lombok之前,经常手动创建javabean的set个get方法,使用这个框架之后,其以注解的方式,可以自动生成set和get方法。同时,其强大的功能远不止这些,也可以生成无参构造器、全参构造器,指定参数构造器、重写toString方法、重写Hashcode、equals方法等等。如代码所示:/** * Created By zby on 17:37 2019/1/30 /@AllArgsConstructor@NoArgsConstructor@Data@ToString@EqualsAndHashCodepublic class Address { /* * 收货人 / private String consignee; /* * 手机号码 / private String phone; /* * 所在地区 / private String area; /* * 详细地址 / private String detail; /* * 标签 / private AddressTagEnum addressTag;}想要更深层次的了解这个框架,可以参考这个博客:Lombok使用详解fastjson<!– fastjson驱动包 开始–><dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.28</version></dependency><!– fastjson驱动包 结束–>fastjson是阿里巴巴开源的框架,其用来处理服务器端的json格式的数据。比如,我们需要将服务端的对象以json(JavaScript object Notation,js对象标记)格式传输到前端,但是,如果自己手动创建的话,势必会非常的麻烦,于是,我们借助这个框架,帮助我们生成json格式的对象。同时,如果我们要调用第三方接口,比如调用连连绑定银行卡的接口,其返回给我们的也是json对象的数据。但是,我们需要将其转化为我们定义的对象,调用其save方法,保存到数据库中,对账所用。对于,将对象转化为json格式的对象,如代码所示:@Testpublic void test() {// 地址 Address address = new Address(); address.setAddressTag(AddressTagEnum.ADDRESS_TAG_COMPANY); address.setArea(“杭州市….”); address.setConsignee(“zby”);// 用户 User user = new User(); user.setHobby(HobbyEnum.HOBBY_DANCING); user.setGender(“男”); user.setUserName(“蒋三”);// 订单 OrderSnapshot orderSnapshot = new OrderSnapshot(); orderSnapshot.setAddress(address); orderSnapshot.setId(1L); orderSnapshot.setName(“复读机”); orderSnapshot.setOrderNo(Long.valueOf(System.currentTimeMillis()).toString() + “1L”); orderSnapshot.setUser(user); System.out.println(JSON.toJSON(orderSnapshot));}其输出结果如图所示:但是,类似于解析json格式的数据,不只有fastjson框,还有org.json框架、Jackson框架。但经过有人验证呢,还是fastjson的效率更高一些。可以参考这篇博客:Gson、FastJson、org.JSON到底哪一个效率更高,速度更快org.json也是通过maven配置的,如代码所示:<!– json驱动包 开始–><dependency> <groupId>org.json</groupId> <artifactId>json</artifactId> <version>20140107</version></dependency><!– json驱动包 开始–>如果想要深层次了解org.json,可以参考这篇博客:Java使用org.json.jar构造和解析Json数据想要更深层次的了解fastjson,可以参考这篇博客:Fastjson 简明教程spring相关配置如果从事java-web开发,一般会用到spring框架,这方面的教程太多了,笔者就不在这介绍,但我们会用到spring的这些框架:<!–spring 相关配置开始–><dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId></dependency>spring会集合很多框架,比如具有拦截效果的shiro框架,持久层的hibernate和mybatis框架等等。spring通过配置文件通过注解或者配置文件的方式,实现依赖注入(dependency injection)和控制反转(inversion of control)。通过@controller遍历相应的接口,实现前后端的接口对接。spring可以实现面向切面编程,实现某个业务点的单一执行。比如,专门处理事务的业务点。spring并不难,很快就能掌握到其精髓。如果想深入了解,可以参考这篇教程:Spring教程hibernate框架hibernate框架就类似于mybatis框架,其专门处理持久层的技术。我们将瞬时状态的对象存储到数据库中,变成持久状态的对象。我们也可以从数据库中取数据,以瞬时态的对象返回到前端。这就是一存一取的框架。其可以使用注解的方式创建数据表,也可以通过配置文件创建瞬时态的对象。但就目前为止,在很多情况下,我们都是通过注解的方式,实现数据表的创建。导入hibernate相关的框架,如下所示:<!–hibernate相关配置 开始–><!–hibernateh核心框架–><dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId></dependency><!–hibernateh验证器–><dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId></dependency><!–hibernateh缓存技术–><dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-ehcache</artifactId></dependency> <!–Java Persistence API ORM映射元数据 查询语言–><dependency> <groupId>org.hibernate.java-persistence</groupId> <artifactId>jpa-api</artifactId></dependency><!–hibernate相关配置 结束–>hibernate和mybatis具有同样的功能,如果想要了解mybatis,可以参考这篇教程:mybatis教程想要深入理解hibernate,可参考这篇教程:hibernate教程_w3cschooljbdc驱动包我们上面说了hibernate框架,但前提是,我们需要导入jdbc的框架包,如代码所示: <!– 数据库驱动包相关 开始–><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId></dependency><!– 数据库驱动包相关 结束–>这个驱动包主要处理java与数据库的连接,实现数据的增、删、改、查。我们没有用到这个包,但是hibernate用到了这个包,因而,我们需要导入这个包,以免数据库报错。alibaba的Druid包这个包有什么用吗?我们既然通过hibernate实现与数据库的交互,那么就需要在初始化时创建连接池。现在连接池有很多种,我们为什么选择了它Druid。<!– Druid驱动包相关 开始–><dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId></dependency><!– Druid驱动包相关 结束–>它是目前最好的数据库连接池,在功能、性能、扩展性方面,都超过其他数据库连接池,包括DBCP、C3P0、BoneCP、Proxool、JBoss DataSource。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。Druid是阿里巴巴开发的号称为监控而生的数据库连接池!时代在变化,我们也应该应世而生,与时俱进,才不会和时代脱轨。我们使用Druid来实现数据的配置,如代码所示: <bean id=“dataSource” class=“com.alibaba.druid.pool.DruidDataSource” init-method=“init” destroy-method=“close”> <property name=“driverClassName” value=“com.mysql.jdbc.Driver”/> <property name=“url” value="${jdbc.url}"/> <property name=“username” value="${jdbc.username}"/> <property name=“password” value="${jdbc.password}"/> <property name=“maxActive” value="${jdbc.maxActive}"/> <property name=“initialSize” value="${jdbc.initialSize}"/> <property name=“removeAbandoned” value=“true”/> <property name=“removeAbandonedTimeout” value="${jdbc.removeAbandonedTimeout}"/> <property name=“testOnBorrow” value=“true”/> <property name=“minIdle” value="${jdbc.minIdle}"/> <property name=“maxWait” value="${jdbc.maxWait}"/> <property name=“validationQuery” value="${jdbc.validationQuery}"/> <property name=“connectionProperties” value=“clientEncoding=UTF-8”/></bean>你们可以看到,value值是形参,而不是具体的值。因为我们根据不同的打包方式,其传入形参对应的实参不同。这也就是我们上文提到的,platform-dev.properties和platform-prd.properties配置文件,以及maven配置的激活文件。如果想要深入了解阿里巴巴的Druid框架,可以参考这篇博客:DRUID连接池的实用 配置详解阿里云短信短信业务一般固定不变,由架构师封装好,其他人直接调用即可,因而,该框架可以写进plateform-core模块中,其配置的代码如下所示:<!– 阿里短息驱动包配置 开始 –><dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-dysmsapi</artifactId></dependency><dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-core</artifactId></dependency><!– 阿里短息驱动包配置 结束 –>日志相关配置我们在开发的过程中,经常会使用到日志,来记录相应的错误、警告、信息。比如,我在使用连连支付做提现业务时,提现成功后其会回调我们的接口,从而显示在服务端的Tomcat页面中。再比如,我们在登录时,其会在Tomcat中显示相关信息,如图所示:我们都知道日志分为几种级别。这里就不再赘述了。日志分为好多种,我们推荐使用slf4j+logback模式。因为logback自身实现了slf4j的接口,无须额外引入适配器,另外logback是log4j的升级版,具备比log4j更多的优点,我们可以通过如下配置进行集成:<!– 日志驱动包配置 开始 –><dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.21</version></dependency><dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.7</version></dependency><dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.1.7</version></dependency><!– 日志驱动包配置 结束 –>我们这时就用到了plateform-prd.properties文件和plateform-dev.properties文件,因为,我们需要在这里面配置日志的输出位置。然后,在logback.xml中以参数的形式,调用文件中的输出位置,如图所示:如果想要了解更多的配置文件信息,请参考这篇博客:使用 logback + slf4j 进行日志记录commons家族我们在开发的过程中,经常用到Commons家族的驱动包,比如文件操作的io包,MD5加密和解密用的codec包。当然,我们也会用到java自带的local_policy驱动包,但有时需要替换替换该驱动包,否则,就会报出Illegal Key Size的错误。文件上传下载的fileupload驱动包,操作字符串类型的lang3包,配置的驱动包如下所示:<!–comon包相关配置–><commons-io.version>2.4</commons-io.version><commons-lang3.version>3.4</commons-lang3.version><commons-codec.version>1.10</commons-codec.version><commons-fileupload.version>1.3.1</commons-fileupload.version><!– apache common 开始 –><dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>${commons-lang3.version}</version></dependency><dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>${commons-io.version}</version></dependency><dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>${commons-codec.version}</version></dependency><dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>${commons-fileupload.version}</version></dependency><!– apache common 结束 –>lang3包我们可以用其分割字符串,判断字符串是否为空格,判断字符串是否为空等等。如以下代码所示:public static void main(String[] args) { String keyword = “1-1-2”; if (StringUtils.isNotBlank(keyword)) { System.out.println(“keyword = " + keyword); } String[] keys = StringUtils.split(keyword, “-”); for (String key : keys) { System.out.println(“key=” + key); }}我们有时还会用其操作时间类,比如格式化时间等等,入一下代码:@Testpublic void testDate(){ String date1= FastDateFormat.getInstance(“yyyy-MM-dd”).format(System.currentTimeMillis()); System.out.println(“System.currentTimeMillis:"+date1); String date2= FastDateFormat.getInstance(“yyyy-MM-dd”).format(new Date()); System.out.println(“new Date:"+date2);}其功能远不止这些,具体可以参考这篇博客:commons-lang3工具包io包见名知意,IO即input和output的简写,即输入流和输出流。因而,我们经常使用到java自带的InputStream或FileInputStream的字节输入流,以及OutputStream或FileOutputStream的输出流。如果更高级的话,那么,就使用到了带有缓存效果的bufferReader输入流和bufferWrite输出流。这里面用到了装饰设计模式。什么是装修设计模式,可以自行学习。上面的操作比较复杂,我们就用到了apache下的io驱动包。这里就当做抛砖引玉了,想要有更深的了解,可以参考这篇博客:io包工具类codec包codec包是Commons家族中的加密和解密用的包,这里不做任何解释,具体可以参考这篇博客:Commons Codec基本使用fileupload包我们如果做java-web开发,经常会有文件上传和文件下载的功能。这时,我们就考虑到了Apache下面的 fileupload包,这可以完成文件的上传和下载。这里的文件不单单是指doc文件,也会指图片和视频文件。具体想要有更多的理解,可以参考这篇文档:commons-fileupload上传下载shiro包我们在web开发时,经常会涉及到权限问题,比如哪些页面不需要登录就能看,而哪些页面只能登录才能看。当用户在打开该页面之前,就进入到相应的过滤器中,来做相关业务的判断。如果通过,就进入到controller层;不通过,则抛出相应的异常给前端。这里就需要相应的权限控制。说到权限控制,我们不得不提到shiro框架。其有三大核心组件Subject, SecurityManager 和 Realms。这个百度百科上也说了,可以查看其解说内容:java安全框架 <!– shiro驱动包 开始 –><dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId></dependency><dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId></dependency><dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId></dependency><dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId></dependency><!–shiro驱动包 结束 –>公司也会做相应的配置,配置如下:<?xml version=“1.0” encoding=“UTF-8”?><beans xmlns:util=“http://www.springframework.org/schema/util" xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xmlns=“http://www.springframework.org/schema/beans" xsi:schemaLocation=“http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <!– 缓存管理器 –> <bean id=“cacheManager” class=“com..shared.framework.SpringCacheManagerWrapper”> <property name=“cacheManager” ref=“springCacheManager”/> </bean> <bean id=“springCacheManager” class=“org.springframework.cache.ehcache.EhCacheCacheManager”> <property name=“cacheManager” ref=“ehcacheManager”/> </bean> <bean id=“ehcacheManager” class=“org.springframework.cache.ehcache.EhCacheManagerFactoryBean”> <property name=“configLocation” value=“classpath:ehcache.xml”/> </bean> <!– 凭证匹配器 –> <bean id=“credentialsMatcher” class=“com.*.RetryLimitHashedCredentialsMatcher”> <constructor-arg ref=“cacheManager”/> <property name=“hashAlgorithmName” value=“md5”/> <property name=“hashIterations” value=“2”/> <property name=“storedCredentialsHexEncoded” value=“true”/> </bean> <!– Realm实现 –> <bean id=“userRealm” class=“com..shared.web.listener.MemberSecurityRealm”> <!–<property name=“credentialsMatcher” ref=“credentialsMatcher”/>–> <property name=“cachingEnabled” value=“false”/> <!–<property name=“authenticationCachingEnabled” value=“true”/>–> <!–<property name=“authenticationCacheName” value=“authenticationCache”/>–> <!–<property name=“authorizationCachingEnabled” value=“true”/>–> <!–<property name=“authorizationCacheName” value=“authorizationCache”/>–> </bean> <!– 会话ID生成器 –> <!–<bean id=“sessionIdGenerator” class=“org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator”/>–> <!– 会话Cookie模板 –> <bean id=“sessionIdCookie” class=“org.apache.shiro.web.servlet.SimpleCookie”> <constructor-arg value=“platform-portal-sid”/> <property name=“httpOnly” value=“true”/> <property name=“maxAge” value=“7200”/> </bean> <!– 会话管理器 –> <bean id=“sessionManager” class=“org.apache.shiro.web.session.mgt.DefaultWebSessionManager”> <property name=“globalSessionTimeout” value=“43200000”/> <property name=“deleteInvalidSessions” value=“true”/> <property name=“sessionIdCookieEnabled” value=“true”/> <property name=“sessionIdCookie” ref=“sessionIdCookie”/> </bean> <!– 安全管理器 –> <bean id=“securityManager” class=“org.apache.shiro.web.mgt.DefaultWebSecurityManager”> <property name=“realm” ref=“userRealm”/> <property name=“sessionManager” ref=“sessionManager”/> <property name=“cacheManager” ref=“cacheManager”/> </bean> <!– 相当于调用SecurityUtils.setSecurityManager(securityManager) –> <bean class=“org.springframework.beans.factory.config.MethodInvokingFactoryBean”> <property name=“staticMethod” value=“org.apache.shiro.SecurityUtils.setSecurityManager”/> <property name=“arguments” ref=“securityManager”/> </bean> <!– Shiro的Web过滤器 –> <bean id=“shiroFilter” class=“org.apache.shiro.spring.web.ShiroFilterFactoryBean” depends-on=“securityManager,memberShiroFilerChainManager”> <property name=“securityManager” ref=“securityManager”/> </bean> <!– 基于url+角色的身份验证过滤器 –> <bean id=“urlAuthFilter” class=“com.zfounder.platform.core.shared.web.filter.UrlAuthFilter”> <property name=“ignoreCheckUriList”> <list> <value>//common/enums/</value> <value>//security/</value> <value>//common/dd/</value> <value>//pictures/</value> <value>//common/sms/</value> <value>//wx/</value> </list> </property> </bean> <bean id=“memberFilterChainManager” class=“com.zfounder.platform.core.shared.web.listener.CustomDefaultFilterChainManager”> <property name=“customFilters”> <util:map> <entry key=“roles” value-ref=“urlAuthFilter”/> </util:map> </property> </bean> <bean id=“memberFilterChainResolver” class=“com..shared.web.listener.CustomPathMatchingFilterChainResolver”> <property name=“customDefaultFilterChainManager” ref=“memberFilterChainManager”/> </bean> <bean class=“org.springframework.beans.factory.config.MethodInvokingFactoryBean” depends-on=“shiroFilter”> <property name=“targetObject” ref=“shiroFilter”/> <property name=“targetMethod” value=“setFilterChainResolver”/> <property name=“arguments” ref=“memberFilterChainResolver”/> </bean> <!– Shiro生命周期处理器–> <bean id=“lifecycleBeanPostProcessor” class=“org.apache.shiro.spring.LifecycleBeanPostProcessor”/></beans>想要对其有更深的理解,请参考这篇博客:Shiro讲解工具类<!–汉字转拼音开源工具包–> <dependency> <groupId>com.github.stuxuhai</groupId> <artifactId>jpinyin</artifactId></dependency><!–网络爬虫的驱动包–><dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId></dependency><!–验证码生成工具包–><dependency> <groupId>com.github.penggle</groupId> <artifactId>kaptcha</artifactId></dependency><!–发送邮件–><dependency> <groupId>javax.mail</groupId> <artifactId>mail</artifactId></dependency>因为篇幅的限制,这里就不再细说了,如果想要更深层次的了解,可以参考以下博客:汉字转拼音开源工具包Jpinyin介绍爬虫+jsoup轻松爬知乎使用kaptcha生成验证码使用javax.mail发送邮件图片验证码的配置文件如下:<?xml version=“1.0” encoding=“UTF-8”?><beans xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xmlns=“http://www.springframework.org/schema/beans" xsi:schemaLocation=“http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd" default-lazy-init=“true”> <bean id=“captchaProducer” class=“com.google.code.kaptcha.impl.DefaultKaptcha”> <property name=“config”> <bean class=“com.google.code.kaptcha.util.Config”> <constructor-arg> <props> <prop key=“kaptcha.border”>${kaptcha.border}</prop> <prop key=“kaptcha.border.color”>${kaptcha.border.color}</prop> <prop key=“kaptcha.textproducer.font.color”>${kaptcha.textproducer.font.color}</prop> <prop key=“kaptcha.textproducer.char.space”>${kaptcha.textproducer.char.space}</prop> <prop key=“kaptcha.textproducer.font.size”>${kaptcha.textproducer.font.size}</prop> <prop key=“kaptcha.image.width”>${kaptcha.image.width}</prop> <prop key=“kaptcha.image.height”>${kaptcha.image.height}</prop> <prop key=“kaptcha.textproducer.char.length”>${kaptcha.textproducer.char.length}</prop> <prop key=“kaptcha.textproducer.char.string”>1234567890</prop> <prop key=“kaptcha.textproducer.font.names”>宋体,楷体,微软雅黑</prop> <prop key=“kaptcha.noise.color”>${kaptcha.noise.color}</prop> <prop key=“kaptcha.noise.impl”>com.google.code.kaptcha.impl.NoNoise</prop> <prop key=“kaptcha.background.clear.from”>${kaptcha.background.clear.from}</prop> <prop key=“kaptcha.background.clear.to”>${kaptcha.background.clear.to}</prop> <prop key=“kaptcha.word.impl”>com.google.code.kaptcha.text.impl.DefaultWordRenderer</prop> <prop key=“kaptcha.obscurificator.impl”>com.google.code.kaptcha.impl.ShadowGimpy</prop> </props> </constructor-arg> </bean> </property> </bean></beans>里面的占位符来源于plateform-dev.properties或者plateform-prd.properties,这就是我们maven激活的配置文件的作用。测试依赖包我们在开发完一个功能后,首先会想到测试它走不走得通。我们可能会在main方法中测试,一个项目类中可以写多个main方法。如果每个功能类中都写一个main方法,未免会造成代码的混乱,一点都不美观和儒雅。java为什么一直推崇面向对象,任何在现实中真实的、虚拟的事物,都可以将其封装为为java中的对象类。对象与对象之间以方法作为消息传递机制,以属性作为数据库存储的机制。如果我们在每个功能中都写一个main方法,势必会破坏这种对象的美观性。因而,我们把测试的数据以对象的方式操作,这样,将其封装为一个测试包,比如,在我写的spring框架中,就把测试类单独拿出来,如图所示:<!– 测试依赖包 开始–><!– spring test –><dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version></dependency><!– 路径检索json或设置Json –><dependency> <groupId>com.jayway.jsonpath</groupId> <artifactId>json-path</artifactId> <version>${jsonpath.version}</version> <scope>test</scope></dependency><!– testng –><dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>${testng.version}</version></dependency><!– 单元测试的powermock –><dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-testng</artifactId> <version>${powermock.version}</version></dependency><dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito</artifactId> <version>${powermock.version}</version></dependency><!–测试相关 结束–>以上是几种测试包的依赖,一个是spring的测试包,这里由于篇幅的限制,就不做详细的介绍了,网上有很多这方面的教程,想要深入的了解,可参考这篇博客:Spring-Test(单元测试)我们有时也会用到TestNG框架,它是Java中的一个测试框架,类似于JUnit 和NUnit,功能都差不多,只是功能更加强大,使用也更方便。测试人员一般用TestNG来写自动化测试,开发人员一般用JUnit写单元测试。如果你是测试人员,想对其有更全面的了解,可以参考这篇教程:TestNG教程,或者这篇博客::testNG常用用法总结如果想要更深层次的了解powermock,可以参考这篇博客:PowerMock从入门到放弃再到使用如果想要更深层次的了解JsonPath,可以参考这篇博客:JsonPath教程图片处理我们在开发的过程中,会把图片存放到服务器的某个文件夹下,即某个磁盘上。如果图片过大,会占用服务器的磁盘,因而,我们需要将图片缩略,来减少对内存的占用。这时,我们如果使用java原生的图片缩略图,是非常复杂的,因而,我们可以使用以下框架对图片进行操作。<!–图片处理驱动包 开始–><dependency> <groupId>net.coobird</groupId> <artifactId>thumbnailator</artifactId></dependency><!–图片处理驱动包 结束–>这里不再细说,想要有更多的了解,可以参考这篇博客:Thumbnailator框架的使用Excel操作我们在工作的过程中,经常会将数据导出到Excel表,或将Excel表的数据导入数据库。我们以前使用poi框架,但是,超过一定量的时候,会占用大量的内存,从而降低导入的效率。阿里巴巴现在开放出操作Excel表的easyexcel框架,对百万级的导入影响不是很大。以下是maven配置两个驱动依赖:<!–阿里巴巴的easyexcel驱动包 –><dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>{latestVersion}</version></dependency><!–poi驱动包 –><dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>${poi.version}</version></dependency><dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>${poi-ooxml.version}</version></dependency>这两个就不再细说,如果想要对easyexcel更深的了解,可以参考这篇博客:alibaba/easyexcel 框架使用。如果想要对poi有更深的了解,可以参考这篇博客:Apache POI使用详解guava包我们在开发的过程中,有时会用到guava驱动包。它是为了方便编码,并减少编码错误,用于提供集合,缓存,支持原语句,并发性,常见注解,字符串处理,I/O和验证的实用方法。使用它有以下好处:标准化 - Guava库是由谷歌托管。高效 - 可靠,快速和有效的扩展JAVA标准库优化 -Guava库经过高度的优化。同时,又有增加Java功能和处理能力的函数式编程,提供了需要在应用程序中开发的许多实用程序类的,提供了标准的故障安全验证机制,强调了最佳的做法等等。它的宗旨就是:提高代码质量、简化工作,促使代码更有弹性、更加简洁的工具。我们在项目中的配置包为:<!–guava驱动包 开始–> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId></dependency><!–guava驱动包 结束–>如果想要对其有更深的了解,可以参考这篇教程:guava入门教程freemarker包我们在开发的过程中,也许会用到这个框架。为什么要用到这个框架呢?我们有时需要动态地将xml文件转为doc文件,这个时候,就用到了freemarker包,如图所示:截图不是很全面,你会看到画红框的部分,这是一种占位符的标记,就相当于java中的形参一样。 当用户点击前端的下载按钮时,有些数据是无法直接转换成doc的,因为我们先把数据写进xml中,再将xml转化为doc。具体如何转换的可以参考该博客:Java将xml模板动态填充数据转换为word文档我们可以引用这个包: <!–freemarker驱动包 开始–><dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>${freemarker.version}</version></dependency><!–freemarker驱动包 结束–>由于篇幅限制,想要详细了解,可以参考这篇手册: freemarker在线手册servlet驱动包我记得当时在学java-web开发时,最开始用的就是servlet。接收客户端的输入,并经过一系列DB操作,将数据返回给客户端。但使用纯servlet不利于可视化界面。后来,使用了JSP开发,其是可视化界面。但是,当我们启动Tomcat后,JSP通过JSP引擎还是会转为servlet。从本质上来说,JSP和servlet是服务端语言。我最初用servlet和JSP开发的源码地址:图书馆项目后来,工作了以后。后端就用了springMVC,hibernate框架等,前端使用的是HTML、jQuery等。慢慢地脱离了JSP和servlet。但是,并没与完全与servlet分隔开,我们还时不时会用到servlet的一些类,比如HttpServletRequest,HttpServletResponse等类。既然使用了spring MVC框架,为什么还要用servlet的东西,比如,我们在导入和导出时,一个是接收前端导入的请求,一个是响应前端导出的请求。响应前端导出的代码,这里就用到了响应private static void downloadExcel(HttpServletResponse response, File newFile, String fileName) throws IOException { InputStream fis = new BufferedInputStream(new FileInputStream( newFile)); String substring = fileName.substring(fileName.indexOf(”/”) + 1); byte[] buffer = new byte[fis.available()]; fis.read(buffer); fis.close(); response.reset(); response.setContentType(“text/html;charset=UTF-8”); OutputStream toClient = new BufferedOutputStream( response.getOutputStream()); response.setContentType(“application/x-msdownload”); String newName = URLEncoder.encode( substring + System.currentTimeMillis() + “.xlsx”, “UTF-8”); response.addHeader(“Content-Disposition”, “attachment;filename="” + newName + “"”); response.addHeader(“Content-Length”, "” + newFile.length()); toClient.write(buffer); toClient.flush();}接收前端导入的请求 public static LinkedHashMap<String, List<JSONObject>> importMultiSheetExcel(HttpServletRequest request, LinkedHashMap<Integer, Integer> sheetDataStartRowMap, LinkedHashMap<Integer, String> sheetDataEndColMap) { LinkedHashMap<String, List<JSONObject>> resMap = new LinkedHashMap<>(); try { MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request; ifNullThrow(multipartRequest, ResultCodeEnum.ILLEGAL_PARAM); MultipartFile file = multipartRequest.getFile(“file”); Workbook work = getWorkbook(file.getInputStream(), file.getOriginalFilename()); ifNullThrow(work, ResultCodeEnum.ILLEGAL_PARAM); 。。。}虽然我们现在使用了spring MVC,还是用到了servlet,而且shiro里面要使用到,以下是代码的配置:<!–servlet 开始–><!–shiro里面要使用到–><dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>${servlet.version}</version></dependency><!–servlet 结束–><dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>${jstl.version}</version></dependency><!–servlet 结束–>如果想要了解servlet的话,可以参考该文档:Java Servlet API中文说明文档Lucene全文检索有时,我们在开发的过程中,需要做全文检索数据,就比如,我在Word文档中,全文检索某个词、某句话等,如图所示:这就是web端的全文检索。但是我做Java,当然,也需要全文检索。因而,我们就想到了Lucene。它是一套用于全文检索和搜寻的开源程式库,由Apache软件基金会支持和提供。提供了一个简单却强大的应用程式接口,能够做全文索引和搜寻。在Java开发环境里,它是一个成熟的免费开源工具。就其本身而言,它是当前以及最近几年最受欢迎的免费Java信息检索程序库。我们在java的maven库中的配置为: <!– lucene 开始 –><dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-core</artifactId> <version>${lucene.version}</version></dependency><dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-highlighter</artifactId> <version>${lucene.version}</version></dependency><dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-analyzers-common</artifactId> <version>${lucene.version}</version></dependency><!– lucene 结束 –>想要对其有更深的了解,可以参考这篇笔记:Lucene学习笔记Quartz任务调度我们在开发的过程中,总想着要在某个时间,执行什么样的事情,于是呢,我们就相当了任务调度,比如:每天八点按时起床每年农历什么的生日每个星期都要爬一次山我们就可以用到Quartz这个框架,我们需要做一些配置,如图所示:我们可以在maven中的配置为:<!– quartz驱动包 开始–><dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>${quartz.version}</version></dependency><!– quartz驱动包 结束–>想要对其有根深多的了解,可参考这篇博客:Quartz使用总结zxing二维码我们经常使用到二维码,比如,添加微信好友的二维码,支付二维码、扫一扫二维码等等,那么,这是怎么实现的呢,其实,这有一个工具包,就是zxing工具包。它是谷歌旗下的工具类,我们可以用它来生成我们想要的二维码,但是,我们先要在maven项目中配置它。如代码所示:<!– 二维码驱动包 开始–><dependency> <groupId>com.google.zxing</groupId> <artifactId>core</artifactId> <version>${zxing.version}</version></dependency><dependency> <groupId>com.google.zxing</groupId> <artifactId>javase</artifactId> <version>${zxing.se.version}</version></dependency><!– 二维码驱动包 开始–>想要对其有根深的了解,可以参考这篇博客:zxing实现二维码生成和解析WSDL包这个我也不大懂,也没有操作过,如果想要了解的话,可以参考这篇文档:WebService中的WSDL详细解析我们在maven中的怕配置为:<!– WSDL驱动包 开始–> <dependency> <groupId>wsdl4j</groupId> <artifactId>wsdl4j</artifactId> <version>${wsdl4j.version}</version></dependency><!– WSDL驱动包 结束–>配置文件配置文件来源于框架中的以下文件,如图所示:所有的配置文件都来源于资源包。这里就不再细说。总结要想成为架构师,首先学习别人的东西,他山之石,可以攻玉。 ...

March 30, 2019 · 5 min · jiezi

API 统一返回状态码实践

统一API返回json工具类package com.zhihu.portal.common;import java.io.Serializable;/** * 统一API返回json工具类 /public class Result implements Serializable { private Integer code; private String message; private Object data; public Result() { } public Result(Integer code, String message) { this.code = code; this.message = message; } public static Result success(){ Result res = new Result(); res.setResultCode(ResultCode.SUCCESS); return res; } public static Result success(Object data) { Result result = new Result(); result.setResultCode(ResultCode.SUCCESS); result.setData(data); return result; } public static Result failure(ResultCode resultCode) { Result result = new Result(); result.setResultCode(resultCode); result.setMessage(resultCode.message()); return result; } public static Result failure(ResultCode resultCode, Object data) { Result result = new Result(); result.setResultCode(resultCode); result.setData(data); return result; } public void setResultCode(ResultCode code) { this.code = code.code(); this.message = code.message(); } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public Object getData() { return data; } public void setData(Object data) { this.data = data; }}API 统一返回状态码package com.zhihu.portal.common;/* * API 统一返回状态码 /public enum ResultCode { / 成功状态码 / SUCCESS(0, “成功”), / 令牌失效 / TOKEN_INVALID(401,“令牌失效”), SERVER_ERROR(400, “服务器错误”), /参数错误 10001-19999 / PARAM_IS_INVALID(10001, “参数无效”), PARAM_IS_BLANK(10002, “参数为空”), PARAM_TYPE_BIND_ERROR(10003, “参数类型错误”), PARAM_NOT_COMPLETE(10004, “参数缺失”), / 用户错误:20001-29999/ USER_NOT_LOGGED_IN(20001, “用户未登录”), USER_LOGIN_ERROR(20002, “账号不存在或密码错误”), USER_ACCOUNT_FORBIDDEN(20003, “账号已被禁用”), USER_NOT_EXIST(20004, “用户不存在”), USER_HAS_EXISTED(20005, “用户已存在”), Cert_HAS_EXISTED(20006, “认证已存在”), / 业务错误:30001-39999 / CREATE_FAIL(30001, “创建失败”), / 系统错误:40001-49999 / SYSTEM_INNER_ERROR(40001, “系统繁忙,请稍后重试”), / 数据错误:50001-599999 / RESULE_DATA_NONE(50001, “数据未找到”), DATA_IS_WRONG(50002, “数据有误”), DATA_ALREADY_EXISTED(50003, “数据已存在”), / 接口错误:60001-69999 / INTERFACE_INNER_INVOKE_ERROR(60001, “内部系统接口调用异常”), INTERFACE_OUTTER_INVOKE_ERROR(60002, “外部系统接口调用异常”), INTERFACE_FORBID_VISIT(60003, “该接口禁止访问”), INTERFACE_ADDRESS_INVALID(60004, “接口地址无效”), INTERFACE_REQUEST_TIMEOUT(60005, “接口请求超时”), INTERFACE_EXCEED_LOAD(60006, “接口负载过高”), / 权限错误:70001-79999 */ PERMISSION_NO_ACCESS(70001, “只有标签 Owner ,才具备删除权限”), PERMISSION_NO_PHONE_ACCESS(70002,“此认证标签已有员工认证,不可以进行删除”); private Integer code; private String message; ResultCode(Integer code, String message) { this.code = code; this.message = message; } public Integer code() { return this.code; } public String message() { return this.message; } public static String getMessage(String name) { for (ResultCode item : ResultCode.values()) { if (item.name().equals(name)) { return item.message; } } return name; } public static Integer getCode(String name) { for (ResultCode item : ResultCode.values()) { if (item.name().equals(name)) { return item.code; } } return null; } @Override public String toString() { return this.name(); } /public static void main(String[] args) { ResultCode[] ApiResultCodes = ResultCode.values(); List<Integer> codeList = new ArrayList<>(); for (ResultCode ApiResultCode : ApiResultCodes) { if (codeList.contains(ApiResultCode.code)) { System.out.println(ApiResultCode.code); } else { codeList.add(ApiResultCode.code()); } System.out.println(ApiResultCode.code); } }/} ...

March 18, 2019 · 2 min · jiezi

SpringBoot 实战 (十八) | 整合 MongoDB

微信公众号:一个优秀的废人。如有问题,请后台留言,反正我也不会听。前言如题,今天介绍下 SpringBoot 是如何整合 MongoDB 的。MongoDB 简介MongoDB 是由 C++ 编写的非关系型数据库,是一个基于分布式文件存储的开源数据库系统,它将数据存储为一个文档,数据结构由键值 (key=>value) 对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组,非常灵活。存储结构如下:{ “studentId”: “201311611405”, “age”:24, “gender”:“男”, “name”:“一个优秀的废人”}准备工作SpringBoot 2.1.3 RELEASEMongnDB 2.1.3 RELEASEMongoDB 4.0IDEAJDK8创建一个名为 test 的数据库,不会建的。参考菜鸟教程:http://www.runoob.com/mongodb…配置数据源spring: data: mongodb: uri: mongodb://localhost:27017/test以上是无密码写法,如果 MongoDB 设置了密码应这样设置:spring: data: mongodb: uri: mongodb://name:password@localhost:27017/testpom 依赖配置<!– mongodb 依赖 –><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId></dependency><!– web 依赖 –><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency><!– lombok 依赖 –><dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional></dependency><!– test 依赖(没用到) –><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope></dependency>实体类@Datapublic class Student { @Id private String id; @NotNull private String studentId; private Integer age; private String name; private String gender;}dao 层和 JPA 一样,SpringBoot 同样为开发者准备了一套 Repository ,只需要继承 MongoRepository 传入实体类型以及主键类型即可。@Repositorypublic interface StudentRepository extends MongoRepository<Student, String> {}service 层public interface StudentService { Student addStudent(Student student); void deleteStudent(String id); Student updateStudent(Student student); Student findStudentById(String id); List<Student> findAllStudent();}实现类:@Servicepublic class StudentServiceImpl implements StudentService { @Autowired private StudentRepository studentRepository; /** * 添加学生信息 * @param student * @return / @Override @Transactional(rollbackFor = Exception.class) public Student addStudent(Student student) { return studentRepository.save(student); } /* * 根据 id 删除学生信息 * @param id / @Override public void deleteStudent(String id) { studentRepository.deleteById(id); } /* * 更新学生信息 * @param student * @return / @Override @Transactional(rollbackFor = Exception.class) public Student updateStudent(Student student) { Student oldStudent = this.findStudentById(student.getId()); if (oldStudent != null){ oldStudent.setStudentId(student.getStudentId()); oldStudent.setAge(student.getAge()); oldStudent.setName(student.getName()); oldStudent.setGender(student.getGender()); return studentRepository.save(oldStudent); } else { return null; } } /* * 根据 id 查询学生信息 * @param id * @return / @Override public Student findStudentById(String id) { return studentRepository.findById(id).get(); } /* * 查询学生信息列表 * @return */ @Override public List<Student> findAllStudent() { return studentRepository.findAll(); }}controller 层@RestController@RequestMapping("/student")public class StudentController { @Autowired private StudentService studentService; @PostMapping("/add") public Student addStudent(@RequestBody Student student){ return studentService.addStudent(student); } @PutMapping("/update") public Student updateStudent(@RequestBody Student student){ return studentService.updateStudent(student); } @GetMapping("/{id}") public Student findStudentById(@PathVariable(“id”) String id){ return studentService.findStudentById(id); } @DeleteMapping("/{id}") public void deleteStudentById(@PathVariable(“id”) String id){ studentService.deleteStudent(id); } @GetMapping("/list") public List<Student> findAllStudent(){ return studentService.findAllStudent(); }}测试结果Postman 测试已经全部通过,这里仅展示了保存操作。这里推荐一个数据库可视化工具 Robo 3T 。下载地址:https://robomongo.org/download完整代码https://github.com/turoDog/De…如果觉得对你有帮助,请给个 Star 再走呗,非常感谢。后语如果本文对你哪怕有一丁点帮助,请帮忙点好看。你的好看是我坚持写作的动力。另外,关注之后在发送 1024 可领取免费学习资料。资料详情请看这篇旧文:Python、C++、Java、Linux、Go、前端、算法资料分享 ...

March 9, 2019 · 2 min · jiezi

Spring Boot 学习 (1): 初始化工程

spring boot 项目初始化,介绍三种方式:IntelliJ 创建、Spring CLI 创建以及手动创建,工程使用 gradle 构建工具。IntelliJ创建选择 spring initializr填写自己想要的配置信息选择依赖包:配置工程名和工程所在目录:进入到工程,如下图所示:创建完成。Spring CLI创建示例:spring init -dweb,data-jpa,h2,thymeleaf –build gradle initbycli运行命令后会显示:Using service at https://start.spring.ioProject extracted to ‘<current_path>/initbycli’执行完成会看到当前目录下会多出 initbycli 目录..└── initbycli ├── HELP.md ├── build.gradle ├── gradle │ └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main │ ├── java │ │ └── com │ │ └── example │ │ └── initbycli │ │ └── DemoApplication.java │ └── resources │ ├── application.properties │ ├── static │ └── templates └── test └── java └── com └── example └── initbycli └── DemoApplicationTests.java可以看到基本工程目录文件已经创建好了。再看一下 Spring CLI 的说明:$ spring help initspring init - Initialize a new project using Spring Initializr (start.spring.io)usage: spring init [options] [location]Option Description—— ————a, –artifactId <String> Project coordinates; infer archive name (for example ’test’)-b, –boot-version <String> Spring Boot version (for example ‘1.2.0.RELEASE’)–build <String> Build system to use (for example ‘maven’ or ‘gradle’) (default: maven)-d, –dependencies <String> Comma-separated list of dependency identifiers to include in the generated project–description <String> Project description-f, –force Force overwrite of existing files–format <String> Format of the generated content (for example ‘build’ for a build file, ‘project’ for a project archive) (default: project)-g, –groupId <String> Project coordinates (for example ‘org.test’)-j, –java-version <String> Language level (for example ‘1.8’)-l, –language <String> Programming language (for example ‘java’)–list List the capabilities of the service. Use it to discover the dependencies and the types that are available-n, –name <String> Project name; infer application name-p, –packaging <String> Project packaging (for example ‘jar’)–package-name <String> Package name-t, –type <String> Project type. Not normally needed if you use – build and/or –format. Check the capabilities of the service (–list) for more details–target <String> URL of the service to use (default: https://start. spring.io)-v, –version <String> Project version (for example ‘0.0.1-SNAPSHOT’)-x, –extract Extract the project archive. Inferred if a location is specified without an extension说明:依赖: 使用 -d, –dependencies <String>, , 号分割.项目构建类型: –build <String>, 默认为 maven, 另一选项是 gradle.初始化类型: –format <String>, 默认为 project, 会初始化整个工程的目录结构。可选项是 build, 只会生成工程所需要的 build.gradle 文件.查看有哪些可以配置的:–list, 命令输出以后,内容包括可选的依赖包,工程类型和构建属性( java 版本等).手动创建建立工程目录: mkdir initbyself在工程目录下建立 build.gradle 文件,cd initbyselfvim build.gradlebuild.gradle 内容为: plugins { id ‘org.springframework.boot’ version ‘2.1.3.RELEASE’ id ‘java’ } apply plugin: ‘io.spring.dependency-management’ group = ‘com.example’ version = ‘0.0.1-SNAPSHOT’ sourceCompatibility = ‘1.8’ repositories { mavenCentral() } dependencies { implementation ‘org.springframework.boot:spring-boot-starter-data-jpa’ implementation ‘org.springframework.boot:spring-boot-starter-thymeleaf’ implementation ‘org.springframework.boot:spring-boot-starter-web’ runtimeOnly ‘com.h2database:h2’ testImplementation ‘org.springframework.boot:spring-boot-starter-test’ }创建 setting.gradle 文件 pluginManagement { repositories { gradlePluginPortal() } } rootProject.name = ‘initbyself’创建java代码目录mkdir -p src/main/java/com/example/initbyselfmkdir -p src/main/java/resources创建 application切换到代码目录cd src/main/java/com/example/initbyself编写源码文件 DemoApplication.javapackage com.example.initbyself;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }}运行 gradle build, 结果为:> Task :buildSkipping task ‘:build’ as it has no actions.:build (Thread[Daemon worker Thread 2,5,main]) completed. Took 0.0 secs.BUILD SUCCESSFUL in 3s2 actionable tasks: 2 executed运行项目:java -jar build/libs/initbyself-0.0.1-SNAPSHOT.jar . ____ _ __ _ _ /\ / ’ __ _ () __ __ _ \ \ \ ( ( )__ | ‘_ | ‘| | ‘ / ` | \ \ \ \ \/ )| |)| | | | | || (| | ) ) ) ) ’ || .__|| ||| |_, | / / / / =========||==============|/=//// :: Spring Boot :: (v2.1.3.RELEASE)…..2019-03-07 00:17:36.996 INFO 11848 — [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ‘‘2019-03-07 00:17:36.999 INFO 11848 — [ main] com.example.initbyself.DemoApplication : Started DemoApplication in 19.497 seconds (JVM running for 19.992)此时,已说明工程初始化成功了。小结以上是 Spring Boot 项目的初始化的三种方式,不一定全。第一种和第二种都依赖 https://start.spring.io 这个地址,这是官方提供的快速初始化方式,第三种是我们全手动一步一步初始化,需要对构建工具十分熟悉。 ...

March 7, 2019 · 3 min · jiezi

SpringBoot 实战 (十七) | 整合 WebSocket 实现聊天室

微信公众号:一个优秀的废人。如有问题,请后台留言,反正我也不会听。前言昨天那篇介绍了 WebSocket 实现广播,也即服务器端有消息时,将消息发送给所有连接了当前 endpoint 的浏览器。但这无法解决消息由谁发送,又由谁接收的问题。所以,今天写一篇实现一对一的聊天室。今天这一篇建立在昨天那一篇的基础之上,为便于更好理解今天这一篇,推荐先阅读:「SpringBoot 整合WebSocket 实现广播消息 」准备工作Spring Boot 2.1.3 RELEASESpring Security 2.1.3 RELEASEIDEAJDK8pom 依赖因聊天室涉及到用户相关,所以在上一篇基础上引入 Spring Security 2.1.3 RELEASE 依赖<!– Spring Security 依赖 –><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency>Spring Security 的配置虽说涉及到 Spring Security ,但鉴于篇幅有限,这里只对这个项目相关的部分进行介绍,具体的 Spring Security 教程,后面会出。这里的 Spring Security 配置很简单,具体就是设置登录路径、设置安全资源以及在内存中创建用户和密码,密码需要注意加密,这里使用 BCrypt 加密算法在用户登录时对密码进行加密。 代码注释很详细,不多说。package com.nasus.websocket.config;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.builders.WebSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;@Configuration// 开启Spring Security的功能@EnableWebSecuritypublic class WebSecurityConfig extends WebSecurityConfigurerAdapter{ @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() // 设置 SpringSecurity 对 / 和 “/login” 路径不拦截 .mvcMatchers("/","/login").permitAll() .anyRequest().authenticated() .and() .formLogin() // 设置 Spring Security 的登录页面访问路径为/login .loginPage("/login") // 登录成功后转向 /chat 路径 .defaultSuccessUrl("/chat") .permitAll() .and() .logout() .permitAll(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() // 在内存中分配两个用户 nasus 和 chenzy ,用户名和密码一致 // BCryptPasswordEncoder() 是 Spring security 5.0 中新增的加密方式 // 登陆时用 BCrypt 加密方式对用户密码进行处理。 .passwordEncoder(new BCryptPasswordEncoder()) .withUser(“nasus”) // 保证用户登录时使用 bcrypt 对密码进行处理再与内存中的密码比对 .password(new BCryptPasswordEncoder().encode(“nasus”)).roles(“USER”) .and() // 登陆时用 BCrypt 加密方式对用户密码进行处理。 .passwordEncoder(new BCryptPasswordEncoder()) .withUser(“chenzy”) // 保证用户登录时使用 bcrypt 对密码进行处理再与内存中的密码比对 .password(new BCryptPasswordEncoder().encode(“chenzy”)).roles(“USER”); } @Override public void configure(WebSecurity web) throws Exception { // /resource/static 目录下的静态资源,Spring Security 不拦截 web.ignoring().antMatchers("/resource/static**"); }}WebSocket 的配置在上一篇的基础上另外注册一个名为 “/endpointChat” 的节点,以供用户订阅,只有订阅了该节点的用户才能接收到消息;然后,再增加一个名为 “/queue” 消息代理。@Configuration// @EnableWebSocketMessageBroker 注解用于开启使用 STOMP 协议来传输基于代理(MessageBroker)的消息,这时候控制器(controller)// 开始支持@MessageMapping,就像是使用 @requestMapping 一样。@EnableWebSocketMessageBrokerpublic class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { //注册一个名为 /endpointNasus 的 Stomp 节点(endpoint),并指定使用 SockJS 协议。 registry.addEndpoint("/endpointNasus").withSockJS(); //注册一个名为 /endpointChat 的 Stomp 节点(endpoint),并指定使用 SockJS 协议。 registry.addEndpoint("/endpointChat").withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { // 广播式配置名为 /nasus 消息代理 , 这个消息代理必须和 controller 中的 @SendTo 配置的地址前缀一样或者全匹配 // 点对点增加一个 /queue 消息代理 registry.enableSimpleBroker("/queue","/nasus/getResponse"); }}控制器 controller指定发送消息的格式以及模板。详情见,代码注释。@Autowired//使用 SimpMessagingTemplate 向浏览器发送信息private SimpMessagingTemplate messagingTemplate;@MessageMapping("/chat")public void handleChat(Principal principal,String msg){ // 在 SpringMVC 中,可以直接在参数中获得 principal,principal 中包含当前用户信息 if (principal.getName().equals(“nasus”)){ // 硬编码,如果发送人是 nasus 则接收人是 chenzy 反之也成立。 // 通过 messageingTemplate.convertAndSendToUser 方法向用户发送信息,参数一是接收消息用户,参数二是浏览器订阅地址,参数三是消息本身 messagingTemplate.convertAndSendToUser(“chenzy”, “/queue/notifications”,principal.getName()+"-send:" + msg); } else { messagingTemplate.convertAndSendToUser(“nasus”, “/queue/notifications”,principal.getName()+"-send:" + msg); }}登录页面<!DOCTYPE html><html xmlns=“http://www.w3.org/1999/xhtml" xmlns:th=“http://www.thymeleaf.org” xmlns:sec=“http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"><meta charset=“UTF-8” /><head> <title>登陆页面</title></head><body><div th:if="${param.error}"> 无效的账号和密码</div><div th:if="${param.logout}"> 你已注销</div><form th:action=”@{/login}” method=“post”> <div><label> 账号 : <input type=“text” name=“username”/> </label></div> <div><label> 密码: <input type=“password” name=“password”/> </label></div> <div><input type=“submit” value=“登陆”/></div></form></body></html>聊天页面<!DOCTYPE html><html xmlns:th=“http://www.thymeleaf.org”><meta charset=“UTF-8” /><head> <title>Home</title> <script th:src="@{sockjs.min.js}"></script> <script th:src="@{stomp.min.js}"></script> <script th:src="@{jquery.js}"></script></head><body><p> 聊天室</p><form id=“nasusForm”> <textarea rows=“4” cols=“60” name=“text”></textarea> <input type=“submit”/></form><script th:inline=“javascript”> $(’#nasusForm’).submit(function(e){ e.preventDefault(); var text = $(’#nasusForm’).find(’textarea[name=“text”]’).val(); sendSpittle(text); }); // 连接 SockJs 的 endpoint 名称为 “/endpointChat” var sock = new SockJS("/endpointChat"); var stomp = Stomp.over(sock); stomp.connect(‘guest’, ‘guest’, function(frame) { // 订阅 /user/queue/notifications 发送的消息,这里与在控制器的 // messagingTemplate.convertAndSendToUser 中订阅的地址保持一致 // 这里多了 /user 前缀,是必须的,使用了 /user 才会把消息发送到指定用户 stomp.subscribe("/user/queue/notifications", handleNotification); }); function handleNotification(message) { $(’#output’).append("<b>Received: " + message.body + “</b><br/>”) } function sendSpittle(text) { stomp.send("/chat", {}, text); } $(’#stop’).click(function() {sock.close()});</script><div id=“output”></div></body></html>页面控制器 controller@Controllerpublic class ViewController { @GetMapping("/nasus") public String getView(){ return “nasus”; } @GetMapping("/login") public String getLoginView(){ return “login”; } @GetMapping("/chat") public String getChatView(){ return “chat”; }}测试预期结果应该是:两个用户登录系统,可以互相发送消息。但是同一个浏览器的用户会话的 session 是共享的,这里需要在 Chrome 浏览器再添加一个用户。具体操作在 Chrome 的 设置–>管理用户–>添加用户:两个用户分别访问 http://localhost:8080/login 登录系统,跳转至聊天界面:相互发送消息:完整代码https://github.com/turoDog/De…如果觉得对你有帮助,请给个 Star 再走呗,非常感谢。后语如果本文对你哪怕有一丁点帮助,请帮忙点好看。你的好看是我坚持写作的动力。另外,关注之后在发送 1024 可领取免费学习资料。资料详情请看这篇旧文:Python、C++、Java、Linux、Go、前端、算法资料分享 ...

March 6, 2019 · 2 min · jiezi

SpringBoot 实战 (十六) | 整合 WebSocket 基于 STOMP 协议实现广播消息

前言如题,今天介绍的是 SpringBoot 整合 WebSocket 实现广播消息。什么是 WebSocket ?WebSocket 为浏览器和服务器提供了双工异步通信的功能,即浏览器可以向服务器发送信息,反之也成立。WebSocket 是通过一个 socket 来实现双工异步通信能力的,但直接使用 WebSocket ( 或者 SockJS:WebSocket 协议的模拟,增加了当前浏览器不支持使用 WebSocket 的兼容支持) 协议开发程序显得十分繁琐,所以使用它的子协议 STOMP。STOMP 协议简介它是高级的流文本定向消息协议,是一种为 MOM (Message Oriented Middleware,面向消息的中间件) 设计的简单文本协议。它提供了一个可互操作的连接格式,允许 STOMP 客户端与任意 STOMP 消息代理 (Broker) 进行交互,类似于 OpenWire (一种二进制协议)。由于其设计简单,很容易开发客户端,因此在多种语言和多种平台上得到广泛应用。其中最流行的 STOMP 消息代理是 Apache ActiveMQ。STOMP 协议使用一个基于 (frame) 的格式来定义消息,与 Http 的 request 和 response 类似 。广播接下来,实现一个广播消息的 demo。即服务端有消息时,将消息发送给所有连接了当前 endpoint 的浏览器。准备工作SpringBoot 2.1.3IDEAJDK8Pom 依赖配置<dependencies> <!– thymeleaf 模板引擎 –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!– web 启动类 –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!– WebSocket 依赖 –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <!– test 单元测试 –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>代码注释很详细,不多说。配置 WebSocket实现 WebSocketMessageBrokerConfigurer 接口,注册一个 STOMP 节点,配置一个广播消息代理@Configuration// @EnableWebSocketMessageBroker注解用于开启使用STOMP协议来传输基于代理(MessageBroker)的消息,这时候控制器(controller)// 开始支持@MessageMapping,就像是使用@requestMapping一样。@EnableWebSocketMessageBrokerpublic class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { //注册一个 Stomp 的节点(endpoint),并指定使用 SockJS 协议。 registry.addEndpoint("/endpointNasus").withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { // 广播式配置名为 /nasus 消息代理 , 这个消息代理必须和 controller 中的 @SendTo 配置的地址前缀一样或者全匹配 registry.enableSimpleBroker("/nasus"); }}消息类客户端发送给服务器:public class Client2ServerMessage { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; }}服务器发送给客户端:public class Server2ClientMessage { private String responseMessage; public Server2ClientMessage(String responseMessage) { this.responseMessage = responseMessage; } public String getResponseMessage() { return responseMessage; } public void setResponseMessage(String responseMessage) { this.responseMessage = responseMessage; }}演示控制器代码@RestControllerpublic class WebSocketController { @MessageMapping("/hello") // @MessageMapping 和 @RequestMapping 功能类似,浏览器向服务器发起消息,映射到该地址。 @SendTo("/nasus/getResponse") // 如果服务器接受到了消息,就会对订阅了 @SendTo 括号中的地址的浏览器发送消息。 public Server2ClientMessage say(Client2ServerMessage message) throws Exception { Thread.sleep(3000); return new Server2ClientMessage(“Hello,” + message.getName() + “!”); }}引入 STOMP 脚本将 stomp.min.js (STOMP 客户端脚本) 和 sockJS.min.js (sockJS 客户端脚本) 以及 Jquery 放在 resource 文件夹的 static 目录下。演示页面<!DOCTYPE html><html xmlns:th=“http://www.thymeleaf.org”><head> <meta charset=“UTF-8” /> <title>Spring Boot+WebSocket+广播式</title></head><body onload=“disconnect()"><noscript><h2 style=“color: #ff0000”>貌似你的浏览器不支持websocket</h2></noscript><div> <div> <button id=“connect” onclick=“connect();">连接</button> <button id=“disconnect” disabled=“disabled” onclick=“disconnect();">断开连接</button> </div> <div id=“conversationDiv”> <label>输入你的名字</label><input type=“text” id=“name” /> <button id=“sendName” onclick=“sendName();">发送</button> <p id=“response”></p> </div></div><script th:src=”@{sockjs.min.js}"></script><script th:src=”@{stomp.min.js}"></script><script th:src=”@{jquery.js}"></script><script type=“text/javascript”> var stompClient = null; function setConnected(connected) { document.getElementById(‘connect’).disabled = connected; document.getElementById(‘disconnect’).disabled = !connected; document.getElementById(‘conversationDiv’).style.visibility = connected ? ‘visible’ : ‘hidden’; $(’#response’).html(); } function connect() { // 连接 SockJs 的 endpoint 名称为 “/endpointNasus” var socket = new SockJS(’/endpointNasus’); // 使用 STOMP 子协议的 WebSocket 客户端 stompClient = Stomp.over(socket); stompClient.connect({}, function(frame) { setConnected(true); console.log(‘Connected: ’ + frame); // 通过 stompClient.subscribe 订阅 /nasus/getResponse 目标发送的信息,对应控制器的 SendTo 定义 stompClient.subscribe(’/nasus/getResponse’, function(respnose){ // 展示返回的信息,只要订阅了 /nasus/getResponse 目标,都可以接收到服务端返回的信息 showResponse(JSON.parse(respnose.body).responseMessage); }); }); } function disconnect() { // 断开连接 if (stompClient != null) { stompClient.disconnect(); } setConnected(false); console.log(“Disconnected”); } function sendName() { // 向服务端发送消息 var name = $(’#name’).val(); // 通过 stompClient.send 向 /hello (服务端)发送信息,对应控制器 @MessageMapping 中的定义 stompClient.send("/hello”, {}, JSON.stringify({ ’name’: name })); } function showResponse(message) { // 接收返回的消息 var response = $("#response"); response.html(message); }</script></body></html>页面 Controller注意,这里使用的是 @Controller 注解,用于匹配 html 前缀,加载页面。@Controllerpublic class ViewController { @GetMapping("/nasus") public String getView(){ return “nasus”; }}测试结果打开三个窗口访问 http://localhost:8080/nasus ,初始页面长这样:三个页面全部点连接,点击连接订阅 endpoint ,如下图:在第一个页面,输入名字,点发送 ,如下图:在第一个页面发送消息,等待 3 秒,结果是 3 个页面都接受到了服务端返回的信息,广播成功。源码下载:https://github.com/turoDog/De…如果觉得对你有帮助,请给个 Star 再走呗,非常感谢。后语如果本文对你哪怕有一丁点帮助,请帮忙点好看。你的好看是我坚持写作的动力。另外,关注之后在发送 1024 可领取免费学习资料。资料详情请看这篇旧文:Python、C++、Java、Linux、Go、前端、算法资料分享 ...

March 5, 2019 · 2 min · jiezi

SpringBoot 实战 (十二) | 整合 thymeleaf

微信公众号:一个优秀的废人如有问题或建议,请后台留言,我会尽力解决你的问题。前言如题,今天介绍 Thymeleaf ,并整合 Thymeleaf 开发一个简陋版的学生信息管理系统。SpringBoot 提供了大量模板引擎,包含 Freemarker、Groovy、Thymeleaf、Velocity 以及 Mustache,SpringBoot 中推荐使用 Thymeleaf 作为模板引擎,因为 Thymeleaf 提供了完美的 SpringMVC 支持。Thymeleaf 是新一代 Java 模板引擎,在 Spring 4 后推荐使用。什么是模板引擎?Thymeleaf 是一种模板语言。那模板语言或模板引擎是什么?常见的模板语言都包含以下几个概念:数据(Data)、模板(Template)、模板引擎(Template Engine)和结果文档(Result Documents)。数据数据是信息的表现形式和载体,可以是符号、文字、数字、语音、图像、视频等。数据和信息是不可分离的,数据是信息的表达,信息是数据的内涵。数据本身没有意义,数据只有对实体行为产生影响时才成为信息。模板模板,是一个蓝图,即一个与类型无关的类。编译器在使用模板时,会根据模板实参对模板进行实例化,得到一个与类型相关的类。模板引擎模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的HTML文档。结果文档一种特定格式的文档,比如用于网站的模板引擎就会生成一个标准的HTML文档。模板语言用途广泛,常见的用途如下:页面渲染文档生成代码生成所有 “数据+模板=文本” 的应用场景Thymeleaf 简介Thymeleaf 是一个 Java 类库,它是一个 xml/xhtml/html5 的模板引擎,可以作为 MVC 的 web 应用的 View 层。Thymeleaf 还提供了额外的模块与 SpringMVC 集成,所以我们可以使用 Thymeleaf 完全替代 JSP 。Thymeleaf 语法博客资料:http://www.cnblogs.com/nuoyia…官方文档:http://www.thymeleaf.org/docu…SpringBoot 整合 Thymeleaf下面使用 SpringBoot 整合 Thymeleaf 开发一个简陋版的学生信息管理系统。1、准备工作IDEAJDK1.8SpringBoot2.1.32、pom.xml 主要依赖<dependencies> <!– JPA 数据访问 –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!– thymeleaf 模板引擎 –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</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></dependencies>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 表示每次都重新建表,之后设置为 update show-sql: true4、实体类@Data@Entity@AllArgsConstructor@NoArgsConstructorpublic class Student { @Id @GeneratedValue /** * 主键 / private Long id; /* * 主键 / private Long studentId; /* * 姓名 / private String name; /* * 年龄 / private Integer age; /* * 专业 / private String major; /* * 宿舍 / private String dormitory; /* * 籍贯 / private String city; /@Temporal(TemporalType.TIMESTAMP)//将时间戳,转换成年月日时分秒的日期格式 @Column(name = “create_time”,insertable = false, updatable=false, columnDefinition = “timestamp default current_timestamp comment ‘注册时间’”) private Date createDate; @Temporal(TemporalType.TIMESTAMP)//将时间戳,转换成年月日时分秒的日期格式 @Column(name = “update_time”,insertable = false, updatable=true, columnDefinition = “timestamp default current_timestamp comment ‘修改时间’”) private Date updateDate;/}5、dao 层@Repositorypublic interface StudentRepository extends JpaRepository<Student, Long> {}6、service 层public interface StudentService { List<Student> findStudentList(); Student findStudentById(Long id); Student saveStudent(Student student); Student updateStudent(Student student); void deleteStudentById(Long id);}实现类:@Servicepublic class StudentServiceImpl implements StudentService { @Autowired private StudentRepository studentRepository; /** * 查询所有学生信息列表 * @return / @Override public List<Student> findStudentList() { Sort sort = new Sort(Direction.ASC,“id”); return studentRepository.findAll(sort); } /* * 根据 id 查询单个学生信息 * @param id * @return / @Override public Student findStudentById(Long id) { return studentRepository.findById(id).get(); } /* * 保存学生信息 * @param student * @return / @Override public Student saveStudent(Student student) { return studentRepository.save(student); } /* * 更新学生信息 * @param student * @return / @Override public Student updateStudent(Student student) { return studentRepository.save(student); } /* * 根据 id 删除学生信息 * @param id * @return / @Override public void deleteStudentById(Long id) { studentRepository.deleteById(id); }}7、controller 层 (Thymeleaf) 使用controller 层将 view 指向 Thymeleaf:@Controller@RequestMapping("/student")public class StudentController { @Autowired private StudentService studentService; /* * 获取学生信息列表 * @param map * @return / @GetMapping("/list") public String findStudentList(ModelMap map) { map.addAttribute(“studentList”,studentService.findStudentList()); return “studentList”; } /* * 获取保存 student 表单 / @GetMapping(value = “/create”) public String createStudentForm(ModelMap map) { map.addAttribute(“student”, new Student()); map.addAttribute(“action”, “create”); return “studentForm”; } /* * 保存学生信息 * @param student * @return / @PostMapping(value = “/create”) public String saveStudent(@ModelAttribute Student student) { studentService.saveStudent(student); return “redirect:/student/list”; } /* * 根据 id 获取 student 表单,编辑后提交更新 * @param id * @param map * @return / @GetMapping(value = “/update/{id}”) public String edit(@PathVariable Long id, ModelMap map) { map.addAttribute(“student”, studentService.findStudentById(id)); map.addAttribute(“action”, “update”); return “studentForm”; } /* * 更新学生信息 * @param student * @return / @PostMapping(value = “/update”) public String updateStudent(@ModelAttribute Student student) { studentService.updateStudent(student); return “redirect:/student/list”; } /* * 删除学生信息 * @param id * @return / @GetMapping(value = “/delete/{id}”) public String deleteStudentById(@PathVariable Long id) { studentService.deleteStudentById(id); return “redirect:/student/list”; }}简单说下,ModelMap 对象来进行数据绑定到视图。return 字符串,该字符串对应的目录在 resources/templates 下的模板名字。 @ModelAttribute 注解是用来获取页面 Form 表单提交的数据,并绑定到 Student 数据对象。8、studentForm 表单定义了一个 Form 表单用于注册或修改学生信息。<form th:action="@{/student/{action}(action=${action})}" method=“post” class=“form-horizontal”> <div class=“form-group”> <label for=“student_Id” class=“col-sm-2 control-label”>学号:</label> <div class=“col-xs-4”> <input type=“text” class=“form-control” id=“student_Id” name=“name” th:value="${student.studentId}" th:field="{student.studentId}"/> </div> </div> <div class=“form-group”> <label for=“student_name” class=“col-sm-2 control-label”>姓名:</label> <div class=“col-xs-4”> <input type=“text” class=“form-control” id=“student_name” name=“name” th:value="${student.name}" th:field="{student.name}"/> </div> </div> <div class=“form-group”> <label for=“student_age” class=“col-sm-2 control-label”>年龄:</label> <div class=“col-xs-4”> <input type=“text” class=“form-control” id=“student_age” name=“name” th:value="${student.age}" th:field="{student.age}"/> </div> </div> <div class=“form-group”> <label for=“student_major” class=“col-sm-2 control-label”>专业:</label> <div class=“col-xs-4”> <input type=“text” class=“form-control” id=“student_major” name=“name” th:value="${student.major}" th:field="{student.major}"/> </div> </div> <div class=“form-group”> <label for=“student_dormitory” class=“col-sm-2 control-label”>宿舍:</label> <div class=“col-xs-4”> <input type=“text” class=“form-control” id=“student_dormitory” name=“name” th:value="${student.dormitory}" th:field="{student.dormitory}"/> </div> </div> <div class=“form-group”> <label for=“student_city” class=“col-sm-2 control-label”>籍贯:</label> <div class=“col-xs-4”> <input type=“text” class=“form-control” id=“student_city” name=“writer” th:value="${student.city}" th:field="{student.city}"/> </div> </div> <div class=“form-group”> <div class=“col-sm-offset-3 col-sm-10”> <input class=“btn btn-primary” type=“submit” value=“提交”/>&nbsp;&nbsp; <input class=“btn” type=“button” value=“返回” onclick=“history.back()”/> </div> </div> </form>9、studentList 学生列表用于展示学生信息:<table class=“table table-hover table-condensed”> <legend> <strong>学生信息列表</strong> </legend> <thead> <tr> <th>学号</th> <th>姓名</th> <th>年龄</th> <th>专业</th> <th>宿舍</th> <th>籍贯</th> <th>管理</th> </tr> </thead> <tbody> <tr th:each=“student : ${studentList}"> <th scope=“row” th:text="${student.studentId}"></th> <td><a th:href=”@{/student/update/{studentId}(studentId=${student.id})}" th:text="${student.name}"></a></td> <td th:text="${student.age}"></td> <td th:text="${student.major}"></td> <td th:text="${student.dormitory}"></td> <td th:text="${student.city}"></td> <td><a class=“btn btn-danger” th:href="@{/student/delete/{studentId}(studentId=${student.id})}">删除</a></td> </tr> </tbody> </table>页面效果列表页面:点击按钮可注册学生信息注册/修改学生信息页面:点提交保存学生信息到数据库并返回列表页面有数据的列表页面:点击名字跳到注册/修改页面可修改学生信息,点击删除可删除学生信息后语如果本文对你哪怕有一丁点帮助,请帮忙点好看。你的好看是我坚持写作的动力。另外,关注之后在发送 1024 可领取免费学习资料。资料内容详情请看这篇旧文:Python、C++、Java、Linux、Go、前端、算法资料分享 ...

February 25, 2019 · 4 min · jiezi

IntelliJ IDEA创建第一个Spring boot项目

下载maven:http://maven.apache.org/downl…开发工具:IntelliJ IDEAJDK: Java JDK1.8 1.为了第一个项目初始化速度加快,我们先来配置maven:添加配置:选择Build,Execution,Deployment下, Bulid Tools下的Maven,在勾选右边红框中的Override,选择你下载后的文件夹中的settings.xml2.使用IntelliJ IDEA创建springboot项目1.创建新项目2.选择spring,选择jdk1.83.填写group ,选择packaging— War, 选择Next4.选择Web, 点击Next,下一步点击finish就好了。5.等着项目初始化完成就可以了。6.在项目的applicaiton右键,选择Run “DemoApplication"运行成功的截图:我的网站:https://wayne214.github.io

January 19, 2019 · 1 min · jiezi

javaWeb传收参数方式总结

有时候,我真会被传参搞得头晕,这样传要怎么接收,那样传又要怎么接收? get可以json吗?什么是json方式提交?等等问题,已困扰我许久 所以,在此想做个总结,整理一下思绪,不再为传收参烦恼!如有错误,望联系纠正,感谢!首先梳理一下HTTP的一些知识1.GET和POST请求,在传参方面有什么区别GET传输的数据存储在URL上进行拼接 POST传输的数据存储在Requst Body(请求体)中2.http请求的中Content-Typehttp的请求头和响应头中都有Content-Type字段,这个字段向我们说明了请求和响应的HTTP body(请求体或响应体)存储了什么类型的数据,然后客户端和服务端就可以根据http头部得Content-Type正确解码HTTP body内容。关联第1点:GET方式的请求和Content-Type是没有任何关系的,因为GET请求的数据在URL上。好叻,进入正题。这里我们分为3种情况说明如何传参与接参,分别为 GET方式请求、 表单提交、 Json提交1.GET方式请求(1).普通URL get请求http://localhost:8080/ajaxGet?id=1&username=用户名&userTrueName=真实姓名//get也可以传json,通过参数传json字符串,然后后端进行解析(不过一般都不这么做)http://localhost:8080/ajaxGet?user={“id”:“1”,“username”:“用户名”,“userTrueName”:“真实姓名”}(2).表单类GET请求<form id=“fromGet” action=“fromGet” method=“GET”><input type=“text"name=“id” value=“1”><input type=“text"name=“username” value=“用户名”><input type=“text” name=“userTrueName” value=“真实名字”></form>(3).AJAX Get请求(A)正确示例$.ajax({ type: “GET”, url: “http://localhost:8080/ajaxGet”, data:{“id”:1,“username”:“用户名”,“userTrueName”:“真实名称”}, //contentType:‘application/x-www-form-urlencoded’ contentType:‘application/json’});注意:1.data必须为json对象2.实际上无需设置contentType 示例中我故意设置了contentType,但其实不管设置成什么都是无效的,因为传输的数据会在发送请求时,对Json对象进行编码解析,拼接到URL上,如下图(B)错误示例(data为json字符串)//data为json字符串$.ajax({ type: “GET”, url: “http://localhost:8080/ajaxGet”, data:JSON.stringify({“id”:1,“username”:“用户名”,“userTrueName”:“真实名称”}), //contentType:‘application/x-www-form-urlencoded’ contentType:‘application/json’});GET请求时,data不能使用json字符串,无法解析,如下图 SpringMvc接收参数方式1.实体类接收 2.Map接收,必须使用@RequestParam注解 3.拆开单个参数接收(参数少的情况可使用)2.Form表单提交ps:针对POST,第一点包含了所有GET请求方式form表单提交一般说的是content-type为x-www-form-unlencoded或multipart/form-data的请求(1) 传统form表单提交,默认content-type为 x-www-form-unlencoded,如下<form id=“fromPost” action=“fromPost” method=“POST”> <input type=“text"name=“id” value=“1”> <input type=“text"name=“username” value=“用户名”> <input type=“text” name=“userTrueName” value=“真实名字”></form>(2) 含文件的form表单,需要指明enctype为 multipart/form-data(enctype相当于content-type)<form id=“fromMutli” action=“fromMutli” enctype=“multipart/form-data” method=“POST”> <input type=“text"name=“id” value=“1”> <input type=“file” name=“file”></form>(3) Ajaxform表单提交//data为json对象$.ajax({ type: “POST”, url: “http://localhost:8080/ajaxPost”, dataType: ‘json’, data:{“id”:1,“username”:“用户名”,“userTrueName”:“真实名称”}, contentType:‘application/x-www-form-urlencoded’});SpringMvc接收参数方式1.实体类接收 2.Map接收,必须使用@RequestParam注解 3.拆开单个参数接收(参数少的情况可使用) 4.后台的file文件需要使用MultipartFile类型接收3.Json提交ps:针对POST,第一点包含了所有GET请求方式Json提交一般说的是content-type为application/json的请求,传输的Json是Json字符串正确示例//注意:data为json字符串 contentType为application/json$.ajax({ type: “POST”, url: “http://localhost:8080/ajaxPost”, dataType: ‘json’, data:JSON.stringify({“id”:1,“username”:“用户名”,“userTrueName”:“真实名称”}), contentType:‘application/json;charset=urf-8’});注意:data为Json字符串,这个很重要SpringMvc接收参数方式必须使用@RequestBody注解1.字符串接收,然后对Json字符串解析转换2.实体类接收 3.Map接收 如下:@PostMapping(value = “ajaxPost”)public void ajaxPost(@RequestBody String param){ JSONObject json = JSON.parseObject(param);}@PostMapping(value = “ajaxPost”)public void ajaxPost(@RequestBody User user){}@PostMapping(value = “ajaxPost”)public void ajaxPost(@RequestBody Map map){}总结:1.GET请求方式与Form提交,后端spingMvc接收参数方式相同,如下(1)实体类接收(2)Map接收,必须使用@RequestParam注解(3)拆开单个参数接收(参数少的情况使用)(4)后台的file文件需要使用MultipartFil类型接收(form表单文件提交)2.Json提交 必须使用@RequestBody注解(1)字符串接收,然后对json字符串解析转换(2)实体类接收 (3)Map接收3.建议:(1)参数少的查询使用Get请求,参数多可使用Post(2)涉及到数据库的修改操作,使用Post请求(3)Post请求统一使用Json提交(即content-type=application/json),统一方式方便前后端联调,json传参灵活(4)参数多的,使用实体类接收,因为Map含有参数的不确定性,根本看不出你需要的啥参数,宁愿新建一个实体类接收参数,可增强代码的可读性比如使用swagger api文档时,可使用注解标注的实体类对应参数,但用map的话,你要啥参数,鬼知道咯~拓展知识:@RequestParam注解其实使用注解@RequestParam就等同于request.getParamter获取参数 但@RequestParam有更多的用处,它有以下几个重要属性(1).value:前端传参的参数名称,这个属性可以使得前端参数名字与方法参数名不相同,使用这个参数进行数据绑定就ok了//前端传参可以是"name” 方法参数中为"userName” //使用@RequestParam(“name”)可将name和userName进行绑定//即可使用userName接收name的数据@RequestMapping("/test”)public void test(@RequestParam(“name”) String userName)){}(2)requried:表示是否是必须的参数,默认为true,所以加上@RequestParam,默认这个参数就是必须的,如果没有传对应参数会报错(3)defaultValue:参数默认值。即设置默认值后,没有传参时,会赋予参数一个默认值。设置了默认值,就算必须参数不传也不会报错 THANDKSEnd -一个立志成大腿而每天努力奋斗的年轻人伴学习伴成长,成长之路你并不孤单! ...

January 9, 2019 · 1 min · jiezi

SpingMvc复杂参数传收总结

上一篇文章[javaWeb传收参数方式总结]总结了简单传收参数,这一篇讲如何传收复杂参数,比如Long[] 、User(bean里面包含List)、User[]、List<User><user style=“margin: 0px; padding: 0px; max-width: 100%; overflow-wrap: break-word !important; box-sizing: border-box;">、List<Map<String,Object>等几种复杂参数</user>。一.简单数组集合类比如Long[],String[],List<User><long style=“margin: 0px; padding: 0px; max-width: 100%; overflow-wrap: break-word !important; box-sizing: border-box;">等</long>前端:1.重复单个参数//(1)普通http://localhost:8080/ajaxGet?id=1&id=2&id=3//(2)Ajaxget方式 发送请求时等于(1)方式 $.ajax({ type: “GET”, url: “http://localhost:8080/ajaxGet?id=1&id=2&id=3” });//(3)Form表单GET方式 发送请求时等于(1)方式<form id=“fromGet” action=“fromGet” method=“GET”> <input type=“text"name=“id” value=“1”> <input type=“text"name=“id” value=“2”> <input type=“text"name=“id” value=“3”></form>//(4)Form表单POST方式 //发送请求参数会被拼接成 id=1&id=2&id=3 存储在请求体中<form id=“fromGet” action=“fromGet” method=“POST”> <input type=“text"name=“id” value=“1”> <input type=“text"name=“id” value=“2”> <input type=“text"name=“id” value=“3”></form>后端SpringMvc://数组public void ajaxGet(Long[] id){}//List集合public void ajaxGet(@RequestParam(“id”) List<Long> id){}2.数组参数前端://(1)普通urlhttp://localhost:8080/ajaxGet?id[]=1&id[]=2&id[]=3//2.Form GET方式(Ajax异步表单提交) 发送请求时等于(1)方式$.ajax({ type: “GET”, url: “http://localhost:8080/ajaxGet”, data: {“id”:[1,2,3]}, contentType:‘application/x-www-form-urlencoded’ });//(3)Form POST方式(Ajax异步表单提交)//发送请求参数会被拼接成 id[]=1&id[]=2&id[]=3 存储在请求体中$.ajax({ type: “POST”, url: “http://localhost:8080/ajaxPost”, data: {“id”:[1,2,3]}, contentType:‘application/x-www-form-urlencoded’ });后端SpringMvc://数组public void ajaxGet(@RequestParam(“id[]”) Long[] id){}//List集合public void ajaxGet(@RequestParam(“id[]”) List<Long> id){}其实以上两种都是一个道理,主要是看发送请求时 参数是id还是id,来决定后端使不使用@RequestParam(“id[]")进行数据绑定二.复杂实体类与集合比如User(bean里面包含List)、User[]、List<User><user style=“margin: 0px; padding: 0px; max-width: 100%; overflow-wrap: break-word !important; box-sizing: border-box;">、List<Map<String,Object>等,此种类型均使用Json提交</user>1.复杂实体类UserUser实体类//User实体类public class User { private String name; private String pwd; private List<User> customers;//属于用户的客户群 //省略getter/setter } 前端://用户var user = {};user.name = “李刚”;user.pwd = “888”;//客户var customerArray = new Array();customerArray.push({name: “李四”,pwd: “123”});customerArray.push({name: “张三”,pwd: “332”});user. customers = customerArray; $.ajax({ type: “POST”, url: “http://localhost:8080/ajaxPost”, data: JSON.stringify(user), contentType:‘application/json;charset=utf-8’ });后端SpringMvc: public void ajaxPost(@ResponBody User user){ }前端://用户var userList = new Array(); userList.push({name: “李四”,pwd: “123”}); userList.push({name: “张三”,pwd: “332”}); $.ajax({ type: “POST”, url: “http://localhost:8080/ajaxPost”, data: JSON.stringify(userList), contentType:‘application/json;charset=utf-8’ });后端SpringMvc: public void ajaxPost(@ResponBody User[] user){ } public void ajaxPost(@ResponBody List<User> user){ } public void ajaxPost(@ResponBody List<Map<String,Object>> userMap){ } THANDKSEnd -一个立志成大腿而每天努力奋斗的年轻人伴学习伴成长,成长之路你并不孤单! ...

January 9, 2019 · 1 min · jiezi