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>    

添加Header

使用 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 , 跨域,

爬虫相关材料整理

这篇文章用来收集整理爬虫相关的资料。

相关技术

如果只想单纯的自己开发,可以使用 Python + Celery + Redis/MySQL 基本能满足 80% 的需求。

如果想要选用框架 Scrapy,pyspider,等等都是非常不错的选择,我甚至在 GitHub 上看到过 Java 的分布式爬虫。

书籍

Python 3 网络爬虫开发实战

这本书在网上有部分 gitbook,链接在这里

网上公开的部分都是无关痛痒的部分,不过提及的工具倒是可以参考一下。大部分我之前的文章也都有提及


2017-08-29 collection , spider , crawler , python , redis , mysql

树莓派系统安装及设置

树莓派官网有很多系统可以选择,我选了官方维护的 Raspbian 基于 Debian 的衍生版,主要是熟悉他的 APT 包管理,看评价三方维护的 Snappy Ubuntu Core 换用了其他的 snap 的管理,不是很了解,所以还是选择了 Raspbian。

系统安装

官网提供的教程非常方便, 采用开源的镜像烧录工具 Etcher 非常方便的就可以在三大平台上完成镜像到 SD 的烧录。当然如果熟悉各个平台的工具也可以自己手动完成烧制。

启动系统

在将系统写入 microSD 卡之后,将卡插入树莓派板子,启动树莓派,开机即可,可以用 HDMI 接口连接显示器,用一个外接键盘来输入。树莓派的默认用户名是: pi ,默认密码为: raspberry

root 账户

使用如下命令给 root 账户设置密码并允许登录

sudo passwd root
# 然后输入密码
# 用同样的方式修改默认账户 pi 的密码
sudo passwd pi

启用 SSH

raspbian 自带 SSH ,启动

sudo service ssh start

其他配置

raspi-config

运行该命令可以看到一系列的选项,比如修改 Hostname ,修改密码等等选项,可以配置一下。

更换 sources.list

换用清华的源 : https://mirror.tuna.tsinghua.edu.cn/help/raspbian/

必备应用

apt install vim
apt install samba samba-common-bin
apt install zsh
apt-get install nginx
sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
chsh -s /bin/zsh
apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev \
libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev \
xz-utils tk-dev
curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash
apt install mysql-server
apt-get install libmysqlclient-dev

2017-08-26 Raspberrypi , linux

Redis 安全性检查

Redis 在设计上,是用来被可信客户端访问的,也就意味着不适合暴露给外部环境非可信客户端访问。

最佳的实践方法是在 Redis 前增加一个访问控制层,用于校验用户请求。

基本配置

Redis 本身提供了一些简单的配置以满足基本的安全控制。

  • IP 绑定。如果不需要直接对外提供服务,bind 127.0.0.1 就行了,切忌 bind 0.0.0.0
  • 端口设置。修改默认的 6379,一定程度上避免被扫描。
  • 设置密码。Redis 的密码是通过 requirepass 以明文的形式配置在 conf 文件里的,所以要尽可能得长和复杂,降低被破解的风险。因为 redis 非常快,外部环境可以在一秒内 150k 次暴力破解,所以配置密码一定要复杂。
  • 重命名或禁用某些高危操作命令。向 config、flushall、flushdb 这些操作都是很关键的,不小心就会导致数据库不可用。可以在配置文件中通过 rename-command 重命名或禁用这些命令。

网络配置

对于直接暴露在互联网的 Redis,应该使用防火墙阻止外部访问 Redis 端口。客户端应该只通过回环接口访问 Redis。

在 redis.conf 文件添加

bind 127.0.0.1

由于 Redis 设计的初衷,如果不能成功阻止外部访问 Redis 端口,会有很大的安全影响。外部攻击者使用一个 FLUSHALL 命令就可以删除整个数据集。

使用密码

虽然 Redis 没有实现访问控制,但是提供了一个简单的身份验证功能。

在配置文件中修改:

requirepass mypassword

重启 redis

sudo service redis-server restart

登录验证

./redis-cli -h 127.0.0.1 -p 6379 -a mypassword
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "mypassword"

如上输出,配置正确。也可以在连接之后使用 auth 验证

./redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> auth mypassword
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "mypassword"

通过修改 server 的 conf 文件方式需要重启 server, 也可以通过客户端来修改密码

127.0.0.1:6379> config set requirepass mypassword
OK

使用客户端设置的密码,Redis 重启之后还会使用 redis.conf 配置文件中的密码。

设置密码之后 Redis 集群中 slave 中也需要配置 和 master 一样的密码

masterauth master-password

身份验证是一个可选的冗余层,如果防火墙或者其他保护 Redis 安全的系统被攻破,对于不知道授权密码的情况,攻击者依然不能访问 Redis。

auth 命令是明文传输的,所以依然不能阻止那些获得网络访问权限的攻击者嗅探。

reference


2017-08-25 redis , database , nosql , security , key-value , db

Linux 主机在线监控

很久没有更新这个分类下的文章了,其实一直在体验不同的产品,只是真的很少有能拿出来讲一下的东西。不管是硬件还是软件,最近几年使用的东西越来越狭窄,越来越收缩,当然对于某一个特定的需求,总有一个产品能够占领绝大多数市场,而也有部分产品能够瓜分小众市场。这里要介绍的这个 NodeQuery 就不是一个大而全的产品,而是一个很精细的小众产品。我用它也一年多了,我的需求很简单,能够实时监控我的 VPS,能够在宕机或者高负载时报警。NodeQuery 完全能够满足我的需求。

用 NodeQuery 自己的话描述自己就是:”一个轻量、易用的 Linux 服务器监控服务”.

NodeQuery provides a lightweight and easy to use Linux server monitoring service.

NodeQuery 免费账户可以提供 10 台机器的监控,

官网地址: https://nodequery.com/

界面展示

index

details

使用

同样使用也非常方便,新建 Server,然后会给出一个一键脚本,在自己的 VPS 上一键安装就行,脚本同样是开源的托管在 GitHub 上。人人都可以审查。

API

这个网站也能够提供 API 支持,能够读取历史情况下 Server 的状态,目前写功能暂时还无法使用。

reminder

不过需要提醒的是,这个网站自从 2014 年起就再没有更新,不清楚背后发生了什么事情,但是也是感到非常的可惜。


2017-08-23 linux , vps , monitor

Python 笔记之内存模型 Variables Objects and References 区别

许多使用静态语言比如 C、 C++ 或者 Java 的人,在转到 Python 的时候可能第一个会疑惑的就是 Python 不需要显示的指定类型,那么 Python 是怎么知道变量的类型呢?

变量创建流程

