Tomcat探祕(4):tomcat啟動過程詳述
熟悉Tomcat的工程師們,或者從事Java開發的,肯定都知道Tomcat是如何啟動和停止的。在Tomcat原始碼包裡面有個bin目錄,該目錄下放置了一些很重要的指令碼,Tomcat啟動和停止的指令碼程式就放在這裡,分別是startup.bat、shutdown.bat(Windows環境)和start.sh、shutdown.sh(Linux、Unix環境)。大家一定都知道如何使用它們,接下來就是研究一下它們是如何實現啟動和停止服務的。
在實際的生產環境下,絕大多數的Tomcat都是部署在Linux環境下的,在這片文章中,我們以startup.sh和shutdown.sh為例簡單說說啟動和停止過程。(Tomcat版本7.0.69)
啟動過程分析:
進入到Tomcat的bin目錄,啟動startup.sh的命令如下:
./startup.sh
該腳本里面都做了什麼呢?這裡貼出啟動指令碼:#!/bin/sh
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------------
# Start Script for the CATALINA Server
# -----------------------------------------------------------------------------
# Better OS/400 detection: see Bugzilla 31132
os400=false
case "`uname`" in
OS400*) os400=true;;
esac
# resolve links - $0 may be a softlink
PRG="$0"
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`/"$link"
fi
done
PRGDIR=`dirname "$PRG"`
EXECUTABLE=catalina.sh
# Check that target executable exists
if $os400; then
# -x will Only work on the os400 if the files are:
# 1. owned by the user
# 2. owned by the PRIMARY group of the user
# this will not work if the user belongs in secondary groups
eval
else
if [ ! -x "$PRGDIR"/"$EXECUTABLE" ]; then
echo "Cannot find $PRGDIR/$EXECUTABLE"
echo "The file is absent or does not have execute permission"
echo "This file is needed to run this program"
exit 1
fi
fi
exec "$PRGDIR"/"$EXECUTABLE" start " [email protected]"
下面簡單看看這個指令碼,前面幾行:
os400=false
case "`uname`" in
OS400*) os400=true;;
esac
使用uname檢視當前作業系統的名稱,然後判斷是否符合模式OS400*,如果是則進行標記,設定os400=true,用於控制後面的邏輯。
在後面的指令碼命令中,涉及到的兩個比較重要的變數就是PRGDIR(當前startup.sh指令碼的上一級目錄,即bin目錄)和EXECUTABLE(設定了指令碼名稱catalina.sh),然後最後通過執行
exec "$PRGDIR"/"$EXECUTABLE" start "[email protected] "
來啟動執行catalina.sh指令碼並將start引數傳遞給指令碼用於控制是進行啟動操作的。因為catelina.sh指令碼的程式碼較長,這裡只貼出跟啟動相關的部分指令:
...........
elif [<span style="color:#FF0000;"> "$1" = "start"</span> ] ; then
<span style="color:#3366FF;">if [ ! -z "$CATALINA_PID" ]; then
if [ -f "$CATALINA_PID" ]; then
if [ -s "$CATALINA_PID" ]; then
echo "Existing PID file found during start."
if [ -r "$CATALINA_PID" ]; then
PID=`cat "$CATALINA_PID"`
ps -p $PID >/dev/null 2>&1
if [ $? -eq 0 ] ; then
echo "Tomcat appears to still be running with PID $PID. Start aborted."
echo "If the following process is not a Tomcat process, remove the PID file and try again:"
ps -f -p $PID
exit 1
else
echo "Removing/clearing stale PID file."
rm -f "$CATALINA_PID" >/dev/null 2>&1
if [ $? != 0 ]; then
if [ -w "$CATALINA_PID" ]; then
cat /dev/null > "$CATALINA_PID"
else
echo "Unable to remove or clear stale PID file. Start aborted."
exit 1
fi
fi
fi
else
echo "Unable to read PID file. Start aborted."
exit 1
fi
else
rm -f "$CATALINA_PID" >/dev/null 2>&1
if [ $? != 0 ]; then
if [ ! -w "$CATALINA_PID" ]; then
echo "Unable to remove or write to empty PID file. Start aborted."
exit 1
fi
fi
fi
fi
fi</span>
shift
touch "$CATALINA_OUT"
if [ "$1" = "-security" ] ; then
if [ $have_tty -eq 1 ]; then
echo "Using Security Manager"
fi
shift
eval "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
-Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \
-Djava.security.manager \
-Djava.security.policy=="\"$CATALINA_BASE/conf/catalina.policy\"" \
-Dcatalina.base="\"$CATALINA_BASE\"" \
-Dcatalina.home="\"$CATALINA_HOME\"" \
-Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
<span style="color:#CC33CC;"> org.apache.catalina.startup.Bootstrap "[email protected]" start \
>> "$CATALINA_OUT" 2>&1 "&"</span>
else
eval "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
-Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \
-Dcatalina.base="\"$CATALINA_BASE\"" \
-Dcatalina.home="\"$CATALINA_HOME\"" \
-Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
<span style="color:#CC33CC;">org.apache.catalina.startup.Bootstrap "[email protected]" start \
>> "$CATALINA_OUT" 2>&1 "&"</span>
fi
if [ ! -z "$CATALINA_PID" ]; then
echo $! > "$CATALINA_PID"
fi
echo "Tomcat started."
elif [ <span style="color:#FF0000;">"$1" = "stop"</span> ] ; then
.................
catalina.sh指令碼是所有其他指令碼最終實際執行操作的指令碼,不管是啟動還是停止指令碼,執行startup.sh和shutdown.sh最終都會轉換為執行catalina.sh,只是在執行catalina.sh時傳遞的引數不一樣。
上面的紅色部分就是判斷是啟動還是停止服務,而藍色部分是對一些引數的校驗工作,而從紫色部分可以看出,最終啟動的時候是執行了org.apache.catalina.startup.Bootstrap這個類,同時傳遞了引數"start"。在之前的文章<Tomcat探祕(2):如何在Eclipse中匯入和執行tomcat原始碼?>中我們已經把Tomcat原始碼匯入到了Eclipse中,這時候我們可以根據類包名路徑找到相應的類Bootstrap,並且找到其main(),這裡貼出main方法實現,如下所示:
/**
* Main method and entry point when starting Tomcat via the provided
* scripts.
*
* @param args Command line arguments to be processed
*/
public static void main(String args[]) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
// When running as a service the call to stop will be on a new
// thread so make sure the correct class loader is used to prevent
// a range of class not found exceptions.
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
try {
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}
if (command.equals("startd")) {
args[args.length - 1] = "start";
daemon.load(args);
daemon.start();
} else if (command.equals("stopd")) {
args[args.length - 1] = "stop";
daemon.stop();
} <span style="color:#FF0000;">else if (command.equals("start")</span>) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else if (command.equals("configtest")) {
daemon.load(args);
if (null==daemon.getServer()) {
System.exit(1);
}
System.exit(0);
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
} catch (Throwable t) {
// Unwrap the Exception for clearer error reporting
if (t instanceof InvocationTargetException &&
t.getCause() != null) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(1);
}
}
上面的標紅部分就是根據傳遞進來的“start”引數來確定的分支邏輯。
從上面的main方法可以看出,整個啟動過程大概分為三個步驟,如上面藍色標記的部分:
(1)判斷bootstrap是否已經初始化了?如果沒有則進行初始化;
(2)呼叫Bootstrap類的load()來載入server.xml配置檔案(這是一個很重要的配置檔案);
(3)呼叫Bootstrap類的start()方法啟動Tomcat;
下面對這三個步驟進行簡單的描述。
第一步:判斷bootstrap是否已經初始化了?如果沒有則進行初始化;
通過上面的程式碼可以知道實際是通過呼叫bootstrap.init();來進行初始化的,那麼具體又做了什麼呢?我們看看init()方法是怎麼實現的:
/**
* Initialize daemon.
*/
public void init()
throws Exception
{
// Set Catalina path
setCatalinaHome();
setCatalinaBase();
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
Class<?> startupClass =
catalinaLoader.loadClass
("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.newInstance();
// Set the shared extensions class loader
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
String methodName = "setParentClassLoader";
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);
catalinaDaemon = startupInstance;
}
從程式碼裡可以看出,主要做了三件事:
(1)設定了catalina.base和catalina.home路徑;
(2)初始化了需要的類載入器;
(3)利用Java中的反射機制,通過執行setParentClassLoader方法來設定Tomcat類載入體系的頂級類載入器;
第二步:呼叫Bootstrap類的load()來載入server.xml配置檔案
我們開啟Bootstrap類的load方法,如下所示:
/**
* Load daemon.
*/
private void load(String[] arguments)
throws Exception {
// Call the load() method
String methodName = "load";
Object param[];
Class<?> paramTypes[];
if (arguments==null || arguments.length==0) {
paramTypes = null;
param = null;
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
Method method =
catalinaDaemon.getClass().getMethod(methodName, paramTypes);
if (log.isDebugEnabled())
log.debug("Calling startup class " + method);
method.invoke(catalinaDaemon, param);
}
從程式碼可以看出最終還是執行的是org.apache.catalina.startup.Catalina類的load方法,載入server.xml檔案,如下所示,至於具體的解析過程我們這裡就不詳述了,後面講使用專門的一篇文章進行講解,如果想知道可以繼續關注我的部落格或者自己去看原始碼學習^_^:
/**
* Start a new server instance.
*/
public void load() {
long t1 = System.nanoTime();
initDirs();
// Before digester - it may be needed
initNaming();
// Create and execute our Digester
Digester digester = createStartDigester();
InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
try {
file = configFile();
inputStream = new FileInputStream(file);
inputSource = new InputSource(file.toURI().toURL().toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail", file), e);
}
}
if (inputStream == null) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream(getConfigFile());
inputSource = new InputSource
(getClass().getClassLoader()
.getResource(getConfigFile()).toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail",
getConfigFile()), e);
}
}
}
// This should be included in catalina.jar
// Alternative: don't bother with xml, just create it manually.
if( inputStream==null ) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream("server-embed.xml");
inputSource = new InputSource
(getClass().getClassLoader()
.getResource("server-embed.xml").toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail",
"server-embed.xml"), e);
}
}
}
if (inputStream == null || inputSource == null) {
if (file == null) {
log.warn(sm.getString("catalina.configFail",
getConfigFile() + "] or [server-embed.xml]"));
} else {
log.warn(sm.getString("catalina.configFail",
file.getAbsolutePath()));
if (file.exists() && !file.canRead()) {
log.warn("Permissions incorrect, read permission is not allowed on the file.");
}
}
return;
}
try {
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
} catch (SAXParseException spe) {
log.warn("Catalina.start using " + getConfigFile() + ": " +
spe.getMessage());
return;
} catch (Exception e) {
log.warn("Catalina.start using " + getConfigFile() + ": " , e);
return;
}
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// Ignore
}
}
}
getServer().setCatalina(this);
// Stream redirection
initStreams();
// Start the new server
try {
getServer().init();
} catch (LifecycleException e) {
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new java.lang.Error(e);
} else {
log.error("Catalina.start", e);
}
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
}
}
第三步:呼叫Bootstrap類的start()方法啟動Tomcat
我們開啟start()方法的實現,如下所示:
/**
* Start the Catalina daemon.
*/
public void start()
throws Exception {
if( catalinaDaemon==null ) init();
Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
method.invoke(catalinaDaemon, (Object [])null);
}
從程式碼看,其實最終執行的是org.apache.catalina.startup.Catalina類的start方法,那麼org.apache.catalina.startup.Catalina類的start方法是怎麼實現的呢?如下所示:
/**
* Start a new server instance.
*/
public void start() {
if (getServer() == null) {
load();
}
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
}
long t1 = System.nanoTime();
// Start the new server
try {
getServer().start();
} catch (LifecycleException e) {
log.fatal(sm.getString("catalina.serverStartFail"), e);
try {
getServer().destroy();
} catch (LifecycleException e1) {
log.debug("destroy() failed for failed Server ", e1);
}
return;
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
}
// Register shutdown hook
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
// If JULI is being used, disable JULI's shutdown hook since
// shutdown hooks run in parallel and log messages may be lost
// if JULI's hook completes before the CatalinaShutdownHook()
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
if (await) {
await();
stop();
}
}
從程式碼看,做了以下工作:
(1)首先是判斷Catalina的Server例項是否初始化成功了,如果沒有,則繼續呼叫Catalina類的的load方法進行初始化;
(2)初始化成功之後,會呼叫上面程式碼中標紅部分進行整個元件的啟動操作,這裡想說的是,由於Tomcat內部設計的問題,整個元件的啟動並不是分散的,父級元件的啟動會帶動子級元素元件的啟動,依次類推,會使整個元件都啟動起來,例如Server的啟動會使相關的Service元件都啟動,而Service的啟動又會帶動Connector的啟動等等。這裡不做詳述,將另起一篇文章單獨說明,這篇文章只簡單描述整個啟動過程框架。
(3)如上述程式碼中藍色部分所示,設定了關閉鉤子,用於在Tomcat因為各種原因退出時做一些清理工作;
(4)呼叫Catalina自身類的await方法迴圈等待shutdown命令;
/**
* Await and shutdown.
*/
public void await() {
getServer().await();
}
Catalina類的await()方法其實只是代理了Server元件的await()方法。
我們在Eclipse內找到org.apache.catalina.core包下的Server元件的標準實現StandardServer,我們看看它的await方法都做了什麼?
/**
* <span style="color:#FF0000;">Wait until a proper shutdown command is received, then return.</span>
* This keeps the main thread alive - the thread pool listening for http
* connections is daemon threads.
*/
@Override
public void await() {
// Negative values - don't wait on port - tomcat is embedded or we just don't like ports
if( port == -2 ) {
// undocumented yet - for embedding apps that are around, alive.
return;
}
if( port==-1 ) {
try {
awaitThread = Thread.currentThread();
while(!stopAwait) {
try {
Thread.sleep( 10000 );
} catch( InterruptedException ex ) {
// continue and check the flag
}
}
} finally {
awaitThread = null;
}
return;
}
// Set up a server socket to wait on
try {
awaitSocket = new ServerSocket(port, 1,
InetAddress.getByName(address));
} catch (IOException e) {
log.error("StandardServer.await: create[" + address
+ ":" + port
+ "]: ", e);
return;
}
try {
awaitThread = Thread.currentThread();
// Loop waiting for a connection and a valid command
while (!stopAwait) {
ServerSocket serverSocket = awaitSocket;
if (serverSocket == null) {
break;
}
// Wait for the next connection
Socket socket = null;
StringBuilder command = new StringBuilder();
try {
InputStream stream;
long acceptStartTime = System.currentTimeMillis();
try {
<span style="color:#3333FF;">socket = serverSocket.accept();</span>
socket.setSoTimeout(10 * 1000); // Ten seconds
stream = socket.getInputStream();
} catch (SocketTimeoutException ste) {
// This should never happen but bug 56684 suggests that
// it does.
log.warn(sm.getString("standardServer.accept.timeout",
Long.valueOf(System.currentTimeMillis() - acceptStartTime)), ste);
continue;
} catch (AccessControlException ace) {
log.warn("StandardServer.accept security exception: "
+ ace.getMessage(), ace);
continue;
} catch (IOException e) {
if (stopAwait) {
// Wait was aborted with socket.close()
break;
}
log.error("StandardServer.await: accept: ", e);
break;
}
// Read a set of characters from the socket
int expected = 1024; // Cut off to avoid DoS attack
while (expected < shutdown.length()) {
if (random == null)
random = new Random();
expected += (random.nextInt() % 1024);
}
while (expected > 0) {
int ch = -1;
try {
ch = stream.read();
} catch (IOException e) {
log.warn("StandardServer.await: read: ", e);
ch = -1;
}
if (ch < 32) // Control character or EOF terminates loop
break;
command.append((char) ch);
expected--;
}
} finally {
// Close the socket now that we are done with it
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
// Ignore
}
}
// Match against our command string
<span style="color:#3366FF;">boolean match = command.toString().equals(shutdown);
if (match) {
log.info(sm.getString("standardServer.shutdownViaPort"));
break;
} else
log.warn("StandardServer.await: Invalid command '"
+ command.toString() + "' received");</span>
}
} finally {
ServerSocket serverSocket = awaitSocket;
awaitThread = null;
awaitSocket = null;
// Close the server socket and return
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
// Ignore
}
}
}
}
正如上面標紅部分描述的那樣,這個方法一直等待socket連線並接受shutdown命令。上述程式碼中藍色標記部分用於一直等待Socket連線,並且判斷接收到的指令是否是shutdown變數(在類前面定義了shutdown = "SHUTDOWN";這裡用的是equals,因此是大小寫敏感的,如果寫成了小寫的“shutdown”也是不行的哦!!!),如果是,則跳出迴圈,並關閉socket服務;
(5)如果Tomcat正常執行且沒有收到shutdown命令,那麼程式會一直等待在await()方法處(阻塞在(4)中的serverSocket.accept()),一旦接收到shutdown命令,awaiit()方法會退出迴圈等待,並執行stop方法。下面看看stop()方法做了什麼:
/**
* Stop an existing server instance.
*/
public void stop() {
try {
// Remove the ShutdownHook first so that server.stop()
// doesn't get invoked twice
if (useShutdownHook) {
Runtime.getRuntime().removeShutdownHook(shutdownHook);
// If JULI is being used, re-enable JULI's shutdown to ensure
// log messages are not lost
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
true);
}
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
// This will fail on JDK 1.2. Ignoring, as Tomcat can run
// fine without the shutdown hook.
}
// Shut down the server
try {
Server s = getServer();
LifecycleState state = s.getState();
if (LifecycleState.STOPPING_PREP.compareTo(state) <= 0
&& LifecycleState.DESTROYED.compareTo(state) >= 0) {
// Nothing to do. stop() was already called
} else {
s.stop();
s.destroy();
}
} catch (LifecycleException e) {
log.error("Catalina.stop", e);
}
}
從程式碼看,首先是在執行時中移除了關閉鉤子,然後呼叫stop方法和destroy()方法分別關閉服務和釋放資源。
至此,整個Tomcat的啟動過程我們比較詳細的進行了說明,至於其中的部分細節並沒有詳細說明,將另起文章進行單獨說明,如類載入器、元件之間如何只要啟動一個Server元件就可以全部啟動,啟動成功之後關閉又是什麼樣的一個過程等等,如果想知道,可以繼續關注我的部落格,謝謝。
相關推薦
Tomcat探祕(4):tomcat啟動過程詳述
熟悉Tomcat的工程師們,或者從事Java開發的,肯定都知道Tomcat是如何啟動和停止的。在Tomcat原始碼包裡面有個bin目錄,該目錄下放置了一些很重要的指令碼,Tomcat啟動和停止的指令碼程式就放在這裡,分別是startup.bat、shutdown.bat(W
Tomcat探祕(2):如何在Eclipse中匯入和執行tomcat原始碼?
在上一篇,我們講了Tomcat是什麼,作為被廣泛使用的Servlet容器,如果想提高自己,我想大家都有想去閱讀Tomcat原始碼和了解其內部實現原理的衝動吧。為了能夠閱讀原始碼,並能進行除錯程
Android進階(一): Launcher啟動過程
1.前言 最近一直在看 《Android進階解密》 的一本書,這本書編寫邏輯、流程都非常好,而且很容易看懂,非常推薦大家去看看(沒有收廣告費,單純覺得作者寫的很好)。 今天就將 Launcher 系統啟動過程 總結一下(基於Android 8.0 系統)。 文章
Android進階(三):Application啟動過程(最詳細&最簡單)
1.前言 最近一直在看 《Android進階解密》 的一本書,這本書編寫邏輯、流程都非常好,而且很容易看懂,非常推薦大家去看看(沒有收廣告費,單純覺得作者寫的很好)。 上一篇簡單的介紹了Android進階(二): 應用程序啟動過程,最終知道了ActivityThrea
Android進階(四):Activity啟動過程(最詳細&最簡單)
1.前言 最近一直在看 《Android進階解密》 的一本書,這本書編寫邏輯、流程都非常好,而且很容易看懂,非常推薦大家去看看(沒有收廣告費,單純覺得作者寫的很好)。 上一篇簡單的介紹了Android進階(三):Application啟動過程(最詳細&最簡單)
朱老師ARM裸機學習筆記(四):S5PV210啟動過程詳解
常用器件特性 記憶體: SRAM 靜態記憶體 特點就是容量小、價格高,優點是不需要軟體初始化直接上電就能用 DRAM 動態記憶體 特點就是容量大、價格低,缺點就是上電後不能直接使用,需要軟體初始化後才可以使用。 微
Android進階(四):Activity啟動過程(最詳細&最簡單)
1.前言 最近一直在看 《Android進階解密》 的一本書,這本書編寫邏輯、流程都非常好,而
IntelliJ IDEA使用(二):tomcat和jetty配置
上一講用idea建立了maven web專案,接下來我們把專案釋出到tomcat和jetty執行,以便進一步地開發和除錯 配置tomcat 第一、開啟選單欄 第二、點選設定按鈕,新增應用伺服器,選擇tomcat server 選擇tomcat目錄 新增後如下所示 到此我們已經把tomcat伺服器新
Tomcat探祕(1):Tomcat是什麼?
作為一個java軟體開發者,接觸到的很多專案都是web專案,而跟Web專案密切相關的就是Web容器了,目前市面上可以免費試用的效能不錯的當屬Tomcat了,所以決定對Tomcat進行詳細的瞭解一下,在
前端學習(1):Tomcat,MySQL,eclipse——製作簡易留言板
基礎設定 1.下載brackets,用於編輯html文字 2.使用eclipse編輯jsp檔案作為網頁後臺指令碼 3.下載tomcat,在eclipse下配置tomcat 4.下載MySQL和視覺化和視覺化處理軟體Navicat,作為資料庫 遇到的問題 1.之前
網易微專業——Java Web開發工程師學習筆記(2):Tomcat
目錄結構:bin:可執行檔案conf:配置檔案lib:Tomcat依賴庫temp:臨時資料夾webapps:預設的應用部署目錄work:供web應用使用bin:啟動指令碼通過改變環境變數JAVA_OPTS,常見啟動引數-server -Xms512m -Xmx512mserv
JNDI學習總結(二):tomcat配置全域性和私有JNDI資料來源的幾種方式
下面介紹幾種常用的JNDI資料來源配置方式環境:IDEA+tomcat7全域性:1. 修改tomcat的context.xml的<context>標籤 在<context>標籤
Swift學習筆記(4):字符串
min mes 不同的 常用方法 dice 內存空間 全部 there logs 目錄: 初始化 常用方法或屬性 字符串索引 初始化 創建一個空字符串作為初始值: var emptyString = "" // 空字
(十四)Hibernate中的多表操作(4):單向一對一
odin utf-8 lds () clas string 方式 rdb style 案例一: 註解方式實現一對一 UserBean.java package bean; import java.io.Serializable; import javax.pers
Akka(4): Routers - 智能任務分配
相同 pac 線程 文件內容 fun bool fib can ceil Actor模式最大的優點就是每個Actor都是一個獨立的任務運算器。這種模式讓我們很方便地把一項大型的任務分割成若幹細小任務然後分配給不同的Actor去完成。優點是在設計時可以專註實現每個Ac
springBoot(4):日誌配置-logback
springboot 日誌配置-logback和log4j2 一、簡介支持日誌框架:Java Util Logging, Log4J2 and Logback,默認是使用logbacklogback配置方式spring boot默認會加載classpath:logback-spring.xml或者cl
使用bottle進行web開發(4):HTTPError
instead bject hat red uil tle ott class not from bottle import error @error(404) def error404(error): return ‘Nothing here, sorry‘
Java學習(4):統計一個文件中的英文,中文,數字,其他字符以及字符總數
port let args str reader 文件路徑 要求 cnblogs pub 要求:統計一個文件中的英文,中文,數字,其他字符以及字符總數(此隨筆以txt文件為例) import java.io.BufferedReader; import java.io.F
python基礎(4):條件語句與循環語句
語句 單分支 繼續 目的 輸入 代碼 原則 src 分享 今天我們看看條件語句與循環語句。 預習: 1、使用while循環輸出1 2 3 4 5 6 8 9 10 2、求1-100的所有數的和 3、輸出 1-100 內的所有奇數 4、輸出 1-100 內的所有偶數 5、求1
轉每天一個linux命令(4):mkdir命令
指定位置 cnblogs 同名 parent --help pos uri 不存在 必須 linux mkdir 命令用來創建指定的名稱的目錄,要求創建目錄的用戶在當前目錄中具有寫權限,並且指定的目錄名不能是當前目錄中已有的目錄。 1.命令格式: mkdir [選