ributes currentRequestAttributes() throws IllegalStateException {
RequestAttributes attributes = getRequestAttributes();
// 此处省略不相关逻辑…………
return attributes;
}
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<RequestAttributes>("Request attributes");
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<RequestAttributes>("Request context");
}
通过这段代码可以看出,生成的RequestAttributes对象是线程局部变量(ThreadLocal),因此request对象也是线程局部变量;这就保证了request对象的线程安全性。
优缺点
该方法的主要优点:
1) 注入不局限于Controller中:在方法1中,只能在Controller中加入request参数。而对于方法2,不仅可以在Controller中注入,还可以在任何Bean中注入,包括Service、Repository及普通的Bean。
2) 注入的对象不限于request:除了注入request对象,该方法还可以注入其他scope为request或session的对象,如response对象、session对象等;并保证线程安全。
3) 减少代码冗余:只需要在需要request对象的Bean中注入request对象,便可以在该Bean的各个方法中使用,与方法1相比大大减少了代码冗余。
但是,该方法也会存在代码冗余。考虑这样的场景:web系统中有很多controller,每个controller中都会使用request对象(这种场景实际上非常频繁),这时就需要写很多次注入request的代码;如果还需要注入response,代码就更繁琐了。下面说明自动注入方法的改进方法,并分析其线程安全性及优缺点。
方法3:基类中自动注入
代码示例
与方法2相比,将注入部分代码放入到了基类中。
基类代码:
public class BaseController {
@Autowired
protected HttpServletRequest request;
}
Controller代码如下;这里列举了BaseController的两个派生类,由于此时测试代码会有所不同,因此服务端测试代码没有省略;客户端也需要进行相应的修改(同时向2个url发送大量并发请求)。
@Controller
public class TestController extends BaseController {
// 存储已有参数,用于判断参数value是否重复,从而判断线程是否安全
public static Set<String> set = new HashSet<>();
@RequestMapping("/test")
public void test() throws InterruptedException {
String value = request.getParameter("key");
// 判断线程安全
if (set.contains(value)) {
System.out.println(value + "\t重复出现,request并发不安全!");
} else {
System.out.println(value);
set.add(value);
}
// 模拟程序执行了一段时间
Thread.sleep(1000);
}
}
@Controller
public class Test2Controller extends BaseController {
@RequestMapping("/test2")
public void test2() throws InterruptedException {
String value = request.getParameter("key");
// 判断线程安全(与TestController使用一个set进行判断)
if (TestController.set.contains(value)) {
System.out.println(value + "\t重复出现,request并发不安全!");
} else {
System.out.println(value);
TestController.set.add(value);
}
// 模拟程序执行了一段时间
Thread.sleep(1000);
}
}
线程安全性
测试结果:线程安全
分析:在理解了方法2的线程安全性的基础上,很容易理解方法3是线程安全的:当创建不同的派生类对象时,基类中的域(这里是注入的request)在不同的派生类对象中会占据不同的内存空间,也就是说将注入request的代码放在基类中对线程安全性没有任何影响;测试结果也证明了这一点。
优缺点
与方法2相比,避免了在不同的Controller中重复注入request;但是考虑到java只允许继承一个基类,所以如果Controller需要继承其他类时,该方法便不再好用。
无论是方法2和方法3,都只能在Bean中注入request;如果其他方法(如工具类中static方法)需要使用request对象,则需要在调用这些方法时将request参数传递进去。下面介绍的方法4,则可以直接在诸如工具类中的static方法中使用request对象(当然在各种Bean中也可以使用)。
方法4:手动调用
代码示例
@Controller
public class TestController {
@Req