在 Python 中,变量的创建遵循着一个非常合理的方式,拿 a=3 来举例子:

  • 变量创建

    一个变量(名字)比如 a ,当第一次被赋值时被创建。

  • 变量类型 Variable Types

    一个变量永远不会有任何类型信息或者约束,类型的概念和 Object 关联,而不是变量名字。变量都是通用的(泛型),变量总是在特定时间指向一个特定的 Object 。

  • 变量使用 Variable use

    当变量出现在表达式中,他会立即使用当前指向的 Object 替换。因此,所有的变量在使用之前都必须显式的被赋值,当使用未被赋值的变量时会产生错误。

总结来说,变量会在赋值时被创建,并且能够指向任何类型的对象,在引用前必须已经被赋值。这就是动态类型模型和传统静态语言最显著的差别。

所以对于 a=3 Python 会执行三个完全不同的步骤来完成,下面的步骤显示了 Python 语言中所有赋值会执行的步骤:

  1. 创建一个对象来表示 3
  2. 如果变量不存在,则创建一个变量 a
  3. 将变量 a 连接到对象 3

变量和对象会分别保存到不同的内存中。变量永远会指向 Objects,永远不会指向其他变量,但是大型的 Objects 可能会有指向其他 Objects 的链接(比如 list 对象就可能会有很多指向其他对象的链接)。

在 Python 中,这些从变量指向对象的链接被称为 引用 references ,也就是说引用是一种关联,在内存中实现为指针。每当一个变量被使用, Python 会自动跟随着 variable to object 链接。

所以这些术语理解起来要简单得多,具体来讲:

  • Variables 变量是 system table 的记录,使用空间来记录指向 Objects 的链接
  • Objects 对象是真正分配内存的,使用足够的空间来表示他们真的值
  • References 引用 会自动跟随着指针从变量到对象

概念上讲,每一次创建新的值, Python 都会创建新的对象(开辟内存空间)来表示值。内部来说,作为优化,Python 会使用缓存重用一些特定的不会改变的对象,比如比较小的 integers,strings,比如 0 并不会单独每一次都开辟内存空间,而是使用缓存。但是从逻辑上,每一个表达式的结果都会为不同的对象,每一个对象都有自己的内存空间。

每一个对象都有两个标准的 header fields:type designator 用来表明对象的类型,reference counter 来记录引用次数,何时对象应该被回收。

Types live with Objects, Not Variables

对象回收

当对同一个变量重复赋值,那么变量之前指向的 Object 会发生什么?

a = 3
a = 'Spam'

当 a 被重新赋值为 Spam 时,对象 (3) 会发生什么。在 Python 中,当一个变量名被赋值到新对象时,之前对象的空间会被回收(也就是说当当一个对象不再被变量或者其他对象引用时会被回收)。这种自动回收对象空间的机制被称为 垃圾回收 (garbage collection)。

内部实现来说,Python 通过在每一个 Object 中存储一个 counter,来追踪当前对象被引用的次数来实现垃圾回收。一旦引用计数变为 0,对象的内存空间就会被自动回收。

Shared References

上面的内容都是单一变量,如果出现变量赋值比如

a = 3
b = a

这个时候 Python 会怎么处理呢? 当变量 b 被创建时,会创建一个从 b 指向对象 3 的引用。这个有多个变量名字指向相同的对象的场景,被称为 shared reference

在上面的基础上,如果

a = 'spam'

a 被重新赋值 spam 这个时候不会影响 b 指向的对象 3.

如果

a = a + 2

同样不会影响 b 的值,对象 integer 5 会作为加号的结果放到新的内存空间,是一个新的对象,所以也不会改变 b 的值。事实上,也没有任何方法可以改变对象 3 的值,就像之前关于 Python 类型一文 中说的那样,integer 是不可变类型,因此不能原地修改其内容。

共享引用和原地修改

当使用可变对象,比如 list 时,如果存在共享引用,要特别注意,当修改其中一个引用的对象的值时,会影响其他指向这个对象的引用。

L1 = [2,3,4]
L2 = L1
L1[0] = 'spam'

这个时候 L1,L2 变量指向的对象值都被改变了。

如果需要深度拷贝 list 时,就需要特别注意,不要使用引用。

L1 = [2,3,4]
L2 = L1[:]             # 创建了一个 L1 的拷贝
L1[0] = 'spam'          # 此时再修改 L1 则不会对 L2 造成影响

对 L1 的修改不会影响 L2 ,因为 L2 指向和 L1 完全不同的内存区域。

import copy
X = copy.copy(Y)            # make top-level "shadow" copy of any object Y
X = copy.deepcopy(Y)        # make deep copy of any object Y: copy all nested parts

判别相等

因为 Python 的引用模型,所有有两种完全不同的判断相等的方法

L = [1,2,3]
M = L
L == M                      # Same 这时候判断的是引用是否相同,判定值是否相同
L is M                      # Same is 操作符判断是指向的对象是否相同,更强的相等判断

更比如说

L = [1,2,3]
M = [1,2,3]
L == M                      # Same 相同的内容
L is M                      # False 不同的对象

再比如

X = 42
Y = 42                      # 应该是两个不同的对象
X == Y                      # True
X is Y                      # 相同的对象,caching 在起作用

之前说过 Python 垃圾回收时,优化机制,导致 small integer 和 strings 会被缓存和重新使用,is 操作符告诉我们他们引用得是相同的对象。

事实上,如果想要知道对象的引用次数,可以使用 getrefcount 方法,在 sys 模块中。

import sys
sys.getrefcount(1)

检查对象 1 被引用的次数,常常会发现超过期待。因为 Integer 是不可变的,所以也就无所谓多少引用指向相同的对象了。

reference

  • Learning Python 4th Edition Chapter 6

2017-08-22 python , variable , object

Python modules and package

Python 很重要的一个概念 module,用来组织代码结构。

import 导入搜索的路径

  • 代码的 home 路径
  • PYTHONPATH 目录(如果存在的话)
  • 标准库路径
  • .pth 文件中配置的路径(如果存在的话)

最终,这些路径都会存在 sys.path 中,是一个保存着一系列搜索路径的 list。

>>> import sys
>>> sys.path

导入工作流程

  • 在路径中找到导入的模块
  • 编译
  • 运行

Package

一个目录的 Python code 被称为 package,这样的导入被成为 package import。

import dir1.dir2.mod
from dir1.dir2.mod import x

文件目录结构可能是

dir0\dir1\dir2\mod.py

每一个 package 被定义时都会产生一个 __init__.py 的文件,该文件可以像普通文件一样包含 Python 代码,也可以为空。

Package 初始化

当 Python 导入 Package 时,会自动跑 `__init__.py` 下的内容,所以 `__init__.py` 文件下是天然的存放初始化内容的地方,比如初始化数据库连接等等。

