开发者学堂课程【高校精品课-上海交通大学-企业级应用体系架构:Arch. Components 2】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/75/detail/15820
Arch. Components 2(三)
内容介绍:
一、有状态、无状态服务
二、scope 的取值
这时刚才例子的结构,主要代码如下:
UserController.java
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping(value = "/findUser/{id}")
public User findOne(@PathVariable("id") String id) {
System.out.println(userService);
return userService.findUserByld(Integer.valueOf(id));
};
}
UserServicelmpl.java
@Service
public class UserServicelmpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public User findUserByld(Integer id) {
return userDao.findOne(id);
}
}
controler 会发送请求,service 实现类会调用 dao 进行访问。在两个浏览器里面,各自模拟一个客户端发送请求,上述代码没有做 scope 的注解,所以默认是 singleton 的,在下面可以看到,运行 n 多次下来,service instance 只有一个。
如果将下面的 UserServicelmpl.java 变成 prototype,上面保持 singleton 不变。在两个浏览器运行,会发现在当前配置里,虽然变成 prototype,理论来说,controler 是一个,但是每次访问 UserServicelmpl 时都应该应该创建一个新的对象,但运行后发现仍然只有一个 service instance,为什么呢?
usercontroler 是 singleton 的,因为没有做注解。会靠 spring 注入 userservice,创建 usercontroler 之后,就会创建 UserServicelmpl.java 的对象注入service里,所以每次接收请求的时候,都在调用注入对象的例子,相应方法是finduserbyid。所以尽管是 prototype,只有一个 service instance。这是注入机制导致的。
改变方法,将第一个 controler 也改成 prototype,运行时会发现每次都不一样,也就是每一个会生成一个新的controler 对象,尽管是注入进去的,但 Usercontroler 是多个,所以 UserService 也是多个。
如果 controler 保持不变,默认 scope 是 singleton。下面是通过 applicationcontext 的方式来获取 service 对象,而不是直接要求注入 userservice。运行结果就是四个对象都不相同。这个方案会更好一些,实际上是在保证controler 只有一个的同时会有四个,而不是像前边,controler 有四个,下面的也有四个。
把下面换成 session,就会发现2个浏览器里会有2个对象,但在同一个浏览器中发出的请求,会在同一个对象上调用,session 在浏览器里发出多个请求,始终用同一个对象,但是在不同的 session 里,会创建新的对象。
无法回避有状态的服务,还是会用到 prototype 或 session,不能只用默认的 singleton。在两个浏览器里不能只使用singleton,因为确实有可能需要为不同的用户维护他们各自的状态。一旦用 prototype 或 session,在内存当中,对象不止一个,只要不止一个,对服务器来说都是一种压力,压力随着客户端数量的增大而增多,所以要考虑好系统应该是什么样子的。
在 controler 和 service 两个类里,设有 prototype 和 session,可以看到实例的差异,那到底应该怎么设计呢?
系统是分层的,有 controler 层,有 DAO 层,DAO 的实现层,实体层,访问实层,service 层等等。在系统里,不能完全回避状态,总是有一些状态是需要维护的。
状态应该集中在某一层,显然,service 层是一个比较好的地方,controler 层是整个应用的入口,大量的请求过来之后,会通过 controler 做分发,会调用 service 进行操作,在入口的地方创建很多对象显然是不合适的,这和 controler层默认是 singleton 是一个道理。
DAO 层是访问下面的数据库,道理来说,不应该和具体的用户相关,多个 DAO 是没有意义的,而且多个 DAO 还会带来问题,在访问底层的数据库时候,多个 DAO 访问就像数据库原理中的事务,多个事务并发执行,是一个难解决的问题,所以 DAO 层不合适。只剩下 service 层是合适的。
所以一个系统在运行的时候,一些是有状态的,一些是无状态的。有状态的应尽量的少,将他放在服务器这层。如果想做得更好,可以把服务层拆得细一些,拆分成两个层,一层无状态服务层,一层有状态服务层。一层的scope是singleton,一层是 prototype 或者 section。在服务层有它的实践逻辑,大多数逻辑中,可能都不需要定义变量,当应用比较复杂之后,把不需要定义自己变量的东西包装在一层,叫做无状态服务层;把需要包装状态的那些服务放在有状态服务层,在无状态服务层之下。
也就是说,上层的无状态层没有任何的属性,只有方法,会调用下面的有状态服务层,有状态服务层包含一些成员方法为不同的用户维护各自的状态。这样才是一种比较理想的方案。可以把有状态的东西限制的尽可能小,整体数量应该进尽可能少。
原因就是有状态的东西会大量消耗内存,如果想节约内存,性能又会大幅下降。原因是在硬盘和内存之间做频繁的换进换出。
本质上就是要节约使用内存,内存的状态和持久化的状态做优化的管理和存储。在这种情况下,把内存使用率提高,尽量让有状态东西少。但有状态的东西是不可能没有的,如果没有就会变成整个服务器端全部是无状态的,可以把状态全部推到客户端处理,但客户端至少要有2个存储用户的会话状态,cookie 和 localstroge 是可以用来存放的。但是把状态存在客户端是需要格外的仔细的。毕竟这两个东西是很容易打开之后篡改的,所以是有隐患的。
好处是服务器端可以不用管状态,服务器端全部都是 singleton对象,状态全部通过 cookie 和 localstroge 写回到客户端。这样需要在 cookie 和 l ocalstroge 的保护上面下功夫。
总的来说,我们要谈的就是用户的会话状态如何保存,有状态、无状态的服务是什么意思,按照给出的例子自己运行可能会有比较深刻的体会。