Tomcat —— 正统的类加载器结构
为一个功能健全的Web服务器,都要解决 如下的这些问题:
- 部署在同一个服务器上的 两个Web应用程序 所使用的 Java类库可以实现相互隔离。这是最基本的需求,两个不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求每个类库在一个服务器中只能有一份,服务器应当能够保证两个独立应用程序的类库可以互相独立使用。
- 部署在同一个服务器上的 两个Web应用程序 所使用的 Java类库可以互相共享。这个需求与前面一 点正好相反,但是也很常见,例如用户可能有10个使用Spring组织的应用程序部署在同一台服务器上,如果把10份Spring分别存放在各个应用程序的隔离目录中,将会是很大的资源浪费——这主要倒不是浪费磁盘空间的问题,而是指类库在使用时都要被加载到服务器内存,如果类库不能共享,虚拟机的方法区就会很容易出现过度膨胀的风险。
- 服务器需要尽可能地保证自身的安全不受部署的Web应用程序影响。目前,有许多主流的Java Web服务器自身也是使用Java语言来实现的。因此服务器本身也有类库依赖的问题,一般来说,基于安全考虑,服务器所使用的类库应该与应用程序的类库互相独立。
- 支持JSP应用的Web服务器,十有八九都需要支持HotSwap功能。我们知道JSP文件最终要被编译 成Java的Class文件才能被虚拟机执行,但JSP文件由于其纯文本存储的特性,被运行时修改的概率远大于第三方类库或程序自己的Class文件。而且ASP、PHP和JSP这些网页应用也把修改后无须重启作为一个很大的“优势”来看待,因此“主流”的Web服务器都会支持JSP生成类的热替换。
所以各种Web服务器都不约而同地提供了好几个有着不同含义的ClassPath路径供用户存放第三方类库,这些路径一般 会以“lib”或“classes”命名。被放置到不同路径中的类库,具备不同的访问范围和服务对象。
从图中的委派关系中可以看出(父类加载器类库的可以被子类加载器使用,同级类加载器的类库相互隔离)
Common类加载器能加载的类都可以被Catalina类加载器和Shared 类加载器使用。
Catalina类加载器和Shared类加载器自己能加载的类则与对方相互隔离。
WebApp类加载器可以使用Shared类加载器加载到的类,但各个WebApp类加载器实例之间相互隔离。
JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个Class文件,它存在的目的就是为了被丢弃:当服务器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并通过再建立一个新 的JSP类加载器来实现JSP文件的HotSwap功能。
- Common类加载器 用来加载:放置在/common目录中。类库可被Tomcat和所有的Web应用程序共同使用。
- Catalina类加载器(也称为Server类 加载器)用来加载:放置在/server目录中。类库可被Tomcat使用,对所有的Web应用程序都不可见。
- Shared类加载器 用来加载:放置在/shared目录中。类库可被所有的Web应用程序共同使用,但对Tomcat自己不可见。
- Webapp类加载器 用来加载:放置在/WebApp/WEB-INF目录中。类库仅仅可以被该Web应用程序使用,对Tomcat和其他Web应 用程序都不可见。
字节码生成技术与动态代理的实现
1、代理模式
为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。
通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间。
2、Springboot 通过动态代理实现 AOP
springboot团队默认的代理模式是 cglib 代理,看看spring的官方团队是怎么解释的
This was changed in 1.4 (see https://github.com/spring-projects/spring-boot/issues/5423). We’ve generally found cglib proxies less likely to cause unexpected cast exceptions.
他们认为使用cglib更不容易出现转换错误
3、JDK 动态代理
在静态代理中,代理类(RenterProxy)是自己已经定义好了的,在程序运行之前就已经编译完成。
而动态代理是在运行时根据我们在Java代码中的“指示”动态生成的。所谓动态代理 DynamicProxy是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些 interface。你当然可以把该class的实例当作这些interface中的任何一个来用。当然,这个DynamicProxy其实就是一个Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。在使用动态代理类时,必须实现InvocationHandler接口。
Java动态代理类位于java.lang.reflect包下,一般主要涉及到以下两个类:
(1) Interface InvocationHandler:该接口中仅定义了一个方法
public object invoke(Object obj,Method method, Object[] args)
在实际使用时,第一个参数obj一般是指代理类,method是被代理的方法,args为该方法的参数数组。这个抽象方法在代理类中动态实现。
(2) Proxy:该类即为动态代理类,其中主要包含以下内容:
- protected Proxy(InvocationHandler h):构造函数,用于给内部的h赋值。
- static Class getProxyClass (ClassLoaderloader, Class[] interfaces):获得一个代理类,其中loader是类装载器,interfaces是真实类所拥有的全部接口的数组。
- static Object newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h):返回代理类的一个实例,返回后的代理类可以当作被代理类使用。
动态代理步骤:
1.创建一个实现接口InvocationHandler的类,它必须实现invoke方法
2.通过Proxy的静态方法
newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理对象
3.通过代理对象调用方法
4、JDK 动态代理实例
1、需要动态代理的接口
package jiankunking; /** * 需要动态代理的接口 */ public interface Subject { /** * 你好 * * @param name * @return */ public String SayHello(String name); /** * 再见 * * @return */ public String SayGoodBye(); }
2、需要代理的实际对象
package jiankunking; /** * 实际对象 */ public class RealSubject implements Subject { /** * 你好 * * @param name * @return */ public String SayHello(String name) { return "hello " + name; } /** * 再见 * * @return */ public String SayGoodBye() { return " good bye "; } }
3、代理类关联的 handler 对象。实现 InvocationHandler 接口,实现 invoke 方法,构造方法传入真实类。
它的 invoke 方法用来替代理类做真正的工作:invoke() 里除了调用真实对象的方法还可以做些别的
package jiankunking; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * 调用处理器实现类 * 每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象 */ public class InvocationHandlerImpl implements InvocationHandler { /** * 这个就是我们要代理的真实对象 */ private Object realSubject; /** * 构造方法,给我们要代理的真实对象赋初值 * * @param subject */ public InvocationHandlerImpl(Object realSubject) { this.realSubject = realSubject; } /** * 该方法负责集中处理动态代理类上的所有方法调用。 * 调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行 * * @param proxy 代理类实例 * @param method 被调用的方法对象 * @param args 调用参数 * @return * @throws Throwable */
@override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //在代理真实对象前我们可以添加一些自己的操作 System.out.println("在调用之前,我要干点啥呢?"); System.out.println("Method:" + method); //当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用 Object returnValue = method.invoke(realSubject, args); //在代理真实对象后我们也可以添加一些自己的操作 System.out.println("在调用之后,我要干点啥呢?"); return returnValue; } }
4、生成代理类,用代理类进行方法调用
JDK生成的最终真正的代理类,它继承自Proxy并和真实类实现了同一个接口(前面定义的 Subject 接口)。
在代理类实现的Subject接口方法的内部,通过反射调用了InvocationHandlerImpl的invoke方法。
package jiankunking; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; /** * 动态代理演示 */ public class DynamicProxyDemonstration { public static void main(String[] args) { //代理的真实对象 Subject realSubject = new RealSubject(); /** * InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发 * 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用. * 即:要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法 */ InvocationHandler handler = new InvocationHandlerImpl(realSubject); ClassLoader loader = realSubject.getClass().getClassLoader(); Class[] interfaces = realSubject.getClass().getInterfaces(); /** * 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例 */ Subject subject = (Subject) Proxy.newProxyInstance(loader, interfaces, handler); System.out.println("动态代理对象的类型:"+subject.getClass().getName()); String hello = subject.SayHello("jiankunking"); System.out.println(hello); }
}
标签:类库,Web,调用,对象,代理,JVM,子系统,加载 From: https://www.cnblogs.com/suBlog/p/17398225.html