每天学习一个命令: Linux 查看磁盘信息命令 di

平时在 Linux 上查看磁盘信息都使用 df -lh , -l 显示本地文件系统, -h 来表示 human readable 。虽然 df 在一定程度上能够满足查询磁盘剩余空间的需求,但是这里要介绍一款强大于 df 的命令 —- di 。

使用如下命令安装

sudo apt install di

di 命令是 disk information 的缩写,直接运行 di 会有如下结果

di Filesystem Mount Size Used Avail %Used fs Type /dev/sda1 / 901.2G 188.3G 667.1G 26% ext4 tmpfs /dev/shm 7.8G 0.1G 7.6G 2% tmpfs tmpfs /run 1.6G 0.1G 1.5G 4% tmpfs cgmfs /run/cgmanager/ 0.1M 0.0M 0.1M 0% tmpfs tmpfs /run/lock 5.0M 0.0M 5.0M 0% tmpfs tmpfs /run/user/0 1.6G 0.0G 1.6G 0% tmpfs tmpfs /run/user/1000 1.6G 0.0G 1.6G 0% tmpfs tmpfs /sys/fs/cgroup 7.8G 0.0G 7.8G 0% tmpfs /dev/sda1 /var/lib/docker 901.2G 188.3G 667.1G 26% ext4

di 默认的输出就是比较人性化的了。

看 di 的使用介绍 man di 就会发现 di 是这么介绍自己的

> di Displays usage information on mounted filesystems.  Block values are reported in a human readable format.  If the user or group has  a  disk  quota,  the  values  reported  are adjusted according the quotas that apply to the user.

一些简单的使用

A 选项打印所有挂载设备

di -A Mount fs Type Filesystem Options Size Used Free %Used %Free Size Used Avail %Used %Free Size Used Avail %Used Inodes IUsed IFree %IUsed / ext4 /dev/sda1 rw,relatime,errors=remount-ro,data=ordered 901.2G 188.3G 712.9G 21% 79% 901.2G 234.1G 667.1G 26% 74% 855.4G 188.3G 667.1G 22% 60014592 1372538 58642054 2% /dev/shm tmpfs tmpfs rw,nosuid,nodev 7.8G 0.1G 7.6G 2% 98% 7.8G 0.1G 7.6G 2% 98% 7.8G 0.1G 7.6G 2% 2036725 741 2035984 0% /run tmpfs tmpfs rw,nosuid,noexec,relatime,size=1629380k,mode=755 1.6G 0.1G 1.5G 4% 96% 1.6G 0.1G 1.5G 4% 96% 1.6G 0.1G 1.5G 4% 2036725 777 2035948 0% /run/cgmanager/fs tmpfs cgmfs rw,relatime,size=100k,mode=755 0.1M 0.0M 0.1M 0% 100% 0.1M 0.0M 0.1M 0% 100% 0.1M 0.0M 0.1M 0% 2036725 14 2036711 0% /run/lock tmpfs tmpfs rw,nosuid,nodev,noexec,relatime,size=5120k 5.0M 0.0M 5.0M 0% 100% 5.0M 0.0M 5.0M 0% 100% 5.0M 0.0M 5.0M 0% 2036725 4 2036721 0% /run/user/0 tmpfs tmpfs rw,nosuid,nodev,relatime,size=1629380k,mode=700 1.6G 0.0G 1.6G 0% 100% 1.6G 0.0G 1.6G 0% 100% 1.6G 0.0G 1.6G 0% 2036725 4 2036721 0% /run/user/1000 tmpfs tmpfs rw,nosuid,nodev,relatime,size=1629380k,mode=700,uid=1000,gid=1000 1.6G 0.0G 1.6G 0% 100% 1.6G 0.0G 1.6G 0% 100% 1.6G 0.0G 1.6G 0% 2036725 36 2036689 0% /sys/fs/cgroup tmpfs tmpfs rw,mode=755 7.8G 0.0G 7.8G 0% 100% 7.8G 0.0G 7.8G 0% 100% 7.8G 0.0G 7.8G 0% 2036725 18 2036707 0% /var/lib/docker/aufs ext4 /dev/sda1 rw,relatime,errors=remount-ro,data=ordered 901.2G 188.3G 712.9G 21% 79% 901.2G 234.1G 667.1G 26% 74% 855.4G 188.3G 667.1G 22% 60014592 1372538 58642054 2%

c 选项逗号分割

使用 -c 选项分割输出

di -c s,m,b,u,v,p,T "/dev/sda1","/","901.2G","188.3G","667.1G",26%,"ext4" "tmpfs","/dev/shm","7.8G","0.1G","7.6G",2%,"tmpfs" "tmpfs","/run","1.6G","0.1G","1.5G",4%,"tmpfs" "cgmfs","/run/cgmanager/fs","0.1M","0.0M","0.1M",0%,"tmpfs" "tmpfs","/run/lock","5.0M","0.0M","5.0M",0%,"tmpfs" "tmpfs","/run/user/0","1.6G","0.0G","1.6G",0%,"tmpfs" "tmpfs","/run/user/1000","1.6G","0.0G","1.6G",0%,"tmpfs" "tmpfs","/sys/fs/cgroup","7.8G","0.0G","7.8G",0%,"tmpfs" "/dev/sda1","/var/lib/docker/aufs","901.2G","188.3G","667.1G",26%,"ext4"

c 是 --csv-output 的缩写,为了便于程序解析

t 参数增加统计行

-t 参数在最后增加统计行