如果定义了 __all__ ,在 from * 时导入,就会选择性导入指定内容

__all__ = ["Error", "encode", "decode"]

2017-08-20 python , modules , import , pythonpath , library

Spring MVC 实战笔记

从 WizNote 中整理。

POJO, Plain Old java object, 最简单的 Java 对象

DI 带来的最大好处,松耦合,如果一个对象只通过接口(而不是具体实现或初始化过程)来表明依赖关系,那么这种依赖就能够在对象本身毫不知情的情况下,用不同的具体实现进行替换。

AOP aspect-oriented programming, 面向切面编程允许将遍布应用各处的功能分离出来形成可重用的组件

依赖注入让互相协作的软件组件保持松散耦合,而 AOP 则是让遍布各处的功能分离出来形成可重用的组件。

体系结构

Spring 框架提供约 20 个模块。

核心容器

由核心,Bean,上下文,表达式语言模块

  • 核心模块提供框架基本组成部分,包括 IoC (控制反转) 和 依赖注入 DI
  • Bean 模块提供 BeanFactory,是一个工厂模式的复杂实现
  • 上下文,在核心和 Bean 模块基础上,访问定义和配置的任何对象的媒介,ApplicationContext 接口是上下文模块的重点
  • 表达式语言模块在运行时提供查询和操作一个对象图的表达式

数据访问 / 集成

数据访问 / 集成层包括 JDBC,ORM,OXM,JMS 和事务处理模块

Web

Web 层由 Web,Web-MVC,Web-Socket 和 Web-Portlet 组成

其他

还有其他一些重要的模块,像 AOP,Aspects,Instrumentation,Web 和测试模块

  1. DispatcherServlet

  DispatcherServlet 是前置控制器,配置在 web.xml 文件中的。拦截匹配的请求,Servlet 拦截匹配规则要自已定义,把拦截下来的请求,依据相应的规则分发到目标 Controller 来处理,是配置 spring MVC 的第一步。

  1. InternalResourceViewResolver

  视图名称解析器

常用注解

@Controller 负责注册一个 bean 到 spring 上下文中,类名前加此注解,告知 Spring 容器这是一个控制器组件,负责注册一个 bean 到 spring 上下文中

Controller 注解示例

@Controller
@RequestMapping("/mvc")
public class mvcController {
    @RequestMapping("/hello")
    public String hello(){
        return "hello";
    }
}

@Controller
@RequestMapping("/rest")
public class RestController {
    @RequestMapping(value="/user/{id}",method=RequestMethod.GET)
    public String get(@PathVariable("id") Integer id){
        System.out.println("get"+id);
        return "/hello";
    }

    @RequestMapping(value="/user/{id}",method=RequestMethod.POST)
    public String post(@PathVariable("id") Integer id){
        System.out.println("post"+id);
        return "/hello";
    }

    @RequestMapping(value="/user/{id}",method=RequestMethod.PUT)
    public String put(@PathVariable("id") Integer id){
        System.out.println("put"+id);
        return "/hello";
    }

    @RequestMapping(value="/user/{id}",method=RequestMethod.DELETE)
    public String delete(@PathVariable("id") Integer id){
        System.out.println("delete"+id);
        return "/hello";
    }
}

@RequestMapping 类方法前加,注解为控制器指定可以处理哪些 URL 请求

@RequestMapping(value = "/register", method = RequestMethod.POST)

三个常用属性:value,params,method

value 必填属性,代表请求的 url,支持模糊配置。(value 字可以省略,但是属性值必须填)

@RequestMapping(value="/users/**")   匹配"/users/abc/abc";
@RequestMapping(value="/product?")   匹配"/product1"或"/producta",但不匹配"/product"或"/productaa";
@RequestMapping(value="/product*")   匹配“/productabc”或“/product”,但不匹配“/productabc/abc”;
@RequestMapping(value="/product/*")   匹配“/product/abc”,但不匹配“/productabc”;

params 可选属性,代表对请求参数进行过滤

@RequestMapping(value="/login.do",params="flag")   代表请求中必须要有名为 flag 的提交项
@RequestMapping(value="/login.do",params="!flag")  代表请求中不能有名为 flag 的提交项
@RequestMapping(value="/login.do",params="flag=hello") 代表请求中必须有名为 flag 的提交项,且值为 hello
@RequestMapping(value="/login.do",params="flag!=hello") 代表请求中如果有名为 flag 的提交项,其值不能为 hello
@RequestMapping(value="/login.do",params={"flag1","flag2=hello"}) 代表请求中必须有名为 flag1 的提交项,同时必须有名为 flag2 的提交项,且 flag2 的值必须为 hello

method 可选属性,代表请求方式

