[toc]
演化
HttpConnector 的run方法執(zhí)行
socket=serverSocket.accept();
然后將socket傳到HttpProcessor中,由process方法new Request和Response。
也就是說連接器(及其支持類Processor)完成 接受socket請求并生成request和reponse,然后在容器里完成 靜態(tài)資源調(diào)用或者servlet的實(shí)例化及其service方法的調(diào)用。(如下uml關(guān)系)
書中的servlet類關(guān)系
外觀模式
為了防止Request里的一些繼承之外的方法被不安全訪問,可以借鑒這種方式,在RequestFacade 的構(gòu)造函數(shù)里
public class RequestFacade implements ServletRequest {
private ServleLRequest request = null;
public RequestFacade(Request request) {
this.request = request;
}
RequestFacade只實(shí)現(xiàn)ServletRequest的方法
RequestFacade requestFacade = new RequestFacade(request);
servlet.service((ServletRequest) requestFacade, (ServletResponse) responseFacade);
如此一來在service方法里訪問不到Request的其他方法了。
線程專用
public class HttpConnector implements Runnable {
boolean stopped;
private String scheme = "http";
public String getScheme() {
return scheme;
}
public void run() {
。。。。。。。。
}
public void start() {
Thread thread = new Thread(this);
// thread = new Thread(this, threadName);
thread.start();
}
}
如此便可優(yōu)雅的啟動線程
HttpConnector connector = new HttpConnector();
connector.start();
break 標(biāo)記處
boolean flag = true;
loop: {
if (flag) {
break loop;
}
System.out.println("in loop");
}
System.out.println("out loop");
processor對象池
用到的屬性
/**
* The set of processors that have ever been created.
*/
private Vector created = new Vector();
/**
* The current number of processors that have been created.
* 也是HttpProcessor的id
*/
private int curProcessors = 0;
/**
* The minimum number of processors to start at initialization time.
*/
protected int minProcessors = 5;
/**
* The maximum number of processors allowed, or <0 for unlimited.
* 小于0 是無限制!
*/
private int maxProcessors = 20;
/**
* The set of processors that have been created but are not currently
* being used to process a request.
* 已經(jīng)創(chuàng)建但現(xiàn)在沒有被用于處理請求。該棧中的都是可以使用的。
*/
private Stack processors = new Stack();
- HttpConnector的start方法,其中會創(chuàng)建minProcessors數(shù)量的processor。并通過recycle方法,將其push進(jìn)棧processors,以便循環(huán)使用。
// Create the specified minimum number of processors
while (curProcessors < minProcessors) {
// 萬一出現(xiàn)maxProcessors<minProcessors,可以break;
if ((maxProcessors > 0) && (curProcessors >= maxProcessors))
break;
HttpProcessor processor = newProcessor();
recycle(processor);
}
- 在HttpConnector的newProcessor方法中new HttpProcessor 并啟動其線程。并且
curProcessors++, created.addElement(processor);
curProcessors做為HttpProcessor的id。
private HttpProcessor newProcessor() {
HttpProcessor processor = new HttpProcessor(this, curProcessors++);
if (processor instanceof Lifecycle) {
try {
// 啟動processor
((Lifecycle) processor).start();
} catch (LifecycleException e) {
log("newProcessor", e);
return (null);
}
}
created.addElement(processor);
return (processor);
}
- 然后我們看HttpConnector的run方法。主要就是等待socket連接,這里需要注意
socket.setTcpNoDelay(tcpNoDelay);
,這個(gè)是關(guān)閉Nagle算法,防止神奇的40ms延時(shí).
// Loop until we receive a shutdown command
while (!stopped) {
socket = serverSocket.accept();
if (connectionTimeout > 0)
socket.setSoTimeout(connectionTimeout);
// 關(guān)閉Nagle算法,防止神奇的40ms延時(shí)
socket.setTcpNoDelay(tcpNoDelay);
當(dāng)收到一個(gè)socket連接的時(shí)候,需要分配processor,主要是通過createProcessor
方法來創(chuàng)建或者復(fù)用HttpProcessor,HttpProcessor的assign
方法來將socket傳遞到HttpProcessor,進(jìn)行后續(xù)的處理。
// Hand this socket off to an appropriate processor
HttpProcessor processor = createProcessor();
// 沒有可用處理器就關(guān)閉socket。
if (processor == null) {
try {
log(sm.getString("httpConnector.noProcessor"));
socket.close();
} catch (IOException e) {
;
}
continue;
}
processor.assign(socket);
// The processor will recycle itself when it finishes
- HttpConnector的createProcessor方法
synchronized (processors) {
if (processors.size() > 0) {
// 重用已經(jīng)存在的Processor
return ((HttpProcessor) processors.pop());
}
if ((maxProcessors > 0) && (curProcessors < maxProcessors)) {
// 未滿最大值,就new一個(gè)
return (newProcessor());
} else {
if (maxProcessors < 0) {
// <0 代表無上限。
return (newProcessor());
} else {
return (null);
}
}
}
** 到這里我們已經(jīng)明白了,Processor對象池重用(也可以叫做線程池)。**
然后我們要看看HttpProcessor是怎么運(yùn)行的,以及自己怎么可持續(xù)地處理任務(wù)。
關(guān)鍵屬性
/**
* Is there a new socket available?
*/
private boolean available = false;
- HttpProcessor的run。一開始會阻塞在 await(),等待連接器分配socket。處理完請求后調(diào)用
connector.recycle(this);
把自己放進(jìn)棧processors里去
// Process requests until we receive a shutdown signal
while (!stopped) {
// Wait for the next socket to be assigned
Socket socket = await();
if (socket == null)
continue;
// Process the request from this socket
try {
process(socket);
} catch (Throwable t) {
log("process.invoke", t);
}
// Finish up this request
connector.recycle(this);
}
available開始是false,所以阻塞在wait();
private synchronized Socket await() {
// Wait for the Connector to provide a new Socket
while (!available) {
try {
wait();
} catch (InterruptedException e) {
}
}
// Notify the Connector that we have received this Socket
Socket socket = this.socket;
available = false;// 此處由于已經(jīng)拿到了分配的socket的引用,所以可以接下一個(gè)了。當(dāng)然,理論上要等processor調(diào)用recyle才能下一個(gè)。
notifyAll();
if ((debug >= 1) && (socket != null))
log(" The incoming request has been awaited");
return (socket);
}
前面子分析HttpConnector的run方法的時(shí)候,run()里面調(diào)用了 processor.assign(socket);于是我們再看assign。
由于
synchronized void assign(Socket socket) {
// Wait for the Processor to get the previous Socket
// 這種情況應(yīng)該不存在。這里的處理是保險(xiǎn)手法,防止漏掉要處理的socket。
while (available) {
try {
wait();
} catch (InterruptedException e) {
}
}
// Store the newly available Socket and notify our thread
this.socket = socket;
available = true;
notifyAll();
if ((debug >= 1) && (socket != null))
log(" An incoming request is being assigned");
}
由于接收socket和處理socket分別在不同的線程里,所以可以同時(shí)處理較多的請求
生命周期(觀察者模式)
首先,我們編程講究面向接口編程。這樣易于擴(kuò)展。
我們看到書中,列如
SimpleContext implements Context, Pipeline, Lifecycle
SimpleWrapper implements Wrapper, Pipeline, Lifecycle
SimpleContext 實(shí)現(xiàn)Lifecycle后,將生命周期的功能添加了進(jìn)來,而不是直接在所有start和stop的地方println。
同時(shí) 生命周期也是個(gè)非常好的觀察者模式的實(shí)現(xiàn)。
首先我們看uml
- 實(shí)現(xiàn)LifeCycle的都稱為一個(gè)組件(被觀察者)。一個(gè)組件會做以下 的事情包括LifecycleSupport的事情。
// 我們關(guān)心的event
BEFORE_START_EVENT, START_EVENT,AFTER_START_EVENT, BEFORE_STOP_EVENT, STOP_EVENT, AFTER_STOP_EVENT.
addLifecycleListener
findLifecycleListeners
removeLifecycleListener
start
stop
- 支持類LifecycleSupport 維護(hù)一個(gè)
LifecycleListener listeners[]
監(jiān)聽者數(shù)組。 為實(shí)現(xiàn)LifeCycle的類提供以下支持。
addLifecycleListener(LifecycleListener listener)
findLifecycleListeners()
fireLifecycleEvent(String type, Object data)// 通知觀察者
removeLifecycleListener(LifecycleListener listener)
- 實(shí)現(xiàn)LifecycleListener是一個(gè)監(jiān)聽器(觀察者)
流程:
- 向組件添加我們定義的監(jiān)聽器(觀察者,比如SimpleContextLifecycleListener);
- 實(shí)現(xiàn)了LifeCycle的組件調(diào)用start(),LifecycleSupport的fireLifecycleEvent,通知觀察者發(fā)生的事件。
在這里作為最頂層容器的SimpleContext ,會調(diào)自己start,還會間接調(diào)用兩個(gè)子Wrapper容器 的start ,還有SimpleLoader的start等。
類加載器
自定義類加載器
對于java.lang.ClassLoader的loadClass(String name, boolean resolve)方法的解析來看,可以得出以下2個(gè)結(jié)論:
- 如果不想打破雙親委派模型,那么只需要重寫findClass方法即可
- 如果想打破雙親委派模型,那么就重寫整個(gè)loadClass方法
class MyLoader extends ClassLoader {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String filename = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = getClass().getResourceAsStream(filename);
if (is == null) {
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
}
public class ClassLoadTest {
public static void main(String[] args) throws Exception {
ClassLoader myloader = new MyLoader();
Object object = myloader.loadClass("xyy.test.ClassLoadTest").newInstance();
Object object1 = new ClassLoadTest();
System.out.println(object.getClass());
System.out.println(object.getClass().getClassLoader());
System.out.println(object1.getClass().getClassLoader());
System.out.println(object instanceof xyy.test.ClassLoadTest);
}
}
java類加載為什么需要雙親委派模型這樣的往返模式?
- 委派模型對于安全性是非常重要的
- 惡意的意圖有人能寫出一類叫做 java.lang.Object,可用于
訪問任何在硬盤上的目錄。 因?yàn)?JVM 的信任 java.lang.Object 類,它不會關(guān)注這方面的活動。因此,如果自定義 java.lang.Object 被允許加載,安全管理器將很容易癱瘓。幸運(yùn)的是,這將不會發(fā)生,因?yàn)槲赡P蜁柚惯@種情況的發(fā)生。
當(dāng)自定義 java.lang.Object 類在程序中被調(diào)用的時(shí)候, system 類加載器將該請求委派給 extension 類加載器,然后委派給 bootstrap 類加載器。這樣 bootstrap類加載器先搜索的核心庫,找到標(biāo)準(zhǔn) java.lang.Object 并實(shí)例化它。這樣,自定義 java.lang.Object 類永遠(yuǎn)不會被加載。
參考我寫的另一篇總結(jié):傳送門
Digester(xml 解析)
用digester優(yōu)雅解決多環(huán)境配置的問題。
主要知道怎么使用:
- 創(chuàng)建標(biāo)簽對象
- 設(shè)置對象屬性
- 創(chuàng)建子標(biāo)簽,設(shè)置子標(biāo)簽屬性(同上)
- 關(guān)聯(lián)方法。
- 進(jìn)行解析。
<?xml version="1.0" encoding="ISO-8859-1"?>
<employee firstName="Freddie" lastName="Mercury">
<office description="Headquarters">
<address streetName="Wellington Avenue" streetNumber="223"/>
</office>
<office description="Client site">
<address streetName="Downing Street" streetNumber="10"/>
</office>
</employee>
對于如上xml的解析代碼:
public class Test02 {
public static void main(String[] args) {
String path = System.getProperty("user.dir") + File.separator + "etc";
File file = new File(path, "employee2.xml");
Digester digester = new Digester();
// add rules
digester.addObjectCreate("employee", "ex15.pyrmont.digestertest.Employee");
digester.addSetProperties("employee");
digester.addObjectCreate("employee/office", "ex15.pyrmont.digestertest.Office");
digester.addSetProperties("employee/office");
digester.addSetNext("employee/office", "addOffice");
digester.addObjectCreate("employee/office/address", "ex15.pyrmont.digestertest.Address");
digester.addSetProperties("employee/office/address");
digester.addSetNext("employee/office/address", "setAddress");
try {
Employee employee = (Employee) digester.parse(file);
ArrayList offices = employee.getOffices();
Iterator iterator = offices.iterator();
System.out.println("-------------------------------------------------");
while (iterator.hasNext()) {
Office office = (Office) iterator.next();
Address address = office.getAddress();
System.out.println(office.getDescription());
System.out.println("Address : " + address.getStreetNumber() + " " + address.getStreetName());
System.out.println("--------------------------------");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
或者將規(guī)則配置抽取成RuleSetBase的繼承類EmployeeRuleSet。
public class EmployeeRuleSet extends RuleSetBase {
public void addRuleInstances(Digester digester) {
// add rules
digester.addObjectCreate("employee", "ex15.pyrmont.digestertest.Employee");
digester.addSetProperties("employee");
digester.addObjectCreate("employee/office", "ex15.pyrmont.digestertest.Office");
digester.addSetProperties("employee/office");
digester.addSetNext("employee/office", "addOffice");
digester.addObjectCreate("employee/office/address", "ex15.pyrmont.digestertest.Address");
digester.addSetProperties("employee/office/address");
digester.addSetNext("employee/office/address", "setAddress");
}
}
Test02: 修改
Digester digester = new Digester();
digester.addRuleSet(new EmployeeRuleSet());
關(guān)閉鉤子(優(yōu)雅的清理代碼)
關(guān)閉鉤子實(shí)際上是java虛擬機(jī)層面的東西,我們主要用來應(yīng)對應(yīng)用程序非正常關(guān)閉的情況(如ctrl+c)。
我們要做的就是寫一個(gè)線程,Runtime.getRuntime().addShutdownHook
在run方法中處理我們要清理的工作。
然后扔給虛擬機(jī),當(dāng)應(yīng)用程序關(guān)閉的時(shí)候,會去調(diào)用這個(gè)線程要做的事。
demo:
public class ShutdownHookDemo {
public void start() {
System.out.println("Demo");
ShutdownHook ShutdownHook = new ShutdownHook();
Runtime.getRuntime().addShutdownHook(ShutdownHook);
}
public static void main(String[] args) {
ShutdownHookDemo demo = new ShutdownHookDemo();
demo.start();
try {
System.in.read();
}
catch(Exception e) {
}
}
private class ShutdownHook extends Thread {
public void run() {
// do some shutdown works
System.out.println("Shutting down");
}
}
}