di -t Filesystem Mount Size Used Avail %Used fs Type /dev/sda1 / 901.2G 188.4G 667.0G 26% ext4 tmpfs /dev/shm 7.8G 0.1G 7.6G 2% tmpfs tmpfs /run 1.6G 0.1G 1.5G 4% tmpfs cgmfs /run/cgmanager/ 0.1M 0.0M 0.1M 0% tmpfs tmpfs /run/lock 5.0M 0.0M 5.0M 0% tmpfs tmpfs /run/user/0 1.6G 0.0G 1.6G 0% tmpfs tmpfs /run/user/1000 1.6G 0.0G 1.6G 0% tmpfs tmpfs /sys/fs/cgroup 7.8G 0.0G 7.8G 0% tmpfs /dev/sda1 /var/lib/docker 901.2G 188.4G 667.0G 26% ext4 Total 1.8T 0.4T 1.3T 26%

s 参数对结果排序

di -s 默认更具 mount point 输出

  • di -sm 默认 mount pont
  • di -sn 不排序,按照挂载表 /etc/fstab 中顺序
  • di -ss 按照特殊设备
  • di -st 根据 filesystem type
  • di -sr 逆序输出

排序方式可以组合使用,如:di –stsrm :按照类型、设备、挂载点逆序排序。 di –strsrm :按照类型、设备逆序、挂载点逆序排序。

f 选项自定义格式

di -fM Mount / /dev/shm /run /run/cgmanager/fs /run/lock /run/user/0 /run/user/1000 /sys/fs/cgroup /var/lib/docker/aufs

只打印 mount point

更多的 f 的选项可以直接参看 man di

总结

虽然 di 提供了比 df 更多更强大的功能,但是也有其缺点,大部分的 Linux 发行版默认是没有预装的。


2017-10-16 linux , 磁盘管理 , disk , df

Maven 介绍

Maven 是一个项目管理工具,主要用于项目构建,依赖管理,项目信息管理。自动化构建过程,从清理、编译、测试和生成报告、再到打包和部署。Maven 通过一小段描述信息来管理项目。

Maven 安装

安装之前先把 JDK 环境配置好。

Debian/Ubuntu/Linux Mint 下

sudo apt install maven

如果要手动安装则按照下面步骤

  • 下载 Maven 最新安装包,地址 http://Maven.apache.org/download.cgi 比如 apache-Maven-3.3.9-bin.tar.gz
  • tar -zxvf apache-Maven-3.3.9-bin.tar.gz
  • 将 apache-Maven-3.3.9 目录移动到 /usr/local 目录 命令: sudo mv apache-maven-3.3.9/ /usr/local/
  • root 身份修改配置命令 sudo vi ~/.bashrc 在文件最后添加:

      #set Maven environment
      M2_HOME=/usr/local/apache-maven-3.3.9
      export Maven_OPTS="-Xms256m -Xmx512m"
      export PATH=$M2_HOME/bin:$PATH
    

    保存并关闭。

  • 使配置生效必须重启机器或者在命令行输入: source ~/.bashrc
  • 查看 Maven 是否安装成功: mvn -version

如果进行了上面步骤在任意目录中 mvn 命令不能使用,可以在 /etc/profile 文件后面加入下面三行 sudo vim ~/.bashrc

然后输入以下内容

Maven_HOME=/usr/local/apache-maven-3.3.9
export Maven_HOME
export PATH=${PATH}:${Maven_HOME}/bin

设置好 Maven 的路径之后,需要运行下面的命令 source ~/.bashrc 使刚刚的配置生效

Maven 作用

Maven 最熟悉的一个概念就是 POM,Maven 项目会有一个 pom.xml 文件, 在这个文件里面添加相应配置,Maven 就会自动帮你下载相应 jar 包

<dependency>
  <groupId>com.google.firebase</groupId>    项目名
  <artifactId>firebase-admin</artifactId>   项目模块
  <version>5.3.1</version>                  项目版本
</dependency>

项目名 - 项目模块 - 项目版本 三个坐标定义了项目在 Maven 世界中的基本坐标,任何 jar,pom, war 都是基于这些坐标进行区分的。

  • groupId 定义了项目组,组和项目所在组织或公司,或者开源项目名称,一般为公司域名反写,比如 com.google.firebase 等等

  • artifactId 定义了 Maven 项目的名称,在组中的唯一 ID,在同一个项目中可能有不同的子项目,可以定义不同的 artifactId。 artifactId 也是构建完成项目后生成的 jar 包或者 war 包的文件名的一部分。

  • version 顾名思义,就是项目的版本号,如果项目维发布,一般在开发中的版本号习惯性加上 SNAPSHOT, 比如 1.0-SNAPSHOT

根据上面的例子,比如上面定义的 Maven 坐标,可以在对应的中央仓库中 https://repo1.maven.org/maven2/com/google/firebase/firebase-admin/5.3.1/ 目录下找到对应的文件。

  • scope 定义了依赖范围,如果依赖范围为 test ,那么该依赖只对测试有效,也就是说在测试代码中引入 junit 有效,在主代码中用 junit 会造成编译错误。如果不声明依赖范围则默认为 compile ,表示该依赖对主代码和测试代码都有效。

Maven 有以下几种依赖范围:

  • compile 编译依赖,在编译、测试、运行时都有效
  • test 测试依赖,只对于测试 classpath 有效, JUnit 典型
  • provided 已提供依赖,只在编译和测试有效,运行时无效, servlet-api 编译和测试项目时需要该依赖,但是在运行项目时,由于容器已经提供,不需要 Maven 重复引入
  • runtime 运行时依赖,对于测试和运行有效,编译主代码无效, JDBC 驱动实现,项目主代码编译只需要 JDK 提供的 JDBC 接口,只有执行测试或者运行项目才需要实现上述接口的具体 JDBC 驱动
  • system 系统依赖范围,和 provided 范围依赖一致,但是使用 system 范围的依赖时必须通过 systemPath 元素显示地指定依赖文件的路径。
  • import 导入依赖,一般不用

依赖范围和 classpath 的关系