@RequestMapping(value="/login.do",method=RequestMethod.POST)
@RequestMapping(value="/login.do",method=RequestMethod.GET)
@RequestMapping(value="/login.do", method= {RequestMethod.POST, RequestMethod.GET}"

@RequestBody 该注解用于读取 Request 请求的 body 部分数据,使用系统默认配置的 HttpMessageConverter 进行解析,然后把相应的数据绑定到要返回的对象上 , 再把 HttpMessageConverter 返回的对象数据绑定到 Controller 中方法的参数上

@ResponseBody 该注解用于将 Controller 的方法返回的对象,通过适当的 HttpMessageConverter 转换为指定格式后,写入到 Response 对象的 body 数据区

@ModelAttribute 在方法定义上使用 @ModelAttribute 注解:Spring MVC 在调用目标处理方法前,会先逐个调用在方法级上标注了 @ModelAttribute 的方法

在方法的入参前使用 @ModelAttribute 注解:可以从隐含对象中获取隐含的模型数据中获取对象,再将请求参数 –绑定到对象中,再传入入参将方法入参对象添加到模型中

@RequestParam 在处理方法入参处使用 @RequestParam 可以把请求参数传递给请求方法

@RequestMapping(value = "/check", method = RequestMethod.GET)
public
@ResponseBody
String check(@RequestParam(value = "signature", required = true, defaultValue = "") String signature,
             @RequestParam(value = "timestamp", required = true, defaultValue = "") String timestamp,
             @RequestParam(value = "nonce", required = true, defaultValue = "") String nonce,
             @RequestParam(value = "echostr", required = true, defaultValue = "") String echostr,
             HttpServletRequest request,
             HttpServletResponse response
) {
    response.addHeader("Access-Control-Allow-Origin", "*");
    response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");

    if (checkSignature(signature, timestamp, nonce)) {
        return echostr;
    }
    return "";
}

@PathVariable

  绑定 URL 占位符到参数

@ExceptionHandler

  注解到方法上,出现异常时会执行该方法

@ControllerAdvice

使 Contoller 成为全局的异常处理类,类中用 @ExceptionHandler 方法注解的方法可以处理所有 Controller 中发生的异常

@ControllerAdvice
public class GlobalExceptionHandler {
    private static Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public void exceptionHandler(HttpServletRequest req, Exception e) throws Exception {
        //todo add request info to log
        logger.error("error: {}", e);
        return;
    }

}

自动装配主要使用 @ComponentScan、@Component 和 @Autowired。

  • @ComponentScan:作用在配置类上,启用组件扫描。扫描并注册标注了 @Component(@Controller\@Service\@Repository)的类型。@Configuration 已经应用了 @Component 注解。
  • @Autowired:按类型自动装配。@Autowired 和使用 @Inject(JSR-330)或 @Resource(JSR-250)的效果是类似的。@Autowired 和 @Inject 默认按类型注入,@Resource 默认按名称注入。

@Autowired

@Resource

参数绑定注解

A、处理 requet uri 部分(这里指 uri template 中 variable,不含 queryString 部分)的注解: @PathVariable;

B、处理 request header 部分的注解: @RequestHeader, @CookieValue;

C、处理 request body 部分的注解:@RequestParam, @RequestBody;

D、处理 attribute 类型是注解: @SessionAttributes, @ModelAttribute;

@PathVariable

当使用 @RequestMapping URI template 样式映射时, 即 someUrl/{paramId}, 这时的 paramId 可通过 @Pathvariable 注解绑定它传过来的值到方法的参数上。

示例代码:

[java] view plain copy

@Controller @RequestMapping(“/owners/{ownerId}”) public class RelativePathUriTemplateController { @RequestMapping(“/pets/{petId}”) public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) { // implementation omitted } } 上面代码把 URI template 中变量 ownerId 的值和 petId 的值,绑定到方法的参数上。若方法参数名称和需要绑定的 uri template 中变量名称不一致,需要在 @PathVariable(“name”) 指定 uri template 中的名称。

@RequestHeader、@CookieValue

@RequestHeader 注解,可以把 Request 请求 header 部分的值绑定到方法的参数上。

示例代码:

这是一个 Request 的 header 部分:

[plain] view plain copy

Host localhost:8080 Accept text/html,application/xhtml+xml,application/xml;q=0.9 Accept-Language fr,en-gb;q=0.7,en;q=0.3 Accept-Encoding gzip,deflate Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive 300

[java] view plain copy

@RequestMapping(“/displayHeaderInfo.do”) public void displayHeaderInfo(@RequestHeader(“Accept-Encoding”) String encoding, @RequestHeader(“Keep-Alive”) long keepAlive) { //… } 上面的代码,把 request header 部分的 Accept-Encoding 的值,绑定到参数 encoding 上了, Keep-Alive header 的值绑定到参数 keepAlive 上。

@CookieValue 可以把 Request header 中关于 cookie 的值绑定到方法的参数上。

例如有如下 Cookie 值:

[java] view plain copy

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84 参数绑定的代码:

[java] view plain copy

@RequestMapping(“/displayHeaderInfo.do”) public void displayHeaderInfo(@CookieValue(“JSESSIONID”) String cookie) { //… } 即把 JSESSIONID 的值绑定到参数 cookie 上。

@RequestParam, @RequestBody

@RequestParam

A) 常用来处理简单类型的绑定,通过 Request.getParameter() 获取的 String 可直接转换为简单类型的情况( String–> 简单类型的转换操作由 ConversionService 配置的转换器来完成);因为使用 request.getParameter() 方式获取参数,所以可以处理 get 方式中 queryString 的值,也可以处理 post 方式中 body data 的值;

B)用来处理 Content-Type: 为 application/x-www-form-urlencoded 编码的内容,提交方式 GET、POST;

C) 该注解有两个属性: value、required; value 用来指定要传入值的 id 名称,required 用来指示参数是否必须绑定;

示例代码:

@Controller @RequestMapping(“/pets”) @SessionAttributes(“pet”) public class EditPetForm { // … @RequestMapping(method = RequestMethod.GET) public String setupForm(@RequestParam(“petId”) int petId, ModelMap model) { Pet pet = this.clinic.loadPet(petId); model.addAttribute(“pet”, pet); return “petForm”; } // …

@RequestBody

该注解常用来处理 Content-Type: 不是 application/x-www-form-urlencoded 编码的内容,例如 application/json, application/xml 等;

它是通过使用 HandlerAdapter 配置的 HttpMessageConverters 来解析 post data body,然后绑定到相应的 bean 上的。

因为配置有 FormHttpMessageConverter,所以也可以用来处理 application/x-www-form-urlencoded 的内容,处理完的结果放在一个 MultiValueMap<String, String>里,这种情况在某些特殊需求下使用,详情查看 FormHttpMessageConverter api;

示例代码:

[java] view plain copy

@RequestMapping(value = “/something”, method = RequestMethod.PUT) public void handle(@RequestBody String body, Writer writer) throws IOException { writer.write(body); }

4、@SessionAttributes, @ModelAttribute

@SessionAttributes:

该注解用来绑定 HttpSession 中的 attribute 对象的值,便于在方法中的参数里使用。

该注解有 value、types 两个属性,可以通过名字和类型指定要使用的 attribute 对象;

示例代码:

[java] view plain copy

@Controller @RequestMapping(“/editPet.do”) @SessionAttributes(“pet”) public class EditPetForm { // … }

@ModelAttribute

该注解有两个用法,一个是用于方法上,一个是用于参数上;

用于方法上时: 通常用来在处理 @RequestMapping 之前,为请求绑定需要从后台查询的 model;

用于参数上时: 用来通过名称对应,把相应名称的值绑定到注解的参数 bean 上;要绑定的值来源于:

A) @SessionAttributes 启用的 attribute 对象上;

B) @ModelAttribute 用于方法上时指定的 model 对象;

C) 上述两种情况都没有时,new 一个需要绑定的 bean 对象,然后把 request 中按名称对应的方式把值绑定到 bean 中。

用到方法上 @ModelAttribute 的示例代码:

// Add one attribute // The return value of the method is added to the model under the name “account” // You can customize the name via @ModelAttribute(“myAccount”) @ModelAttribute public Account addAccount(@RequestParam String number) { return accountManager.findAccount(number); } 这种方式实际的效果就是在调用 @RequestMapping 的方法之前,为 request 对象的 model 里 put(“account”, Account);

用在参数上的 @ModelAttribute 示例代码:

@RequestMapping(value=”/owners/{ownerId}/pets/{petId}/edit”, method = RequestMethod.POST) public String processSubmit(@ModelAttribute Pet pet) { } 首先查询 @SessionAttributes 有无绑定的 Pet 对象,若没有则查询 @ModelAttribute 方法层面上是否绑定了 Pet 对象,若没有则将 URI template 中的值按对应的名称绑定到 Pet 对象的各属性上。

