tomcat打破双亲委派机制
1.先在Web用本地目录下查找要加载的类
2.如果没找到,交给父加载器去查找,它的父加载器就是系统类加载器AppClassLoader
3.如果附加在其也没找到,则抛出ClassNotFound异常
Tomcat中自定义的类加载器
Tomcat 拥有不同的⾃定义类加载器,以实现对各种资源库的控制。一般来说,Tomcat 主要用类加载器解决以下 4 个问题。
- 同一个Tomcat中,各个Web应⽤之间各⾃使⽤的Java类库要互相隔离。
- 同一个Tomcat中,各个Web应⽤之间可以提供共享的Java类库。
- 为了使Tomcat不受Web应用的影响,应该使服务器的类库与应⽤程序的类库互相独立。
- Tomcat支持热部署。
在 Tomcat中,最重要的一个类加载器是 Common 类加载器,它的父类加载器是应用程序类加载器,负责加载 $CATALINA_ BASE/lib、$CATALINA_HOME/lib 两个目录下所有的.class 文件与.jar 文件。
Tomcat中一般会有多个WebApp类加载器-WebAppClassLoader ,每个类加载器负责加载一个 Web 程序。它的父类加载器是Common类加载器。
由于每个 Web 应该都有多个的 WebApp 类加载器,很好地使多个 Web 应用程序之间互相隔离且能通过创建新的 WebApp类加载器达到热部署。这种类加载器结构能有效使 Tomcat 不受 Web 应用程序影响,而 Common 类加载器的存在使多个 Web 应⽤程序能够互相共享类库。
安全机制
Tomcat中设置了了一些安全策略,默认的策略文件为conf/catalina.policy
Tomcat中设置了安全策略,规定了Tomcat在运⾏过程中拥有的权限,Tomcat管理者可以修改该权限,但是Tomcat中有⼀些类是必须能够被访问到的,所有Tomcat中在启动过程中会提前去加载这些类,如果发现没有对应的权限,那么将会启动失败
tomcat类加载源码:
org.apache.catalina.loader.WebappClassLoaderBase#loadClass(java.lang.String, boolean)
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
if (log.isDebugEnabled())
log.debug("loadClass(" + name + ", " + resolve + ")");
Class<?> clazz = null;
// Log access to stopped class loader
checkStateForClassLoading(name);
//todo 1.现在本地cache中查找该类有没有加载过,
// (0) Check our previously loaded local class cache
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return clazz;
}
//todo 2 从系统类加载器的cache中查找是否加载过
// (0.1) Check our previously loaded class cache
clazz = findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return clazz;
}
// (0.2) Try loading the class with the system class loader, to prevent
// the webapp from overriding Java SE classes. This implements
// SRV.10.7.2
String resourceName = binaryNameToPath(name, false);
ClassLoader javaseLoader = getJavaseClassLoader();
boolean tryLoadingFromJavaseLoader;
try {
// Use getResource as it won't trigger an expensive
// ClassNotFoundException if the resource is not available from
// the Java SE class loader. However (see
// https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for
// details) when running under a security manager in rare cases
// this call may trigger a ClassCircularityError.
// See https://bz.apache.org/bugzilla/show_bug.cgi?id=61424 for
// details of how this may trigger a StackOverflowError
// Given these reported errors, catch Throwable to ensure any
// other edge cases are also caught
URL url;
if (securityManager != null) {
PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName);
url = AccessController.doPrivileged(dp);
} else {
url = javaseLoader.getResource(resourceName);
}
tryLoadingFromJavaseLoader = (url != null);
} catch (Throwable t) {
// Swallow all exceptions apart from those that must be re-thrown
ExceptionUtils.handleThrowable(t);
// The getResource() trick won't work for this class. We have to
// try loading it directly and accept that we might get a
// ClassNotFoundException.
tryLoadingFromJavaseLoader = true;
}
//todo 3.尝试用extClassLoader加载
if (tryLoadingFromJavaseLoader) {
try {
clazz = javaseLoader.loadClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
// (0.5) Permission to access this class when using a SecurityManager
if (securityManager != null) {
int i = name.lastIndexOf('.');
if (i >= 0) {
try {
securityManager.checkPackageAccess(name.substring(0,i));
} catch (SecurityException se) {
String error = "Security Violation, attempt to use " +
"Restricted Class: " + name;
log.info(error, se);
throw new ClassNotFoundException(error, se);
}
}
}
boolean delegateLoad = delegate || filter(name, true);
//todo 5.尝试使用系统类加载器appCLassLoader来加载
// (1) Delegate to our parent if requested
if (delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader1 " + parent);
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from parent");
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
//todo 4.尝试在本地目录搜索class并加载
// (2) Search local repositories
if (log.isDebugEnabled())
log.debug(" Searching local repositories");
try {
clazz = findClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from local repository");
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
//todo 5.尝试用系统类加载器(也就是appClassLoader来加载)
// (3) Delegate to parent unconditionally
if (!delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader at end: " + parent);
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from parent");
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
}
// todo 6.还是找不到,则抛出异常
throw new ClassNotFoundException(name);
}
tomcat如何加载spring
通过org.springframework.web.context.ContextLoaderListener加载spring的bean,通过org.springframework.web.servlet.DispatcherServlet加载springmvc的方法
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<context-param>
<param-name>contextConfigLocation</param-name>
<!-- spring-bean的扫描路径-->
<param-value>classpath:spring/applicationContext.xml</param-value>
</context-param>
<listener>
<!--当调用standardContext的startInnner时候,会初始化webApp加载器,扫描web.xml文件配置的listener,即ContextLoaderListener
完成找到classpath:spring/applicationContext.xml中指定的扫描路径,初始化类
-->
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--DispatcherServlet-->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!--配置springmvc的扫描路径-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
springboot的机制也是一样的,因为也是tomcat拉起了webapp和spring