依赖范围 Scope 编译 classpath 有效 测试 classpath 有效 运行 classpath 有效 例子
compile Y Y Y spring-core
test - Y - junit
provided Y Y - servlet-api
runtime - - Y JDBC 驱动
system Y Y - 本地的, Maven 仓库之外的类库

Maven 依赖调解 Dependency Mediation ,第一原则:路径最近者优先;第二原则:第一声明者优先

SNAPSHOT 快照版本只应该在组织内部的项目或者模块之间的依赖使用,组织对于这些快照版本的依赖具有完全的理解和控制权。项目不应该依赖于任何组织外部的快照版本,由于快照的不稳定性,依赖会产生潜在的危险,即使项目构建今天是成功的,由于外部快照版本可能变化,而导致未来构建失败。

Maven 核心概念 仓库

在上面介绍 Maven 的作用的时候提到了 Maven 的两个核心概念:坐标和依赖,这也是 Maven 首要解决的问题。这里要引入 Maven 另外一个核心概念:仓库。 Maven 时间中通过坐标来定位依赖,Maven 通过仓库来统一管理这些依赖。

Maven 项目的每一个构件对应着仓库的路径为: groupId/artifactId/version/artifactId-version.packageing

Maven 的仓库分为远程仓库和本地仓库,当第一次运行 Maven 命令时,需要网络连接,从远程仓库下载可用的依赖和插件。而当以后在运行 Maven 命令的时候,Maven 会自动先检查本地 ~/.m2/repository 目录下的依赖,如果本地有缓存优先从本地获取,在找不到的情况下去远程仓库寻找。

常用的在线 maven 依赖查看网站

Maven 核心概念 生命周期和插件

Maven 的生命周期是抽象的,实际行为都有插件完成。

clean 生命周期

清理项目,包含三个阶段

  • pre-clean 执行清理前需要完成的工作
  • clean 清理上一次构建生成的文件
  • post-clean 执行一次清理后需要完成的工作

default 生命周期

定义了真正构建时所需要执行的步骤

  • validate 验证工程是否正确,所有需要的资源是否可用。
  • initialize
  • generate-sources
  • process-sources 主要资源文件
  • generate-resources
  • process-resources
  • compile 编译主源码
  • process-classed
  • generate-test-sources
  • process-test-sources 测试资源
  • generate-test-resources
  • process-test-resources
  • test-compile 编译项目测试代码
  • process-test-classes 已发布的格式,如 jar,将已编译的源代码打包
  • prepare-package
  • pre-integration-test 在集成测试可以运行的环境中处理和发布包
  • post-integration-test
  • verify 运行任何检查,验证包是否有效且达到质量标准。
  • install 将包安装到 Maven 本地仓库,供本地 Maven 项目使用
  • deploy 复制到远程仓库,供其他人使用

site 生命周期

建立和发布项目站点

  • pre-site 生成项目站点之前需要完成的工作
  • site 生成站点文档
  • post-site 生成之后
  • site-deploy 生成的站点发布到服务器上

具体的流程可以参考下图

Maven 生命周期

Maven 项目文件结构

下面是一个标准的 Maven 工程

src/main/java - 存放项目.java 文件;
src/main/resources - 存放项目资源文件;
src/test/java - 存放测试类.java 文件;
src/test/resources - 存放测试资源文件;
target - 项目输出目录;
pom.xml - Maven 核心文件(Project Object Model);

Maven 常用命令

mvn archetype:create 创建 Maven 项目
mvn compile 编译源代码
mvn deploy 发布项目
mvn test-compile 编译测试源代码
mvn test 运行应用程序中的单元测试
mvn site 生成项目相关信息的网站
mvn clean 清除项目目录中的生成结果
mvn package 根据项目生成的 jar
mvn install 在本地 Repository 中安装 jar
mvn eclipse:eclipse 生成 eclipse 项目文件
mvn jetty:run 启动 jetty 服务
mvn tomcat:run 启动 tomcat 服务
mvn clean package -DMaven.test.skip=true 清除以前的包后重新打包,跳过测试类
mvn clean package 清除以前的包后重新打包

reference


2017-09-20 maven , java , build , management

IntelliJ IDEA 中使用 Resin 调试

平时开发环境使用的是 jetty,而 Java Web 有一个更好更快的服务器 Resin,这篇文章就来说一下什么是 Resin,以及在 Debug 中如何使用。

什么是 Resin

Resin是一个提供高性能的,支持 Java/PHP 的应用服务器。目前有两个版本:一个是GPL下的开源版本,提供给一些爱好者、开发人员和低流量网站使用;一种是收费的专业版本,增加了一些更加适用于生产环境的特性。

Resin也可以和许多其他的web服务器一起工作,比如Apache Server和IIS等。Resin支持Servlets 2.3标准和JSP 1.2标准。熟悉ASP和PHP的用户可以发现用Resin来进行JSP编程是件很容易的事情。Resin支持负载平衡,可以增加WEB站点的可靠性。方法是增加服务器的数量。比如一台Server的错误率是1%的话,那么支持负载平衡的两个Resin服务器就可以使错误率降到0.01%。到目前为止,Resin对WEB应用的支持已经远远超过Tomcat等各种大型的Server。

Resin 安装

在 Resin 的官方 quick start 教程中有各大平台详细的安装指导。我在使用 apt 安装时没有成功,这里就记录下手工安装的过程。

http://caucho.com/download 网址下载, Resin 有两个版本, Pro 版和GPL开源版,个人使用开源基础版本已经足够。安装过程如下:

  1. 安装JDK 6 或者以上版本,将 java 可执行程序配置到环境变量 JAVA_HOME
  2. tar -vzxf resin-4.0.x.tar.gz 根据下载的最新版本解压
  3. cd resin-
  4. ./configure 更多参数参考 configure options
  5. make
  6. sudo make install
  7. 执行 sudo resinctl start, 或者运行 java -jar lib/resin.jar start
  8. 浏览 http://localhost:8080