补充讲解:

问题: 在不给定注解的情况下,参数是怎样绑定的?

通过分析 AnnotationMethodHandlerAdapter 和 RequestMappingHandlerAdapter 的源代码发现,方法的参数在不给定参数的情况下:

若要绑定的对象时简单类型: 调用 @RequestParam 来处理的。

若要绑定的对象时复杂类型: 调用 @ModelAttribute 来处理的。

这里的简单类型指 Java 的原始类型 (boolean, int 等)、原始类型对象(Boolean, Int 等)、String、Date 等 ConversionService 里可以直接 String 转换成目标对象的类型;

下面贴出 AnnotationMethodHandlerAdapter 中绑定参数的部分源代码:

[java] view plain copy

private Object[] resolveHandlerArguments(Method handlerMethod, Object handler, NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception { Class[] paramTypes = handlerMethod.getParameterTypes(); Object[] args = new Object[paramTypes.length]; for (int i = 0; i < args.length; i++) { MethodParameter methodParam = new MethodParameter(handlerMethod, i); methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer); GenericTypeResolver.resolveParameterType(methodParam, handler.getClass()); String paramName = null; String headerName = null; boolean requestBodyFound = false; String cookieName = null; String pathVarName = null; String attrName = null; boolean required = false; String defaultValue = null; boolean validate = false; Object[] validationHints = null; int annotationsFound = 0; Annotation[] paramAnns = methodParam.getParameterAnnotations(); for (Annotation paramAnn : paramAnns) { if (RequestParam.class.isInstance(paramAnn)) { RequestParam requestParam = (RequestParam) paramAnn; paramName = requestParam.value(); required = requestParam.required(); defaultValue = parseDefaultValueAttribute(requestParam.defaultValue()); annotationsFound++; } else if (RequestHeader.class.isInstance(paramAnn)) { RequestHeader requestHeader = (RequestHeader) paramAnn; headerName = requestHeader.value(); required = requestHeader.required(); defaultValue = parseDefaultValueAttribute(requestHeader.defaultValue()); annotationsFound++; } else if (RequestBody.class.isInstance(paramAnn)) { requestBodyFound = true; annotationsFound++; } else if (CookieValue.class.isInstance(paramAnn)) { CookieValue cookieValue = (CookieValue) paramAnn; cookieName = cookieValue.value(); required = cookieValue.required(); defaultValue = parseDefaultValueAttribute(cookieValue.defaultValue()); annotationsFound++; } else if (PathVariable.class.isInstance(paramAnn)) { PathVariable pathVar = (PathVariable) paramAnn; pathVarName = pathVar.value(); annotationsFound++; } else if (ModelAttribute.class.isInstance(paramAnn)) { ModelAttribute attr = (ModelAttribute) paramAnn; attrName = attr.value(); annotationsFound++; } else if (Value.class.isInstance(paramAnn)) { defaultValue = ((Value) paramAnn).value(); } else if (paramAnn.annotationType().getSimpleName().startsWith(“Valid”)) { validate = true; Object value = AnnotationUtils.getValue(paramAnn); validationHints = (value instanceof Object[] ? (Object[]) value : new Object[] {value}); } } if (annotationsFound > 1) { throw new IllegalStateException(“Handler parameter annotations are exclusive choices - “ + “do not specify more than one such annotation on the same parameter: “ + handlerMethod); } if (annotationsFound == 0) {// 若没有发现注解 Object argValue = resolveCommonArgument(methodParam, webRequest); // 判断 WebRquest 是否可赋值给参数 if (argValue != WebArgumentResolver.UNRESOLVED) { args[i] = argValue; } else if (defaultValue != null) { args[i] = resolveDefaultValue(defaultValue); } else { Class<?> paramType = methodParam.getParameterType(); if (Model.class.isAssignableFrom(paramType)   Map.class.isAssignableFrom(paramType)) { if (!paramType.isAssignableFrom(implicitModel.getClass())) { throw new IllegalStateException(“Argument [” + paramType.getSimpleName() + “] is of type “ + “Model or Map but is not assignable from the actual model. You may need to switch “ + “newer MVC infrastructure classes to use this argument.”); } args[i] = implicitModel; } else if (SessionStatus.class.isAssignableFrom(paramType)) { args[i] = this.sessionStatus; } else if (HttpEntity.class.isAssignableFrom(paramType)) { args[i] = resolveHttpEntityRequest(methodParam, webRequest); } else if (Errors.class.isAssignableFrom(paramType)) { throw new IllegalStateException(“Errors/BindingResult argument declared “ + “without preceding model attribute. Check your handler method signature!”); } else if (BeanUtils.isSimpleProperty(paramType)) {// 判断是否参数类型是否是简单类型,若是在使用 @RequestParam 方式来处理,否则使用 @ModelAttribute 方式处理 paramName = “”; } else { attrName = “”; } } } if (paramName != null) { args[i] = resolveRequestParam(paramName, required, defaultValue, methodParam, webRequest, handler); } else if (headerName != null) { args[i] = resolveRequestHeader(headerName, required, defaultValue, methodParam, webRequest, handler); } else if (requestBodyFound) { args[i] = resolveRequestBody(methodParam, webRequest, handler); } else if (cookieName != null) { args[i] = resolveCookieValue(cookieName, required, defaultValue, methodParam, webRequest, handler); } else if (pathVarName != null) { args[i] = resolvePathVariable(pathVarName, methodParam, webRequest, handler); } else if (attrName != null) { WebDataBinder binder = resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler); boolean assignBindingResult = (args.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1])); if (binder.getTarget() != null) { doBind(binder, webRequest, validate, validationHints, !assignBindingResult); } args[i] = binder.getTarget(); if (assignBindingResult) { args[i + 1] = binder.getBindingResult(); i++; } implicitModel.putAll(binder.getBindingResult().getModel()); } } return args; }

RequestMappingHandlerAdapter 中使用的参数绑定,代码稍微有些不同,有兴趣的同仁可以分析下,最后处理的结果都是一样的。

示例:

@RequestMapping ({“/”, “/home”}) public String showHomePage(String key){ logger.debug(“key=”+key); return “home”; } 这种情况下,就调用默认的 @RequestParam 来处理。

[java] view plain copy

@RequestMapping (method = RequestMethod.POST) public String doRegister(User user){ if(logger.isDebugEnabled()){ logger.debug(“process url[/user], method[post] in “+getClass()); logger.debug(user); } return “user”; } 这种情况下,就调用 @ModelAttribute 来处理。

参考文档:

1、 spring Web Doc:

spring-3.1.0/docs/spring-framework-reference/html/mvc.html


2017-08-19 spring-mvc , spring , notes

Spring mvc 的注解

一般的注解,比如常见的 @Override 是 Java 从 1.5 版本开始引入,注解一般用来对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等等进行注解,他的作用一般分为如下四个方面:

  1. 生成文档
  2. 编译检查,通过注解让编译器在编译期间进行检查校验
  3. 编译时动态处理,编译时通过注解标示进行动态处理,比如生成代码
  4. 运行时动态处理,反射注入实例等等

一般的注解可以分为三类:

  1. Java 自带的注解,包括 @Override @Deprecated 等等
  2. 元注解,用于定义注解,包括 @Retention @Target @Inherited @Documented @Retention 用于标明注解被保留的阶段,@Target 用于标明注解使用的范围,@Inherited 用于标明注解可继承,@Documented 用于标明是否生成 javadoc 文档
  3. 自定义注解,根据需求使用元注解自定义注解

Spring 中的注解

Spring 中的注解大概可以分为两大类:

  • Spring 的 bean 容器相关的注解,或者说 bean 工厂相关的注解
  • Springmvc 相关的注解

Spring 的 bean 容器相关的注解,先后有:@Required, @Autowired, @PostConstruct, @PreDestory,还有 Spring3.0 开始支持的 JSR-330 标准 javax.inject.*中的注解 (@Inject, @Named, @Qualifier, @Provider, @Scope, @Singleton).

Spring mvc 相关的注解有:@Controller, @RequestMapping, @RequestParam, @ResponseBody 等等。

要理解 Spring 中的注解,先要理解 Java 中的注解。

Java 中的注解

Java 中 1.5 中开始引入注解,最熟悉的应该是:@Override, 它的定义如下:

/**
 * Indicates that a method declaration is intended to override a
 * method declaration in a supertype. If a method is annotated with
 * this annotation type compilers are required to generate an error
 * message unless at least one of the following conditions hold:
 * The method does override or implement a method declared in a
 * supertype.
 * The method has a signature that is override-equivalent to that of
 * any public method declared in Object.
 *
 * @author  Peter von der Ah&eacute;
 * @author  Joshua Bloch
 * @jls 9.6.1.4 @Override
 * @since 1.5
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

从注释,我们可以看出,@Override 的作用是,提示编译器,使用了 @Override 注解的方法必须 override 父类或者 java.lang.Object 中的一个同名方法。我们看到 @Override 的定义中使用到了 @Target, @Retention,它们就是所谓的“元注解”——就是定义注解的注解,或者说注解注解的注解。我们看下 @Retention

/**
 * Indicates how long annotations with the annotated type are to
 * be retained.  If no Retention annotation is present on
 * an annotation type declaration, the retention policy defaults to
 * RetentionPolicy.CLASS.
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
	/**
	 * Returns the retention policy.
	 * @return the retention policy
	 */
	RetentionPolicy value();
}

@Retention 用于提示注解被保留多长时间,有三种取值:

public enum RetentionPolicy {
	/**
	 * Annotations are to be discarded by the compiler.
	 */
	SOURCE,
	/**
	 * Annotations are to be recorded in the class file by the compiler
	 * but need not be retained by the VM at run time.  This is the default
	 * behavior.
	 */
	CLASS,
	/**
	 * Annotations are to be recorded in the class file by the compiler and
	 * retained by the VM at run time, so they may be read reflectively.
	 *
	 * @see java.lang.reflect.AnnotatedElement
	 */
	RUNTIME
}

RetentionPolicy.SOURCE 保留在源码级别,被编译器抛弃 (@Override 就是此类); RetentionPolicy.CLASS 被编译器保留在编译后的类文件级别,但是被虚拟机丢弃; RetentionPolicy.RUNTIME 保留至运行时,可以被反射读取。

再看 @Target:

package java.lang.annotation;

/**
 * Indicates the contexts in which an annotation type is applicable. The
 * declaration contexts and type contexts in which an annotation type may be
 * applicable are specified in JLS 9.6.4.1, and denoted in source code by enum
 * constants of java.lang.annotation.ElementType
 * @since 1.5
 * @jls 9.6.4.1 @Target
 * @jls 9.7.4 Where Annotations May Appear
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
	/**
	 * Returns an array of the kinds of elements an annotation type
	 * can be applied to.
	 * @return an array of the kinds of elements an annotation type
	 * can be applied to
	 */
	ElementType[] value();
}

@Target 用于提示该注解使用的地方,取值有:

public enum ElementType {
	/** Class, interface (including annotation type), or enum declaration */
	TYPE,
	/** Field declaration (includes enum constants) */
	FIELD,
	/** Method declaration */
	METHOD,
	/** Formal parameter declaration */
	PARAMETER,
	/** Constructor declaration */
	CONSTRUCTOR,
	/** Local variable declaration */
	LOCAL_VARIABLE,
	/** Annotation type declaration */
	ANNOTATION_TYPE,
	/** Package declaration */
	PACKAGE,
	/**
	 * Type parameter declaration
	 * @since 1.8
	 */
	TYPE_PARAMETER,
	/**
	 * Use of a type
	 * @since 1.8
	 */
	TYPE_USE
}

分别表示该注解可以被使用的地方:1)TYPE 用于类,接口(包括注解),enum 定义;2) FIELD 属性域;3)METHOD 方法;4)PARAMETER 参数;5)CONSTRUCTOR 构造函数;6)LOCAL_VARIABLE 局部变量;7)ANNOTATION_TYPE 注解类型;8)PACKAGE 包

