数据结构,它是储存数据的一种结构体,在此结构中储存一些数据,而这些数据之间有一定的关系。
算法(Algorithm)是对特定问题求解步骤的一种描述,它是指令的有限序列,其中每一条指令表示一个或者多个操作。
真正的自由?——从杨舒平的演讲来谈中国和言论自由
在前天,也就是那个特殊的日子的前一天晚上,我和几个同学彻夜长谈。我们谈了很多东西,我思绪万千,所以我决定写点东西出来。
「Java教程」常用设计模式
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
一场向死而生的征程?——我是这么理解 「Green Android」的
SpringBoot之Thymeleaf用法
Thymeleaf
Thymeleaf是最近SpringBoot推荐支持的模板框架,官网在thymeleaf.org这里。
我们为什么要用Thymeleaf来作为模板引擎呢?官网给了我们一个非常令人信服的解释:
Thymeleaf is a modern server-side Java template engine for both web and standalone environments.>
基本写法就像下面这样:
1 | <table> |
没错,由于这个模板是以xml的格式以属性的形式嵌入在html里,因此不仅适合后台人员使用,也能直接在没有后台程序的情况下直接由浏览器渲染,因为浏览器会自动忽视html未定义的属性。
这个属性还是非常吸引人的,毕竟我们做后台最麻烦的就是在乱七八糟的前台模板加代码,加完代码之后前台的也不知道加的代码对不对,非得先跑一遍才能知道。如果模板文件能够直接由前端人员编写那该多好,而且前端人员在编写的时候就能知道这个代码能不能跑,岂不是非常开心?
参考文档
Thymeleaf的文档链接在这里,细节可以直接去搜索,下面主要列举下我经常遇到的一些问题。
模板定义
由于我们很多的IDE都会提供很好的提示工作,因此我们有必要告诉IDE我们使用的模板规范以方便他给我们提供服务。Thymeleaf的一般规范是这样的:
1 | <html xmlns="http://www.w3.org/1999/xhtml" |
把我们需要编写的DOM放在这个html标签里面就好了。
这句话做了什么事呢?其实就是定义了一个叫th
的名空间,所有Thymeleaf的属性都是在这个名空间下面。
静态文件的加载
我们知道一个网页中加载的静态文件通常有一个十分尴尬的问题,比如对于bootstrap.css,就是如果我们能让IDE识别这个文件,那么我们得用相对路径来引入这个文件。这样我们的IDE才能加载到这个文件,并且给予我们相应的提示。但是如果我们想要在发布后服务器能够加载这个文件,我们就必须用相对于resources或者static的位置来引入静态文件。显然,一般情况下我们不能兼顾这两个问题,只能要么在编写的时候用相对自己的路径,然后在发布的时候用相对于项目资源文件夹的路径,要么就只能放弃IDE的提示,非常尴尬。
而在Thymeleaf中,我们可很好的处理这一点。在引入资源的时候,我们可以写类似下面的代码:
1 | <link rel="stylesheet" type="text/css" media="all" |
当我们在没有后台渲染的情况下,浏览器会认得href,但是不认得th:href,这样它就会选择以相对与本文件的相对路径去加载静态文件。而且我们的IDE也能识别这样的加载方式,从而给我们提示。
当我们在有后台渲染的情况下,后台会把这个标签渲染为这样:
1 | <link rel="stylesheet" type="text/css" media="all" href="/css/gtvg.css" /> |
原来的href标签会被替换成相对于项目的路径,因此服务器就能找到正确的资源,从而正确渲染。
非常的智能而且方便。
这里需要注意到所有的路径我们是用”@{}”来引用,而不是”${}”,因为后者是用来引用变量名的,而前者是引用路径的,因此我们在这里用的是前者。可是如果我们是把路径写在变量里,那么就要用后者来引用了。
常量的渲染以及文字国际化
很多情况下我们并不希望在代码里硬编码进文字,我们希望把文字提取成统一的代号,这样方便管理,也方便更改语言。
我们要做的首先是创建一个语言文件,比如message_chinese.properties
:
1 | title=这是标题 |
然后我们在application.properties里加上下面这行注册这个语言文件:
1 | spring.messages.basename=message_chinese |
这样,我们在模板里就可以通过#{消息名}
来获取这个消息对应的真正的文字:
1 | <title th:text="#{title}"></title> |
变量的渲染
对于一个模板文件来说,最重要的事情莫过与传递变量了。
这件事情非常简单,主要分为两步,首先是在SpringBoot的Controller里给Model传进参数:
1 | @Controller |
这样我们就可以在模板里通过th:属性名="${变量名}"
这种方式来传值,比如:
1 | <span th:text="${var1}"></span> |
定义十分清楚,也很容易类比,支持层次选择,不再细说。
循环语句
当我们需要动态加载一些帖子的时候,我们经常需要用循环语句,Thymeleaf中循环语句也很简单,主要是依靠th:each
这玩意来实现。
首先我们当然是在Controller里创建可供循环的List对象并传递给model:
1 | @Controller |
然后在需要循环的地方这样使用:
1 | <div th:each="value:${list}" th:text="${value}"></div> |
就能循环渲染这个list里的元素了。
激活语句
所谓的激活语句(自己起得名字),就是在某些情况下我们想根据变量的值来选择到底显示还是不显示这个标签。用法也很见简单,主要靠th:if
跟th:unless
:
1 | <div th:if="${judge}" >if clause</div> |
通过判断judge这个变量是否不为空来控制这个标签是否显示。。。if跟unless互为反义词。。。不解释了。。。
选择语句
类似于switch-case语句,非常简单,见下例:
1 | <div th:switch="${user.role}"> |
这段代码顺便体现了一个小细节,如果想在th名空间里直接填入字符串,我们必须再用一对引号来引用。。。
代码分割引用
thymeleaf也提供了类似import的东西,可以将很多代码块抽象成模块,然后在需要的时候引用,非常方便。具体的说,引用方式有两种–replace和include。
比如有两个文件
1 | <!--footer.html--> |
1 | <!--index.html--> |
通过这样,我们就可以在index里面引用footer里面的这个div,我们用的是include,因此渲染的结果就是这样:
1 | <div> |
如果是replace,那就是整个标签的替换,很好理解。
当然,除了用fragment来标识引用的部分,我们还可以用id来引用,具体可以参考文档。
有时候我们可能希望在引用的时候传递参数,我们可以在引用时加上这样的参数:
1 | <!--index.html--> |
这样我们就可以把index页面的value变量传递到footer页面里。非常简单。
引用js的坑
有时候我们想用js变量来保存模板传递的参数,我们可以这样来引用:
1 | <script th:inline="javascript"> |
注意,这是官方推荐的写法,注意以下几点。
- 我们要用inline来指定这个script标签;
- 我们需要注释
<![CDATA[
,]]>
对,否则就会无法在js中使用比较符号; - 我们要用
[[${value}]]
来引用模板变量; - 我们要在变量外面也套上注释
\**\
,并在后面添加上默认的值,这是为了前端开发人员能在没有后台的情况下正常渲染。
最后
当前我遇到的基本上靠上面的知识都足以解决了,更多高级用法可以直接读文档,到时候用到再来添加0.0。
「Java教程」网络编程基础
网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。
java.net 包中的类和接口,它们提供低层次的通信细节。你可以直接使用这些类和接口,来专注于解决问题,而不用关注通信细节。
SpringBoot之拦截器用法
拦截器
我们知道做Web开发最知名的一个编程思路叫AOP–面向切面的编程。第一次接触到这个名词以为是跟面向对象编程一样是套很复杂的流程。然而实际接触下来才发现,这其实是一个非常简单的思想,能够解决非常现实的问题,比如登录问题。
我们知道很多网站在访问时是需要登录的,也就是说服务器在处理每一个访问之前,必须都要做一件事情,就是登录用户的身份确认。我们很容易想到一个解决方法,就是定义一个类,专门处理这个登录问题。但是这也是比较麻烦的事情,在每一个请求处理前都要写一些一模一样的代码来调用这个类,显然维护起来比较难受。那怎么办呢?这时候我们很容易想到在Web请求处理的生命周期里横插一刀,在对请求进行处理之前统一加上一个函数,做一些必须做的事情。那么这个函数就叫做切面,这个方法就叫做面向切面的编程。
不过事实上,切面也分种类,我们可以在通过请求的URL来进行过滤,也可以指定Controller的名字来进行过滤。有时候我们会通过Controller的名字来进行拦截,不过这需要添加一些额外的包,比如spring-boot-starter-aop
。通常情况下我们使用的是对URL进行过滤,这就要用到我们SpringBoot自带的Interceptor机制了。
定义拦截器
为了定义一个拦截器,我们只需要定义一个Component,让他实现HandlerInterceptor接口:
1 | @Component |
事实上,HandlerInterceptor这个接口有三个重载方法,我们可以打开源码来看看:
1 | public interface HandlerInterceptor { |
这三个函数就像三把刀,横着插进了服务端接受并处理请求的整个生命周期。
preHandle
preHandle作用的地方是请求已经被RequestMapping
分配到了不同的Controller
里,但是还并未被Controller
进行处理。
如果我们的目的是对登陆进行验证的话,那么这里就是我们主要的工作地点了。在这个过程里,我们可以验证用户的Cookie:
- 如果成功,则可以记录下当前User的信息,并且将这个信息保存到一个ThreadLocal的UserComponent里,方便以后的调用,并且将请求放行。
- 如果失败,则可以将请求通过response的sendRedirect函数重定向到登陆页面。
这里的返回值就是是否放行,如果不放行,那么客户端就收不到任何消息。显然我们一般都会放行。
postHandle
postHandle作用的地方是请求已经被Controller
处理了,但是还并未传递到网页模板进行渲染。因此我们可以看到postHandle的参数比preHandle多了一个ModelAndView这个参数。这个参数其实就包括了Controller
处理后需要传递给模板的那个Model参数。
如果我们的目的是将拦截器得到的User信息统一渲染到模板上,那么我们只要在这个步骤做这件事就不会错了:
1 | @Override |
afterCompletion
afterCompletion显然就是处理一些收尾工作了,他作用的地方就是在页面被渲染之后即将返回给用户的时候。这里通常是清除一些局部变量,比如清除掉在前面保存的ThreadLocal的本地信息:
1 | @Override |
配置拦截器
当然,上面写了半天代码,我们并没有定义我们的拦截器的拦截规则,也没有在SpringBoot里做任何配置。下面我们就来进行相应的配置。
我们需要新建一个继承了WebMvcConfigurerAdapter
的配置类,或者在原有配置类上进行修改。
1 | @Component |
这个WebMvcConfigurerAdapter管理了很多的配置信息,就包括了拦截器的配置。
我们需要做的就是在这里通过依赖注入导入我们想注册的拦截器,然后通过重写addInterceptors
方法来进行配置。
如果需要对拦截器进行过滤,我们只需要对addInterceptors
返回的InterceptorRegistration
对象进行处理。
这个InterceptorRegistration
对象通常有两个方法addPathPatterns
和excludePathPatterns
,并且支持链式调用。
显然,这两个函数应该会接受一个用来进行匹配的字符串,跟普通的正则匹配的规则不同,这类规则我们通常称为Ant path style
。
Ant Path Style
这个规范的设计在org.springframework.util.AntPathMatcher
里,基本上包括了下面的规则:
1 | ? matches one character |
具体可以参照下面的例子:
1 | com/t?st.jsp — matches com/test.jsp but also com/tast.jsp or com/txst.jsp |
最后
通过上述步骤,我们就成功配置了一个拦截器。
参考资料
SpringBoot之定时任务
任务需求
最近在用SpringBoot写一个关于定时项目的时候遇到一个问题,就是客户端访问服务器的结果实际上是每个一段时间发生一次变化,并且在服务器在每天的某个固定的时间点都要触发一次事件。
我们当然可以在遇到每一个请求时都重新计算结果,但是为了提高效率,我们显然可以让服务器每隔一段时间计算一次结果,并且把这个结果进行保存,对在下一个时间段内的每个请求都直接返回计算后的结果。这样就能较好的提高了服务器的性能。
那么问题就在于如何处理定时任务。其实SpringBoot早就提供了非常方便的接口,但是网上的介绍还是有点乱的,我就记录下具体操作的注意点方便以后查找。
创建定时服务
一般来说定时服务会写在一个Component里,方便管理。对于定时任务,我们其实只要在需要定时执行的函数前加上@Scheduled
注解,比如下面这样:
1 | @Component |
与此同时,我们还要在项目的启动文件里配置上@EnableScheduling
注解,告诉项目我们是支持定时任务的:
1 | @SpringBootApplication |
这样我们的函数就能定时执行了。
Scheduled参数
Scheduled主要支持fixRate
,fixDelay
,cron
,initialDelay
这些参数,下面做简要说明。
fixRate和fixDelay
fixRate和fixDelay参数都指定了函数每隔某个毫秒数执行一次,但是他们之间也有细小的差别。
fixRate
fixRate的计时是相对于系统时间的,也就是一定相隔会固定时间执行。
fixDelay
fixDelay的计时是相对于上一次调用的时间的,因此他受其他程序调用的影响,如果该函数在其他地方被手动调用,那么这个计时器就会重新计时。
initialDelay参数
initialDelay参数是个额外参数,比较简单,就是指定从项目开始运行到该函数首次被调用的执行时间,以毫秒计。
如果不指定这个参数,这个值就是-1,也就是程序开始时不执行。
在不知到initialDelay这个参数的时候为了让程序启动时立即调用该函数,我让这个定时类继承了InitializingBean
并在重写的afterPropertiesSet
方法中手动调用了这个函数。。。现在看起来还真是愚蠢。。。
cron
这个Cron是最复杂也是高度自定义化的定时工具,在Linux系统里也有类似的crontab命令。他其实是更加细致的定义了定时任务,以一个字符串的形式进行表示。
在SpringBoot中,一个cron字符串是由六个部分以空格组成的字符串,文档中的例子是这样的:
1 | "0 0 * * * *" = the top of every hour of every day. |
六个部分分别表示秒、分、时、日、月、周。
他支持’-‘表示范围,’*’表示通配,’/‘表示在左边的时间匹配后间隔右边的时间,’?’一般表示周的通配。
具体的使用方法还要参考相关文档。
网上有很多类似Cron表达式测试工具这样的测试工具,方便我们测试自己写的Cron表达式。
下面一个例子表示每隔五分钟执行一次:
1 | @Scheduled(cron = "0 0/5 * * * ?") |
注意点
在实际使用Scheduled注解时,我们一定要指定且仅仅指定fixRate、fixDelay、cron当中的一个,否则肯定会导致错误。当然,我们可以任意指定initialDelay参数。
参考资料
SpringBoot Guides
Spring CronSequenceGenerator
Cron expression
Cron表达式详解
SpringBoot之简单部署
命令行测试运行
有些时候我们需要将项目部署在服务器上进行简单测试,但是直接部署到Tomcat里又十分慢,这时候我们可以用maven工具的一个命令来模拟本地启动项目:
1 | ./mvnw spring-boot:run |
这里用到了一个叫spring-boot-starter-parent
的包,只要pom.xml里加了这个就能执行。
这样我们就可以在服务器的8080端口访问到这个临时的项目。
简单部署
通常情况下,我们会把SpringBoot生成的项目打包成war放在Tomcat服务器中运行,不过这当中也有一些需要注意的地方,下面就简单罗列一下,作为总结。
修改项目配置
参考SpringBoot的一篇文档Traditional deployment。为了能让项目支持在Tomcat中启动,我们首先要修改一下启动配置。
修改项目中的XXXApplication.java
这个启动文件,使他继承SpringBootServletInitializer
,并重载configure
方法:
1 | @SpringBootApplication |
这样这样就可以让项目在Tomcat中启动了。
修改导出的包类型
由于默认使用的包管理工具是maven,我们要修改下pom.xml中的导出方式,在该文件的开头将packaging
标签修改为war
。
1 | <packaging>war</packaging> |
运行Maven命令导出包
为了保证项目能够在服务器中运行,最好将编译的过程放在服务器上,这样可以提早发现服务器版本落后的等的问题。因为很多情况下,Java Tomcat版本落后会导致很多奇怪的问题,有时候还不容易发现。
在项目根目录中有一个mvnw
文件,我们通过下面命令执行他:
1 | ./mvnw clean package |
这样,程序就会编译并且测试这个项目,以保证该项目能够在当前环境中运行。
编译并测试通过后,会在项目的target/
文件夹下生成一个war包,我们把这个包放到Tomcat的webapps目录下并且重启项目,稍等片刻后即可在8080端口下与项目名相对应的路径中访问到该项目。
配置反向代理
上面的项目在很多情况下会加载不了静态文件,因为项目中访问静态文件通常是直接在xxx.xxx.xxx/css
之类的路径中,而Tomcat简单部署后项目的根路径会变成xxx.xxx.xxx/projectName/
,这样实际的静态文件路径就是xxx.xxx.xxx/projectName/css/
,显然就会找不到了。
当然,我们可以配置Tomcat将项目映射到/
下,但是为了保证项目的并发性更好,我们一般都采用nginx进行反向代理。
一般在/etc/nginx/sites-enabled/default
文件中,修改出一个类似下面的配置:
1 | server { |
主要就是添加一个反向代理并开启Gzip加速。
很多情况下对服务性能的提高还是非常有帮助的,尤其是Gzip,如果发现某些mime-type没有加速可以把这个类型添加到gzip_types
中。
查看Log日志
很多情况下,我们会在程序里打上Log,那么在Tomcat里这些Log都写在了哪里呢?
这个主要是Tomcat的设置了,在Tomcat的logs文件夹下有很多log文件,主要是下面三类:
项目启动类日志
这类日志一般名字会类似catalina.2017-05-19.log
这样。
这些日志主要记录了项目启动以及关闭时可能报的错,主要是Tomcat在启动服务的时候写的。
Web访问类日志
这类日志一般名字会类似localhost_access_log.2017-05-19.txt
这样。
这些日志主要记录了每一个Web访问的历史,跟Apache的日志类似:
1 | 127.0.0.1 - - [19/May/2017:15:03:28 +0800] "GET /onlinelibrary-0.0.1-SNAPSHOT/js/swiper.min.js HTTP/1.1" 200 96826 |
SpringBoot日志
这类日志一般名字会类似catalina.out
这样。
这些日志主要记录了SpringBoot的启动日志,以及程序员在程序中写的logger的日志,方便我们进行监控。