可以使用 sudo resinctl stop 来停止运行,更多内容可以参考官方指南

IntelliJ IEDA 中使用 Resin 调试

第一步,添加 Resin Local 选项,在 IDEA 中 Run/Debug Configuration 中添加 Resin Local 选项

resin-local

点击configure按钮,在弹出窗Application Servers中选择部分一中安装的Resin目录路径和目录下Resin的配置文件路径。

resin-configure

Run/Debug Configurations 中Server页面配置,基本都是默认。

Run/Debug Configurations 中Deployment页面配置,注意红色方框部分选择。选择resin.xml而不是JMX否则项目的index路径是localhost:8080/appname/ 而不是localhost:8080/

resin-deplyment

在Resin的服务器配置下 Depolyment中 Depolyment method:有 JMX 和resin.xml 两种选择, JMX 是把项目打包的文件放在resin 服务器下webapp下 只有在服务器启动时 才把项目给拷贝过去 无法在intellij中实时更新; resin.xml 是在C盘Temp 目录下 copy了一份resin.xml的配置文件 然后把服务器目录空间指向了你的项目工作空间 可以实现IntelliJ 修改实时更新。IntelliJ 默认的选择是JMX 所以我们要选中resin.xml模式。同时当项目 Artifacts 指向的目录是 ROOT 时 上图中的Use default context name(always true if depolyment method is JMX)取消勾选

执行build,得到war文件。执行resin run/debug,会自动在你选择的浏览器中打开项目index页面。也可以在IDEA下方的Application Servers面板中进行Resin的启动,停止等操作。Resin启动的打印信息也在此窗口显示。

run-resin


2017-09-19 Resin , IntelliJ , Java

每天学习一个命令:jstack 打印Java进程堆栈信息

Jstack 用于打印出给定的 java 进程 ID 或 core file 或远程调试服务的 Java 堆栈信息.

Prints Java thread stack traces for a Java process, core file, or remote debug server. This command is experimental and unsupported.

如果 java 程序崩溃生成 core 文件,jstack 工具可以用来获得 core 文件的 java stack 和 native stack 的信息,从而可以轻松地知道 java 程序是如何崩溃和在程序何处发生问题。另外,jstack 工具还可以附属到正在运行的 java 程序中,看到当时运行的 java 程序的 java stack 和native stack 的信息, 如果运行的 java 程序呈现 hung 的状态,jstack 是非常有用的。

thread dump 就是将当前时刻正在运行的 JVM 的线程拷贝一份,可以用来分析程序执行情况。

用法

打印某个进程的堆栈信息

jstack [PID]

jstack -l [PID]
jstack -m [PID]

关于如何找到PID,有很多方法,使用 jps -v 或者 ps -aux 或者 htop 等等方法都可以。

分析

在执行 jstack -l [PID] > /tmp/output.txt 之后可以对 /tmp/output.txt 进行分析

开头交代当前 dump 的时间和 JVM 基本信息

2018-05-24 14:41:06
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.101-b13 mixed mode):

接下来就是程序的线程信息

"resin-8015" #8015 daemon prio=5 os_prio=0 tid=0x00007f991820a800 nid=0x25e65 waiting on condition [0x00007f96b1b39000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)
        at com.caucho.env.thread2.ResinThread2.park(ResinThread2.java:196)
        at com.caucho.env.thread2.ResinThread2.runTasks(ResinThread2.java:147)
        at com.caucho.env.thread2.ResinThread2.run(ResinThread2.java:118)

   Locked ownable synchronizers:
        - None

线程的状态 Thread Life States

  • alive 通常运行时状态
  • not started 线程已经被 java.lang.Thread.start() 请求运行,但是实际上操作系统还没有开始
  • terminated 线程已经结束 run() 并且通知其他线程 joining

线程运行状态 Thread Run States

  • blocked 该线程尝试进入一个被其他线程占用的 synchronized 块,当前线程直到锁被释放之前一直都是 blocked 状态
  • blocked (on thin lock) 和 blocked 相同的状态,但是锁需要是 thin lock
  • waiting 当前线程调用了对象的 Object.wait() ,当前线程会保持该状态直到其他线程发送通知到该对象
  • sleeping 当前线程调用了 java.lang.Thread.sleep()
  • parked 当前线程调用了 java.util.concurrent.locks.LockSupport.park()
  • suspended 当前线程的执行被 java.lang.Thread.suspend() 暂停,或者有 JVMTI/JVMPI agent 调用

以上内容来自 Oracle

通过 jstack 信息可以分析线程死锁,或者系统瓶颈,但是这篇文章比较粗浅,只介绍了大概,等以后熟悉了补上。

reference


2017-09-14 jstack , java , debug , linux , thread-dump

Spring 中 HandlerMethodArgumentResolver 使用

HandlerMethodArgumentResolver 是一个接口:

public interface HandlerMethodArgumentResolver {
    boolean supportsParameter(MethodParameter var1);

    @Nullable
    Object resolveArgument(MethodParameter var1, @Nullable ModelAndViewContainer var2, NativeWebRequest var3, @Nullable WebDataBinderFactory var4) throws Exception;
}

HandlerMethodArgumentResolver 的接口定义如下:

  • supportsParameter() 用于判断是否支持对某种参数的解析
  • resolveArgument() 当判断支持后将请求中的参数值进行相应的转换

关于 HandlerMethodArgumentResolver 执行流程,大部分可以在类 InvocableHandlerMethod 中看到

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
    // 获取 Controller 中函数的参数对象
    Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
    if (this.logger.isTraceEnabled()) {
        this.logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(this.getMethod(), this.getBeanType()) + "' with arguments " + Arrays.toString(args));
    }

    Object returnValue = this.doInvoke(args);
    if (this.logger.isTraceEnabled()) {
        this.logger.trace("Method [" + ClassUtils.getQualifiedMethodName(this.getMethod(), this.getBeanType()) + "] returned [" + returnValue + "]");
    }

    return returnValue;
}

private Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
    // 获取执行的具体函数的参数
    MethodParameter[] parameters = this.getMethodParameters();
    Object[] args = new Object[parameters.length];

    for(int i = 0; i < parameters.length; ++i) {
        MethodParameter parameter = parameters[i];
        parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
        args[i] = this.resolveProvidedArgument(parameter, providedArgs);
        if (args[i] == null) {
            // 首先判断是否有参数解析器支持参数 parameter,采用职责链的设计模式
            if (this.argumentResolvers.supportsParameter(parameter)) {
                try {
                    // 如果参数解析器支持解析参数 parameter,那么解析参数成 Controller 的函数需要的格式
                    args[i] = this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
                } catch (Exception var9) {
                    if (this.logger.isDebugEnabled()) {
                        this.logger.debug(this.getArgumentResolutionErrorMessage("Failed to resolve", i), var9);
                    }

                    throw var9;
                }
            } else if (args[i] == null) {
                throw new IllegalStateException("Could not resolve method parameter at index " + parameter.getParameterIndex() + " in " + parameter.getExecutable().toGenericString() + ": " + this.getArgumentResolutionErrorMessage("No suitable resolver for", i));
            }
        }
    }

    return args;
}

外延

解析请求头及参数 AbstractNamedValueMethodArgumentResolver

AbstractNamedValueMethodArgumentResolver 抽象类主要用来解析方法参数 (resolving method arguments from a named value),请求参数,请求头,path 变量都是 named value 的例子。他下面有很多的子类

AbstractCookieValueMethodArgumentResolver
ExpressionValueMethodArgumentResolver
MatrixVariableMethodArgumentResolver
PathVariableMethodArgumentResolver
RequestAttributeMethodArgumentResolver
RequestHeaderMethodArgumentResolver
RequestParamMethodArgumentResolver
ServletCookieValueMethodArgumentResolver
SessionAttributeMethodArgumentResolver

PathVariableMethodArgumentResolver

PathVariableMethodArgumentResolver 用来解析注解 @PathVariable 的参数。

@PathVariable 用来解析 URI 中的 Path 变量,Path 变量是必须的,并且没有默认值。

RequestParamMethodArgumentResolver

用来将请求参数解析到注解 @RequestParam 修饰的方法参数中。

比如请求 https://localhost:8080/hello?uid=1234

@RequestMapping(value="/hello",method=RequestMethod.POST)
@ResponseBody
public Map<String,Object> test(@RequestParam(value = "uid", required = true, defaultValue = "1") String uid){
    return m;
}

其中将请求的 uid 值解析到方法参数 uid 就是通过 RequestParamMethodArgumentResolver 来实现的。

解析请求体 AbstractMessageConverterMethodArgumentResolver

AbstractMessageConverterMethodArgumentResolver 是一个抽象类,主要用来将请求 body 中的内容解析到方法参数中,其子类:

  • AbstractMessageConverterMethodProcessor
  • HttpEntityMethodProcessor
  • RequestPartMethodArgumentResolver
  • RequestResponseBodyMethodProcessor

代码片段

public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {
    protected final List<HttpMessageConverter<?>> messageConverters;
    protected final List<MediaType> allSupportedMediaTypes;
}

AbstractMessageConverterMethodProcessor 的子类 RequestResponseBodyMethodProcessor:支持 @RequestBody@ResponseBody,使用举例:

@RequestMapping(value="/hello/test",method=RequestMethod.POST)
@ResponseBody
public Map<String,Object> test(@RequestBody Map<String,Object> m){
    return m;
}

reference

  • Spring doc

2017-09-10 spring , resolver , spring-boot

使用 antigen 来管理 zsh 插件

antigen 是 zsh 的插件管理工具,在他 GitHub 主页上的一句话非常形象的解释了他的功能。

Antigen is to zsh, what Vundle is to vim.

安装

curl -L git.io/antigen > antigen.zsh

或者

apt-get install zsh-antigen

配置

如果使用过 Vim 的 Vundle 对 antigen 的配置应该不陌生。

``` source /path-to-antigen-clone/antigen.zsh

# Load the oh-my-zsh's library.
antigen use oh-my-zsh

# Bundles from the default repo (robbyrussell's oh-my-zsh).
antigen bundle git
antigen bundle heroku
antigen bundle pip
antigen bundle lein
antigen bundle command-not-found

# Syntax highlighting bundle.
antigen bundle zsh-users/zsh-syntax-highlighting

# Load the theme.
antigen theme robbyrussell

# Tell antigen that you're done.
antigen apply ```

使配置生效 source ~/.zshrc

可以从这个页面 查看更多的插件。

reference


2017-09-07 antigen , zsh , bash , linux , vim , tmux

使用Shell命令来对 Unix 时间戳和日期进行转换 date 命令

在程序中经常要使用到 Unix timestamp 和日期的转换,通常情况下都是Google一个时间戳转换的网页在进行转换,其实 Linux 命令中就有能够快速实现转换的命令。主要都是集中在 date 这个命令。date 命令主要用于显示或设定系统时间和日期。

修改系统的时区

Linux 用来修正系统的时区

sudo dpkg-reconfigure tzdata

选择 Asia > Shanghai

date 常用命令

获取当前的 unix timestamp

date +%s    # 返回 11 位时间戳,%s 表示从 1970-01-01 0点(epoch开始的秒数
date +%s%3N # 返回 14 位时间戳,毫秒
date +%s%N  # 返回 11 + 9 位纳秒

将时间戳转换成日期

$ date +%s
1504516338
$ date -d @1504516338
Mon Sep  4 17:12:18 CST 2017

将 string 日期转成日期