所以:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

表示 @Override 只能使用在方法上,保留在源码级别,被编译器处理,然后抛弃掉。

还有一个经常使用的元注解 @Documented :

/**
 * Indicates that annotations with a type are to be documented by javadoc
 * and similar tools by default.  This type should be used to annotate the
 * declarations of types whose annotations affect the use of annotated
 * elements by their clients.  If a type declaration is annotated with
 * Documented, its annotations become part of the public API
 * of the annotated elements.
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

表示注解是否能被 javadoc 处理并保留在文档中。

使用 元注解 来自定义注解 和 处理自定义注解

有了元注解,那么我就可以使用它来自定义我们需要的注解。结合自定义注解和 AOP 或者过滤器,是一种十分强大的武器。比如可以使用注解来实现权限的细粒度的控制——在类或者方法上使用权限注解,然后在 AOP 或者过滤器中进行拦截处理。下面是一个关于登录的权限的注解的实现:

/**
 * 不需要登录注解
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NoLogin {
}

我们自定义了一个注解 @NoLogin, 可以被用于 方法 和 类 上,注解一直保留到运行期,可以被反射读取到。该注解的含义是:被 @NoLogin 注解的类或者方法,即使用户没有登录,也是可以访问的。下面就是对注解进行处理了:

/**
 * 检查登录拦截器
 * 如不需要检查登录可在方法或者 controller 上加上 @NoLogin
 */
