stateful和stateless的讨论
想要区分什么是有状态和无状态,首先要明白什么是状态。
- 状态是什么?比如说一个人35岁,那么这个35岁就可以视为这个”人”的状态。在对象中像这类的“状态”就是用属性保存。比如:
1
2
3
4
5
6
7
8
9
10
11
12
13public class Person{
private int age;
public Person(){}
public int getAge(){
return this.age;
}
public void setAge(int newAge){
this.age = newAge;
}
}
这就是一个有状态的类。
在java web中,singlton和prototype的使用
- singlton(单例)默认是在全局中只有一个实例,所有调用都使用的是这一个实例。
prototype(原型)每次创建都会重新创建一个实例,将construct的过程走一遍。
优缺点:
由于singlton只有一个实例,而现实情况中经常会有多线程访问的情况,如果singlton是一个有状态的实例,那么就有可能出现竞争的情况,对资源进行竞争。
prototype每次都会创建,我们知道每一次创建都需要消耗时间和资源,当有很多人访问的时候,代价又难以接受。
所以对于web中应用,如果是没有状态的类,可以实现为单例,对于有状态的类,实现为原型。
更多的讨论
在MVC架构中,struts由于与前段端交流,常会涉及到有状态的类,因此其为原型模式;而在spring中,默认为singlton(单例)模式。
在MVC的Controller,Service,Dao层中,我们应该尽可能实现为无状态的类,只包含方法。servlet为无状态的类,session为有状态的类。
RMI(远程方法调用)
从三个方面理解RMI。
- 目的
远程方法调用,就是为了让在某个java虚拟机上的对象像调用本地对象一样调用另一个java 虚拟机中的对象上的方法。是牵涉到多个JVM的过程。 - RMI的过程

- 为了在两个JVM中都通,有了RMI,使用Java远程消息交换协议JRMP(Java Remote Messaging Protocol)进行通信;
- 为了屏蔽掉通信的具体实现,在客户端有stub,服务端有skeleton,两端通过这两个代理进行远端方法调用。(相当于加了一层)
到目前为止,client端并不知道server端有那些可以调用的方法。通过设置一个中间rmiregister,server将提供的方法注册到该rmiregister中,client端可以到rmiregister中查找需要的远端调用。
远端调用实现的过程:
- rmiregister运行
- server在rmiregister注册所提供的调用(将stub上传到rmiregister)
- client在rmiregister查找所需要的远端调用,并将其stub下载至本地至此,client端有stub,server端有skeleton,两端可以直接通信
- client远端调用
- client的stub发出远端调用请求
- server端的skeleton接收到stub发过来的远端调用,解析并调用远端调用的实现
- 第6步的返回值被skeleton发送值stub
- stub解析第6步的结果并返回给client。
代码实现
在java1.8中,已经不需要使用rmic(rmi compile)手动编译stub和skeleton了所以过程会简单些。代码步骤(假设远端调用返回的对象是Warehouse):- 定义Warehouse接口,并且继承自Remote
- 在服务器端定义WarehouseImpl,继承自UnicastRemoteObject
- 编写client和server端的代码
- 运行rmiregister
- 运行server端代码
运行client端代码
最后client端代码:Warehouse.class,client.class
服务器端代码:Warehouse.class WarehouseImpl.class,server.class注意:设置CLASSPATH,添加client和server的路径到CLASSPATH,否则java找不到其对应的class文件
使用jndi,配置jndi.properties代码示例:
1 | //Warehouse 两端都有 |
[TODO activation pdf2]
- rmi和spring结合
- Java程序执行过程
宏观上来看Java程序运行的过程。
- 编写好的.java程序被编译成为.class文件(可以加密在编译好字后尽心加密 ,然后用自定义的类加载器解密)
- 启动Java虚拟机,类加载器加载需要的类到内存中(可以包括解密过程)
执行引擎执行该类
如下图:

- 加载过程
Java程序启动过程中,会按照顺序有以下几个类加载器:
- 启动类加载器(BootstrapClassLoader):使用C语言编写(如果用java编写,而这个加载器也需要使用类加载器加载,形成一个环,无法加载),主要加载jre/lib下的文件,比如rt.jar等,该类无法被开发者使用。
- 扩展类加载器(Extension ClassLoader):该类加载器负责加载JDK安装目录下的\jre\lib\ext的类,开发者也可以直接使用扩展类加载器。
- 应用程序类加载器(AppClassLoader):负责加载用户类路径(Classpath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有定义过自己的类加载器,该类加载器为默认的类加载器。
- 用户自定义类加载器(User ClassLoader):JVM自带的类加载器是从本地文件系统加载标准的java class文件,而自定义的类加载器可以做到在执行非置信代码之前,自动验证数字签名,动态地创建符合用户特定需要的定制化构建类,从特定的场所(数据库、网络中)取得java class,用户使用的plugin classloader严格来说也是属于user
classloader。
双亲委派模型
每个类加载器加载类的时候,首先调用它的父加载器,如果他的父加载器不能加载,再轮到它加载。
好处:首先从java的内库中加载相应的类,在加载器之间形成一种加载的优先级顺序。
1 | protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ |

如何使用自定义的类加载器:
1 | //Book类定义 |
注意:定义ClassLoader从根本上来说就是重载findClass函数,找到需要load的类的二进制文件,在loader中处理(解密验证等等处理)之后,通过defineClass函数之后返回
invoke函数(反射)
获得反射Class类的三种反射机制:Person person = new Person();
- 1、通过Object类的getClass()方法:(需要先实例化一个对象)
Class clazz1 = person.getClass(); - 2、通过对象实例方法获取对象:(需要先实例化一个对象)
Class clazz2 = person.class; - 3、类的全路径:(不许呀实例对象)通过类的加载器实现
Class clazz3 = Class.forName("com.cn.Person");
invoke 参考
运行时数据区: - 堆(类实例,java垃圾收集器)
- 方法区(常量入”this is the string”,常量池)
- 虚拟机栈(方法调用栈)
- 本地方法栈
- 程序计数器
前两个线程公用