使用 -d 参数可以用来将输入 String 转成特定格式日期,如果不指定具体时间,date会使用 00:00:00

$ date -d "06/04/1989"
Sun Jun  4 00:00:00 CDT 1989
$ date -d "04 June 1989"
Sun Jun  4 00:00:00 CDT 1989
$ date -d "June 04 1989"
Sun Jun  4 00:00:00 CDT 1989
$ date -d "June 04 1989 12:01:01"
Sun Jun  4 12:01:01 CDT 1989

-d 选项也有一些其他很强大的功能,比如

$ date -d '5 minutes ago' # 5 分钟前的时间
Mon Sep  4 17:22:58 CST 2017
$ date -d '100 days'      # 100 天以后的日期
Wed Dec 13 17:29:14 CST 2017
$ date -d '-100 days'     # 100 天以前的日子
Sat May 27 17:30:01 CST 2017
$ date -d '100 days ago'  # 同上
Sat May 27 17:31:10 CST 2017
$ date -d 'next monday'    
Mon Sep 11 00:00:00 CST 2017

格式化参数

可以使用+来输出不同格式

date +%<format options>

比如

$ date '+%Y-%m-%d %H:%M:%S'
2017-09-04 17:38:46
Format options Purpose of Option Output
date +%a 缩略方式显示星期 (like Mon, Tue, Wed) Thu
date +%A 全称显示星期(like Monday, Tuesday) Thursday
date +%b Displays Month name in short (like Jan, Feb, Mar ) Feb
date +%B Displays Month name in full short (like January, February) February
date +%d Displays Day of month (e.g., 01) 07
date +%D Displays Current Date; shown in MM/DD/YY 02/07/13
date +%F Displays Date; shown in YYYY-MM-DD 2013-02-07
date +%H Displays hour in (00..23) format 23
date +%I Displays hour (01..12) format 11
date +%j Displays day of year (001..366) 038
date +%m Displays month (01..12) 02
date +%M Displays minute (00..59) 44
date +%S Displays second (00..60) 17
date +%N Displays nanoseconds (000000000..999999999) 573587606
date +%T Displays time; shown as HH:MM:SS Note: Hours in 24 Format 23:44:17
date +%u Displays day of week (1..7); 1 is Monday 4
date +%U Displays week number of year, with Sunday as first day of week (00..53) 05
date +%Y Displays full year i.e. YYYY 2013
date +%Z alphabetic time zone abbreviation (e.g., EDT) IS

reference


2017-09-04 shell , linux

Mastering the Vim

我已经用了很长一段时间 Vim 了,但是 Vim 最可贵之处便在于你永远达不到 Vim 的天花板,在使用的过程中我永远会发现操作 Vim 的其他便捷方法。最近看了一个关于 Vim 的讲座 ,革新我对 Vim 命令的认识。可以说掌握这样的一个世界观可以对 Vim 的操作上到另外一个层次。下面就总结一下这个视频中的精髓内容。

Text Objects and motions

@ChrisToomey 定义了一种 Vim Language,Vim 的语法由数词 + 动词 + 名词 组成,比如:

d 删除
w 单词
将两个字母组合起来就是 删除单词

这个经常使用的命令非常容易记住。如果想要删除三个单词,则是 3dw,所以可以总结出

{number}{command}{text-object}

这样的形式

  • number 是数量
  • command 是动作
  • text-object 是对象

重复和撤销,相信使用过一段时间 Vim 的人应该会知道 . 表示重复上一次命令, u 表示撤销上一次操作。而重复和撤销是针对命令而不是针对输入的,因此每使用一次 . 或者 u 都是命令级别。因此这就给予了 . 操作非常强大的功能。

Verbs: 常用的动作举例

d Delete
c Change  delete and enter insert mode
y yank
>  intend 缩进
v 选择

Nouns: 常见的动作 Motion

w 移动到下一个 word 开始
e 移动到下一个 word 的结尾
b 移动到上一个 word 的开始 back
2j   向下移动 2 lines

Vim 中定义了很多移动操作

基于内容 Nouns: Text Objects

w => words 表示移动一个单词
s => sentences 移动一个句子
p => paragraphs 向下移动一个段落
t => tags  (html xml)

动作 motions

a => all
i => in
t => 'till
f => find forward
F => find backward

iw => inner word
it => inner tag
i" => inner quotes
ip => inner paragraph
as => a sentence

命令 commands

d => delete(also cut)
c => change(delete, then into insert mode)
y => yank (copy)
v => visual select

组合举例

diw  delete in word,即使光标在 word 中也能够快速删除 word
yi)  yank all text inside the parentheses,光标在 `()` 中复制括号中的所有内容

上面的 Text Object

{command}{text object or motion}

在单词中间,diw 删除光标下的单词,dit 删除光标下 tag 中的内容

Nouns: Parameterized Text Objects

f,F => find the next character
t,T => find till
/,?   => search

比如有一行文本

the program print ("Hello, World!")

如果想要删除 Hello 前面的内容,可以在行首使用 dtH , 解读这个命令可以理解为 d => delete unti H 从这里删除直到遇到 H。典型的 verb + noun 组合。

记住动作加移动,能够很快的记忆很多命令。

比如使用 https://github.com/tpope/vim-surround 这个插件,可以轻松的实现,使用命令 cs"' 来将

"hello world"

变成

'hello world'

命令的语义解释就是 change surroundding double quote to single quote 将包围的双引号变成单引号

使用 cs'<p> 将单引号变成 <p>

<p>hello world</p>

使用 cst" 将 surrounding 变成双引号

"hello world"

或者可以使用 ds" 来将 surrounding 双引号删除 delete surrounding “

hello world

DOT command

Vim 中的 “.” 命令,以命令为单位,重复上一个命令。

Sublime , IntelliJ IDEA 中经常被人提及的 multiple cursor 的功能,能够在编辑器中提供多个光标一起编辑,其实 Vim 不需要多光标就能够做到,结合强大的 .n . 可以快速的编辑大量重复的内容。