public class CheckLoginInterceptor implements HandlerInterceptor {
	private static final Logger logger = Logger.getLogger(CheckLoginInterceptor.class);

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
							 Object handler) throws Exception {
		if (!(handler instanceof HandlerMethod)) {
			logger.warn("当前操作 handler 不为 HandlerMethod=" + handler.getClass().getName() + ",req="
						+ request.getQueryString());
			return true;
		}
		HandlerMethod handlerMethod = (HandlerMethod) handler;
		String methodName = handlerMethod.getMethod().getName();
		// 判断是否需要检查登录
		NoLogin noLogin = handlerMethod.getMethod().getAnnotation(NoLogin.class);
		if (null != noLogin) {
			if (logger.isDebugEnabled()) {
				logger.debug("当前操作 methodName=" + methodName + "不需要检查登录情况");
			}
			return true;
		}
		noLogin = handlerMethod.getMethod().getDeclaringClass().getAnnotation(NoLogin.class);
		if (null != noLogin) {
			if (logger.isDebugEnabled()) {
				logger.debug("当前操作 methodName=" + methodName + "不需要检查登录情况");
			}
			return true;
		}
		if (null == request.getSession().getAttribute(CommonConstants.SESSION_KEY_USER)) {
			logger.warn("当前操作" + methodName + "用户未登录,ip=" + request.getRemoteAddr());
			response.getWriter().write(JsonConvertor.convertFailResult(ErrorCodeEnum.NOT_LOGIN).toString()); // 返回错误信息
			return false;
		}
		return true;
	}
	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response,
						   Object handler, ModelAndView modelAndView) throws Exception {
	}
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
								Object handler, Exception ex) throws Exception {
	}
}

上面我们定义了一个登录拦截器,首先使用反射来判断方法上是否被 @NoLogin 注解:

NoLogin noLogin = handlerMethod.getMethod().getAnnotation(NoLogin.class);

然后判断类是否被 @NoLogin 注解:

noLogin = handlerMethod.getMethod().getDeclaringClass().getAnnotation(NoLogin.class);

如果被注解了,就返回 true,如果没有被注解,就判断是否已经登录,没有登录则返回错误信息给前台和 false. 这是一个简单的使用 注解 和 过滤器 来进行权限处理的例子。扩展开来,那么我们就可以使用注解,来表示某方法或者类,只能被具有某种角色,或者具有某种权限的用户所访问,然后在过滤器中进行判断处理。