比如在 Vim 中的 workflow 就是

  • 使用 /pattern 来所有需要编辑的内容
  • 使用可编辑的 edit,比如 cw 当前单词 ESC 退出,一个完成的动作
  • 使用 n 来找到下一个匹配的地点
  • 使用 . 来重复编辑操作,可以直接将单词替换为上一个动作编辑
  • 然后重复上面两个步骤,整个文件即可替换完成

macro

Vim 允许记录一个宏来完成一组命令

qa                  # 将命令记录到寄存器 a 中
q                   # 再次 q 结束记录
@a                  # 使用寄存器
@@                  # 使用上一次寄存器

refernence


2017-09-03 vim , linux , editor

Raspberry pi 自动挂载NTFS USB设备

一些相关的命令

sudo fdisk -l    # 列出磁盘分区表

结果是这样的:

Disk /dev/ram0: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes


Disk /dev/ram1: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes


Disk /dev/ram2: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes


Disk /dev/ram3: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes


Disk /dev/ram4: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes


Disk /dev/ram5: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes


Disk /dev/ram6: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes


Disk /dev/ram7: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes


Disk /dev/ram8: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes


Disk /dev/ram9: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes


Disk /dev/ram10: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes


Disk /dev/ram11: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes


Disk /dev/ram12: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes


Disk /dev/ram13: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes


Disk /dev/ram14: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes


Disk /dev/ram15: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes


Disk /dev/mmcblk0: 7.4 GiB, 7948206080 bytes, 15523840 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x1fdbda7f

Device         Boot Start      End  Sectors  Size Id Type
/dev/mmcblk0p1       8192    93813    85622 41.8M  c W95 FAT32 (LBA)
/dev/mmcblk0p2      94208 15523839 15429632  7.4G 83 Linux


Disk /dev/sda: 1.4 TiB, 1500301909504 bytes, 2930277167 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x4c63688c

Device     Boot Start        End    Sectors  Size Id Type
/dev/sda1        2048 2930272255 2930270208  1.4T  7 HPFS/NTFS/exFAT

在最后可以看到一块磁盘 /dev/sda1

然后可以使用如下的方式手动挂载。

手动挂载

为了让 Linux 能够读取 NTFS 格式的硬盘,需要安装 ntfs-3g 。然后可以手动挂载。

sudo apt-get install ntfs-3g
sudo mkdir -p /media/sda1
sudo mount -t ntfs-3g /dev/sda1 /media/sda1

挂载完成后可以查看

sudo df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/root       7.3G  2.2G  4.8G  31% /
devtmpfs        460M     0  460M   0% /dev
tmpfs           464M     0  464M   0% /dev/shm
tmpfs           464M   13M  452M   3% /run
tmpfs           5.0M  4.0K  5.0M   1% /run/lock
tmpfs           464M     0  464M   0% /sys/fs/cgroup
/dev/mmcblk0p1   42M   21M   21M  51% /boot
tmpfs            93M     0   93M   0% /run/user/1000
/dev/sda1       1.4T  1.1T  363G  75% /media/sda1

通过编辑fstab来让系统自动挂载

sudo vim /etc/fstab

插入

/dev/sda1 /mnt/hdd ext4 defaults 0 0

从而实现USB设备的自动挂载

sudo vim /etc/udev/rules.d/10-usbstorage.rules

KERNEL!="sd*", GOTO="media_by_label_auto_mount_end"
SUBSYSTEM!="block",GOTO="media_by_label_auto_mount_end"
IMPORT{program}="/sbin/blkid -o udev -p %N"
ENV{ID_FS_TYPE}=="", GOTO="media_by_label_auto_mount_end"
ENV{ID_FS_LABEL}!="", ENV{dir_name}="%E{ID_FS_LABEL}"
ENV{ID_FS_LABEL}=="", ENV{dir_name}="Untitled-%k"
ACTION=="add", ENV{mount_options}="relatime,sync"
ACTION=="add", ENV{ID_FS_TYPE}=="vfat", ENV{mount_options}="iocharset=utf8,umask=000"
ACTION=="add", ENV{ID_FS_TYPE}=="ntfs", ENV{mount_options}="iocharset=utf8,umask=000"
ACTION=="add", RUN+="/bin/mkdir -p /media/%E{dir_name}", RUN+="/bin/mount -o $env{mount_options} /dev/%k /media/%E{dir_name}"
ACTION=="remove", ENV{dir_name}!="", RUN+="/bin/umount -l /media/%E{dir_name}", RUN+="/bin/rmdir /media/%E{dir_name}" 
LABEL="media_by_label_auto_mount_end"

2017-09-02 linux , raspberrypi

Spring MVC 应用处理 CORS

什么是跨域或者说什么是CORS(Cross-origin resource sharing),中文叫”跨域资源共享”。在了解 CORS 之前首先要知道“同源策略”,出于安全考虑,浏览器会限制Ajax中发起的跨站请求。比如,使用 XMLHttpRequest 对象发起 HTTP 请求就必须遵守同源策略(same-origin policy),”同源策略“是浏览器安全的基石。具体而言,Web 应用程序能且只能使用 XMLHttpRequest 对象向其加载的源域名发起 HTTP 请求,而不能向任何其它域名发起请求。阮一峰写的一篇关于 CORS 的文章 介绍得非常详细,这里主要记录一下重点以及 Spring MVC 中如何处理 CORS。

CORS 做到了不破坏即有规则,只要服务端实现了 CORS 接口,就可以跨源通信。

简单请求 VS 非简单请求处理

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

需要同时满足以下两大条件,才属于简单请求。

  • 请求方法仅仅为以下三种方法之一:

    HEAD、GET、POST

  • HTTP的头信息不超出以下几种字段:

    Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