Spring 的 bean 容器相关的注解

  • @Autowired 是我们使用得最多的注解,其实就是 autowire=byType 就是根据类型的自动注入依赖(基于注解的依赖注入),可以被使用再属性域,方法,构造函数上。

  • @Qualifier 就是 autowire=byName, @Autowired 注解判断多个 bean 类型相同时,就需要使用 @Qualifier(“xxBean”) 来指定依赖的 bean 的 id:

      @Controller
      @RequestMapping("/user")
      public class HelloController {
          @Autowired
          @Qualifier("userService")
          private UserService userService;
    
  • @Resource 属于 JSR250 标准,用于属性域和方法上。也是 byName 类型的依赖注入。使用方式:@Resource(name=”xxBean”). 不带参数的 @Resource 默认值类名首字母小写。关于 Autowired 和 @Resouece 的区别可以参考这篇

  • JSR-330 标准 javax.inject.* 中的注解 (@Inject, @Named, @Qualifier, @Provider, @Scope, @Singleton)。@Inject 就相当于 @Autowired, @Named 就相当于 @Qualifier, 另外 @Named 用在类上还有 @Component 的功能。

  • @Component, @Controller, @Service, @Repository, 这几个注解不同于上面的注解,上面的注解都是将被依赖的 bean 注入进入,而这几个注解的作用都是生产 bean, 这些注解都是注解在类上,将类注解成 Spring 的 bean 工厂中一个一个的 bean。@Controller, @Service, @Repository 基本就是语义更加细化的 @Component。关于这几个注解可以参考这篇『文章』()

  • @PostConstruct 和 @PreDestroy 不是用于依赖注入,而是 bean 的生命周期。类似于 init-method(InitializeingBean) destory-method(DisposableBean)

Spring 中注解的处理

Spring 中注解的处理基本都是通过实现接口 BeanPostProcessor 来进行的:

public interface BeanPostProcessor {
	Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
	Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

相关的处理类有: AutowiredAnnotationBeanPostProcessor,CommonAnnotationBeanPostProcessor,PersistenceAnnotationBeanPostProcessor, RequiredAnnotationBeanPostProcessor

这些处理类,可以通过 配置隐式的配置进 Spring 容器。这些都是依赖注入的处理,还有生产 bean 的注解 (@Component, @Controller, @Service, @Repository) 的处理:

<context:component-scan base-package="net.aazj.service,net.aazj.aop" />

这些都是通过指定扫描的基包路径来进行的,将他们扫描进 Spring 的 bean 容器。注意 context:component-scan 也会默认将 AutowiredAnnotationBeanPostProcessor,CommonAnnotationBeanPostProcessor 配置进来。所以是可以省略的。另外 context:component-scan 也可以扫描 @Aspect 风格的 AOP 注解,但是需要在配置文件中加入 进行配合。

Spring 注解和 JSR-330 标准注解的区别:

Spring javax.inject.* javax.inject restrictions/ comments
@Autowired @Inject @Inject 没有 required 属性
@Component @Named  
@Scope(“singletion”) @Singleton JSR-330 默认 scope 和 Spring prototype 类似,为了和 Spring 中默认保持一直, JSR-330 bean 使用 singleton 作为默认值。
@Qualifier @Named -
@Value - no equivalent
@Required - no equivalent
@Lazy - no equivalent

注解的原理

注解被编译后本质上就是一个继承 Annotation 接口的接口

reference


2017-08-18 Spring , Java , Web , 注解 , 拦截器 , 反射

Python 笔记之内置类型

类型

Object type Example literals/creation
Numbers 1234 , 3.1415 , 3+4j , Decimal , Fraction
Strings ‘spam’ , “guido’s” , b’a\x01c’
Lists [1, [2, ‘three’], 4]
Dictionaries {‘food’: ‘spam’, ‘taste’: ‘yum’}
Tuples (1, ‘spam’, 4, ‘U’)
Files myfile = open(‘eggs’, ‘r’)
Sets set(‘abc’), {‘a’, ‘b’, ‘c’}
Other core types Booleans, types, None
Program unit types Functions, modules, classes
Implementation-related types Compiled code, stack tracebacks

Numbers

可以表示整形,浮点数,分数等等,甚至可以用来表示非常大的数,比如 2 ** 10000

不同进制表示

0x1234 0X1234               # 16 进制 0x 后接 [0-9A-F]
0o177 0O177                 # 8 进制  0o Zero 加大小写的 o 后接 [0-7]
0b101 0B101                 # 2 进制 New in 2.6 > ,后接 [0-1]

内置的 hex(number)oct(number)bin(number) 将 int 转变为这三种进制的字符串。

Strings

字符串在 Python 中支持切片的操作,比如 S = 'Spam',这时 S[-1] 表示的是最后一个字符 m

S[1:3]          # 'pa'  左边包括,右边不包括
S[1:]           # 'pam' [1:len(S)]
S[:3]           # 'Spa' 等效于 [0:3]
S[:-1]          # 除去最后一个元素
S[:]            # S 全部

字符串重复操作可以使用 S * 10 打印 10 遍 S.

字符串和 Java 一样是不可变对象,Numbers 和 Tuples 也是不可变的。

字符串常用函数

常用操作

S.find('pa')        # 输出字串位置 1
S.replace('pa', 'xy')   # 替换 pa 为 xy 输出到新的字符串,不改变原始字符串

line = 'hello world'
line.split(' ')     # 空格分割,输出列表
line.upper()        # 转为大写
line.isdigit()      # 判断是否为数字,还有 isalpha() isnumeric() isspace() 等等
line.rstrip()       # 移除行尾空白字符比如 `\n`

格式化字符串

'%s, eggs, and %s' % ('spam', 'SPAM!')     # Python 格式化表达式
# or
'%s, eggs, and %s' % ('spam', 'SPAM!')     # Python 2.6 and 3.0

对于任何一个对象,都可以调用内置方法 dir(S) 来查看相关属性和方法。dir 方法可以用来快速查看对象的可调用方法,所以记不住方法的名字也不需要担心,使用 dir() 方法即可。

对于任何方法的使用,可以用 help(S.replace) 查看。

正则

说到字符串处理就不可避免的要谈到正则

>>> import re
>>> match = re.match('Hello[ \t]*(.*)world', 'Hello
>>> match.group(1)
'Python '

>>> match = re.match('/(.*)/(.*)/(.*)', '/usr/home/einverne')
>>> match.groups()
('usr', 'home', 'einverne')

Lists

基本操作,可 Strings 类似也都支持切片,下标索引等等

L = [123, 'spam', 1.23]
L[0]
L[:-1]
L + [4,5,6]

Lists 不同于其他熟悉的语言,可以承载不同的类型,比如上面的例子,L 中就有三种完全不同的类型,Lists 没有特定的大小,可以根据需求调整长度。

L.append('NI')          # 添加
L.pop(2)                # 删除 index 为 2 的元素,并返回
L.insert(1, 'xyz')      # 在 index 之前插入 'xyz'
L.remove('xyz')         # 按照值移除第一个找到的 item

另外 list 的 sort()reverse() 方法分别为 list 排序,逆序,直接改动 list 自身。

Nesting

循环嵌套,比如表示 3 × 3 矩阵

>>> M = [[1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]]
>>> M
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

在获取值的时候可以

>>> M[1]     # 获取第二行
>>> M[1][2]  # 获取第二行第三列

可以使用 List comprehensions 来获取列

>>> col2 = [row[1] for row in M]   # 获取第二列

这句话表达的意思就是,将矩阵 M 中每一行的 row[1] 第二个元素放到一个新的 list 中返回。这个表达式甚至可以更加复杂,比如在返回时每个元素乘以二。

>>> [row[1] * 2 for row in M]

或者加入判断,只有偶数才返回

>>> [row[1] for row in M if row[1] % 2 == 0]

List comprehensions 可以用在任何可以迭代的对象上,比如返回 M 矩阵对角线元素

>>> diag = [M[i][i] for i in range(3)]
>>> [c * 2 for c in 'spam']  # 输出一个列表 ['ss', 'pp', 'aa', 'mm']

在 Python 3.0 及以上,comprehension 语法也可以用来创建 set 或者 dict

{sum(row) for row in M}       # {sum(row) for row in M} 创建一个每一行和的 set
{i : sum(M[i]) for i in range(3)}    # {0: 6, 1: 15, 2: 24} dict

Dictionaries

基本操作

D = {}
D['name'] = 'bob'

dict 也同样支持嵌套,value 值可以为不同类型。

>>> rec = {'name': {'first': 'Bob', 'last': 'Smith'},
'job': ['dev', 'mgr'],
'age': 40.5}

构造这样一个复杂结构的 dict 在 Python 中非常轻松,但是如果在 C 中将会需要非常多的 coding。在一个 lower-level 的语言中我们需要非常小心释放变量内存空间,但是在 Python 中当丢失对象的引用时,内存空间会自动被释放。

>>> rec = 0

Python 也有自己的垃圾回收机制,在 Python 中当对象的最后一个引用丢失时,空间会立即被回收。

使用 in 来检查 key 是否在 dict 中

if 'f' in D:
    print D['f']

或者在 Python 3 可以使用 get() 方法,来避免获取一个不存在的 key 可能引发的错误

D.get('f', 0)           # 如果 f 存在返回 D['f'],否则返回 0,等效于
D['f'] if 'f' in D else 0

Tuples

元组,像 lists 一样的序列,但是像 string 一样不可变。支持任意的类型,任意的嵌套,和序列一样。

T = (1, 2, 3, 4)
T = ('spam', 3.0, [11, 22, 33])

Files

文件类型是 Python 用来和外部文件访问的重要接口。使用内置的 open() 函数来创建文件对象。

f = open('data.txt', 'w')
f.write('Hello\n')
f.close()

f = open('data.txt')          # 'r' 可以省略
text = f.read()
f.close()

在 Python 3 中区分了 text 和 binary data,Text files 代表字符串内容并且以 Unicode 形式可以 encode 和 decode。而 binary files 代表特殊的 bytes 并且允许用户直接访问无修改的文件内容。

Other Core Types

集合

X = set('spam')
Y = {'h', 'a', 'm'}

X & Y   # 交集
X | Y   # 并集
X - Y   # 差集

Decimal

import deciaml
d = decimal.Decimal('3.141')

>>> decimal.getcontext().prec = 2
>>> decimal.Decimal('1.00') / decimal.Decimal('3.00')
Decimal('0.33')

>>> from fractions import Fraction
>>> f = Fraction(2, 3)
>>> f + 1
Fraction(5, 3)
>>> f + Fraction(1, 2)
Fraction(7, 6)

Type check

if type(L) == type([]):
    print('yes')

if type(L) == list:
    print('yes')

if isinstance(L, list):
    print('yes')

不过需要记住,代码少用这些类型检查。

In Python, we code to object interfaces (operations supported), not to types.

Python 中所有的一切都是 Object,所以上面提到的所有类型都是 Object。


2017-08-16 python , linux , object , type , object-type

电子书

最近文章