简单请求处理

Response Header 选项
Access-Control-Allow-Origin 必须,值要么是请求的 Origin,要么是 * ,表示接受所有域名请求
Access-Control-Allow-Credentials 该字段可选。它的值是一个布尔值,表示是否允许客户端发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。
Access-Control-Expose-Headers 该字段可选。扩展客户端能够访问的字段。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader(‘FooBar’)可以返回FooBar字段的值。

简单请求的处理过程可以参考下图:

简单请求流程

对于简单请求,CORS 的策略是请求时,在头信息中添加一个 Origin 字段,服务器收到请求后,根据该字段判断是否允许该请求。

  • 如果允许,则在 HTTP 头信息中添加 Access-Control-Allow-Origin 字段,并返回正确的结果
  • 如果不允许,则不在头信息中添加 Access-Control-Allow-Origin 字段。

浏览器先于用户得到返回结果,根据有无 Access-Control-Allow-Origin 字段来决定是否拦截该返回结果。

script 或者 image 标签触发的 GET 请求不包含 Origin 头,所以不受到 CORS 的限制,依旧可用。如果是 Ajax 请求,HTTP 头信息中会包含 Origin 字段,由于服务器没有做任何配置,所以返回结果不会包含 Access-Control-Allow-Origin,因此返回结果会被浏览器拦截,接口依旧不可以被 Ajax 跨源访问。

非简单请求

而对于真正实现中的请求,可能会使用 Content-Type:application/json,也有可能有自定义 Header,所以了解非简单请求的处理也非常必要。

对于 Content-Typeapplication/json 的特殊请求,需要服务端特殊对待的请求,在正式通信前会增加一次“预检”请求(preflight)。浏览器会先询问服务器,当前网页所在的域名是否在服务器的许可名单,以及可以使用哪些HTTP动词和头信息,得到服务端回复才会发出正式的请求,否则报错。

CORS 请求相关 Header

Request Header value
Access-Control-Request-Method 真实请求使用的 HTTP 方法
Access-Control-Request-Headers 真实请求包含的自定义 Header

在服务端收到客户端发出的预检请求后,校验 OriginAccess-Control-Request-MethodAccess-Control-Request-Headers,通过校验后在返回中加入如下的header:

Response Header value
Access-Control-Allow-Methods 必须,值为逗号分割的字符串,表明服务器支持的所有跨域请求方法,返回的是所有支持的方法,为了避免多次“预检”请求。
Access-Control-Allow-Headers 如果浏览器请求包括 Access-Control-Request-Headers 字段,则 Access-Control-Allow-Headers 字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在”预检”中请求的字段。
Access-Control-Allow-Credentials 与简单请求含义相同
Access-Control-Max-Age 该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。

一旦服务器通过了”预检”请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

非简单请求流程

Spring 处理跨域

这里主要针对 Spring 3.x 来处理, 在 Spring 4.2 之后官方引入了 @CrossOrigin 注解,处理 CORS 变的非常方便。所以接下来就记录下 3.x 中的处理方法。

更新 web.xml

更新 web.xml 让 Spring 开启 OPTIONS 处理.

<servlet>    
   <servlet-name>application</servlet-name>    
   <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
   <init-param>  
		<param-name>dispatchOptionsRequest</param-name>  
		<param-value>true</param-value>  
   </init-param>    
   <load-on-startup>1</load-on-startup>    
</servlet>    

使用 Interceptor

public class CorsInterceptor extends HandlerInterceptorAdapter {

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		addOriginHeader(request, response);
		if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
			response.setStatus(200);
			return false;
		}	
		return true;
	}

	private void addOriginHeader(HttpServletRequest request, HttpServletResponse response) {
		String origin = request.getHeader("Origin");
		response.addHeader("Access-Control-Allow-Origin", origin);
		response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type");
		response.addHeader("Access-Control-Allow-Credentials", "true");         // 可选,是否允许Cookie
		response.addHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
		response.addHeader("Access-Control-Max-Age", "1728000");
	}
}

在 XML 中配置 Interceptor

然后在 Controller 中

@RequestMapping(value = "/test/hello", method = {RequestMethod.GET, RequestMethod.OPTIONS})

然后OK


2017-09-01 Spring , CORS , JS , Web , HTTP , 跨域,

电子书

Google+

最近文章

  • 使用 alembic 迁移数据库结构 Alembic 是一个处理数据库更改的工具,它利用 SQLAlchemy 来实现形成迁移。 因为 SQLAlchemy 只会在我们使用时根据 metadata create_all 方法来创建缺少的表 ,它不会根据我们对代码的修改而更新数据库表中的列。它也不会自动帮助我们删除表。 Alembic 提供了一种更新 / 删除表,更改列名和添加新约束的方法。因为 Alembic 使用 SQLAlchemy 执行迁移,它们可用于各种后端数据库。
  • 每天学习一个命令:iotop 查看 Linux 下每个进程 IO 占用 iotop 是一个用来监控磁盘 I/O 的类似 top 的工具,Linux 下 IO 统计工具,比如 iostat, nmon 等只能统计到每个设备的读写情况,如果想要知道哪一个进程占用比较高的 IO 就要使用 iotop。 iotop 使用 Python 语言编写,要求 Python >= 2.5,Linux Kernel >= 2.6.20.
  • 修正关于 HTTP Header 的错误认识 HTTP 请求的 Header 是不区分大小写的!,一直以为 HTTP 请求的请求头是有区分大小的,知道今天调试发现 Spring 将 header 全部处理成小写,然后有人提了 Bug 58464 然后看到 Stackoverflow 上面有人回答。
  • 解决 failed to create bus connection no such file or directory 错误 今天在修改 hostname 使用 sudo hostnamectl set-hostname ds 命令时遇到问题:
  • Vim 的颜色主题 Retro groove color scheme for Vim