违反ClassLoader双亲委派机制三部曲第二部——Tomcat类加载机制

转载自 违反ClassLoader双亲委派机制三部曲第二部——Tomcat类加载机制

前言:
本文是基于 ClassLoader双亲委派机制源码分析 了解过正统JDK类加载机制及其实现原理的基础上,进而分析这种思想如何应用到Tomcat这个web容器中,从源码的角度对 违反ClassLoader双亲委派机制三部曲之首部——JDBC驱动加载 中提出的Tomcat是如何完成多个web应用之间相互隔离,又如何保证多个web应用都能加载到基础类库的问题进行了解答,我们按如下的思路布局整篇文章:

  • 先给出Tomcat整体的类加载体系结构
  • 通过查看源码验证该类加载体系的正确性
  • 总结Tomcat如何设计保证多应用隔离
    另外本文是基于Tomcat7的源码进行分析的,因此读者最好先搭建一套基于Tomcat7的环境,以便查阅源码以及运行调试,可以按照该文章的方式进行搭建:Tomcat源码导入Idea

Tomcat类加载体系结构

图1. Tomcat整体类加载体系结构

Tomcat本身也是一个java项目,因此其也需要被JDK的类加载机制加载,也就必然存在引导类加载器、扩展类加载器和应用(系统)类加载器。Tomcat自身定义的类加载器主要由图中下半部分组成,Common ClassLoader作为Catalina ClassLoaderShared ClassLoader的parent,而Shared ClassLoader又可能存在多个children类加载器WebApp ClassLoader,一个WebApp ClassLoader实际上就对应一个Web应用,那Web应用就有可能存在Jsp页面,这些Jsp页面最终会转成class类被加载,因此也需要一个Jsp的类加载器,就是图中的JasperLoder
需要注意的是,在代码层面Catalina ClassLoaderShared ClassLoaderCommon ClassLoader对应的实体类实际上都是URLClassLoader或者SecureClassLoader,一般我们只是根据加载内容的不同和加载父子顺序的关系,在逻辑上划分为这三个类加载器;而WebApp ClassLoaderJasperLoader都是存在对应的类加载器类的
下面我们从源码设计的角度验证图中类加载器的设计

源码分析Tomcat类加载机制

Tomcat的启动入口在Bootstrap.class

 

图2. Tomcat启动入口

其中初始化类加载器的流程在bootstrap.init();中,如下“代码清单1


public void init()throws Exception{// Set Catalina pathsetCatalinaHome();setCatalinaBase();// (1)   初始化 classLoaderinitClassLoaders();Thread.currentThread().setContextClassLoader(catalinaLoader);SecurityClassLoad.securityClassLoad(catalinaLoader);// Load our startup class and call its process() methodif (log.isDebugEnabled())log.debug("Loading startup class");//加载 org.apache.catalina.startup.Catalina classClass<?> startupClass =catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");// (2)  实例化 Catalina 实例Object startupInstance = startupClass.newInstance();// Set the shared extensions class loaderif (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)处注释的代码主要进行类加载的初始化以及形成类加载器的关系初始化,继续跟进

图3. initClassLoaders()方法

这里红线处的代码实际上创建了三个ClassLoader对象,其名称和Tomcat类加载关系图中的类加载器高度一致,那么我们猜测createClassLoader(String,ClassLoader)方法可能就是创建Tomcat自定义类加载器的方法之一,继续往下看 “ 代码清单2


private ClassLoader createClassLoader(String name, ClassLoader parent)throws Exception {// (1) 根据名称查找特定的配置String value = CatalinaProperties.getProperty(name + ".loader");if ((value == null) || (value.equals("")))return parent;value = replace(value);List<Repository> repositories = new ArrayList<Repository>();StringTokenizer tokenizer = new StringTokenizer(value, ",");while (tokenizer.hasMoreElements()) {String repository = tokenizer.nextToken().trim();if (repository.length() == 0) {continue;}// Check for a JAR URL repositorytry {@SuppressWarnings("unused")URL url = new URL(repository);repositories.add(new Repository(repository, RepositoryType.URL));continue;} catch (MalformedURLException e) {// Ignore}// Local repositoryif (repository.endsWith("*.jar")) {repository = repository.substring(0, repository.length() - "*.jar".length());repositories.add(new Repository(repository, RepositoryType.GLOB));} else if (repository.endsWith(".jar")) {repositories.add(new Repository(repository, RepositoryType.JAR));} else {repositories.add(new Repository(repository, RepositoryType.DIR));}}// (2) 类加载器工厂创建特定类加载器return ClassLoaderFactory.createClassLoader(repositories, parent);}

代码清单中(1)处注释是根据上图中传递的“名称”加上后缀.loader去某个配置文件加载文件,为了突出重点,这里直接给出结论,其加载的内容为/org/apache/catalina/startup/catalina.properties,比如要加载 common.loader对应的value,其在文件中的值为${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar,也就是说Common ClassLoader要加载的路径是这些,是Tomcat运行要使用的公共组件,比如servlet-api.jarcatalina.jar等;而我们发现当要加载server.loadershared.loader时,其key在配置文件中的value为空,也就是说,默认情况下Catalina ClassLoader和Shared ClassLoader(Tomcat整体类加载体系结构图中红色虚线内)都不存在,只有Common ClassLoader
方法中的第二个参数表示创建类加载器的父类加载器是哪个,再看initClassLoaders()方法图中代码,在创建catalinaLoadersharedLoader时,父类加载器传入的实际上就是commonLoader,以此可以验证图1中Catalina ClassLoaderShared ClassLoaderCommon ClassLoader的父子关系。而common ClassLoader的父类加载器参数传递的为null,为什么null就会导致该类加载器的父类加载器为System ClassLoader呢?我们需要进入代码清单2中看注释(2)处标识的代码 代码清单3


    public static ClassLoader createClassLoader(List<Repository> repositories,final ClassLoader parent)throws Exception {if (log.isDebugEnabled())log.debug("Creating new class loader");// Construct the "class path" for this class loaderSet<URL> set = new LinkedHashSet<URL>();// 加载指定路径下的资源对象if (repositories != null) {for (Repository repository : repositories)  {if (repository.getType() == RepositoryType.URL) {URL url = buildClassLoaderUrl(repository.getLocation());if (log.isDebugEnabled())log.debug("  Including URL " + url);set.add(url);} else if (repository.getType() == RepositoryType.DIR) {File directory = new File(repository.getLocation());directory = directory.getCanonicalFile();if (!validateFile(directory, RepositoryType.DIR)) {continue;}URL url = buildClassLoaderUrl(directory);if (log.isDebugEnabled())log.debug("  Including directory " + url);set.add(url);} else if (repository.getType() == RepositoryType.JAR) {File file=new File(repository.getLocation());file = file.getCanonicalFile();if (!validateFile(file, RepositoryType.JAR)) {continue;}URL url = buildClassLoaderUrl(file);if (log.isDebugEnabled())log.debug("  Including jar file " + url);set.add(url);} else if (repository.getType() == RepositoryType.GLOB) {File directory=new File(repository.getLocation());directory = directory.getCanonicalFile();if (!validateFile(directory, RepositoryType.GLOB)) {continue;}if (log.isDebugEnabled())log.debug("  Including directory glob "+ directory.getAbsolutePath());String filenames[] = directory.list();if (filenames == null) {continue;}for (int j = 0; j < filenames.length; j++) {String filename = filenames[j].toLowerCase(Locale.ENGLISH);if (!filename.endsWith(".jar"))continue;File file = new File(directory, filenames[j]);file = file.getCanonicalFile();if (!validateFile(file, RepositoryType.JAR)) {continue;}if (log.isDebugEnabled())log.debug("    Including glob jar file "+ file.getAbsolutePath());URL url = buildClassLoaderUrl(file);set.add(url);}}}}// Construct the class loader itselffinal URL[] array = set.toArray(new URL[set.size()]);if (log.isDebugEnabled())for (int i = 0; i < array.length; i++) {log.debug("  location " + i + " is " + array[i]);}//  返回创建的类加载器return AccessController.doPrivileged(new PrivilegedAction<URLClassLoader>() {@Overridepublic URLClassLoader run() {if (parent == null)return new URLClassLoader(array);elsereturn new URLClassLoader(array, parent);}});}

大块的if中的代码实际上是对资源进行转化加载的过程,而return部分才是返回类加载器的部分,代码根据是否有parent调用了URLClassLoader不同的构造器,Common ClassLoader调用的是没有parent的构造器

图4. Common ClassLoader的parent创建的底层关键代码

按红线所画Common ClassLoader的parent实际上是JDK中sun.misc.Launcher.class类的loader成员变量,而在上一篇文章中已经知道该loader的值就是应用类加载器(系统类加载器)System ClassLoader。至此Tomcat中类加载机制和JDK的类加载机制也建立上了联系
现在Tomcat的类加载机制已完成了一大半,剩下用于加载每个web应用的类加载器WebApp ClassLoader的分析,这个时候需要重新回到代码清单1中看注释(2)以下的部分,其主要做的事情是通过反射创建了org.apache.catalina.startup.Catalina类的实例,然后调用了签名为void setParentClassLoader(ClassLoader parentClassLoader)的方法,并传入了Shared ClassLoader,上面我们说过默认情况下Shared ClassLoader就是Common ClassLoader,因此其传入的参数实际上是Common ClassLoader
我们思考既然有保存parent的方法,必定使用时会调用获得parent方法,那么我们需要查看Catalina类中ClassLoader getParentClassLoader()方法的调用栈(层级关系比较复杂,要紧跟主线不要迷失),最终定位到StandardContext中的synchronized void startInternal() throws LifecycleException方法(其中涉及到Tomcat的各种组件关系,生命周期管理等内容,将在下次分析Tomcat组件文章中详细介绍),下面是只保留核心逻辑的startInternal()方法 代码清单4


    protected synchronized void startInternal() throws LifecycleException {// 其他逻辑略......// Add missing components as necessaryif (webappResources == null) {   // (1) Required by Loaderif (log.isDebugEnabled())log.debug("Configuring default Resources");try {String docBase = getDocBase();if (docBase == null) {setResources(new EmptyDirContext());} else if (docBase.endsWith(".war")&& !(new File(getBasePath())).isDirectory()) {setResources(new WARDirContext());} else {setResources(new FileDirContext());}} catch (IllegalArgumentException e) {log.error(sm.getString("standardContext.resourcesInit"), e);ok = false;}}if (ok) {if (!resourcesStart()) {throw new LifecycleException("Error in resourceStart()");}}// (1)  为每一个web应用创建一个WebappLoaderif (getLoader() == null) {WebappLoader webappLoader = new WebappLoader(getParentClassLoader());webappLoader.setDelegate(getDelegate());setLoader(webappLoader);}// 其他逻辑略......try {if (ok) {// (2)  调用WebappLoader的start// Start our subordinate components, if anyif ((loader != null) && (loader instanceof Lifecycle))((Lifecycle) loader).start();}// 其他逻辑省略......} finally {// Unbinding threadunbindThread(oldCCL);}}

(1)处注释下的代码逻辑就是为每一个web应用创建一个类加载器,该类加载器的父类加载器就是通过getParentClassLoader()得到的Shared ClassLoader(Common ClassLoader),(2)处代码调用了WebappLoaderstart方法,继续跟进


    protected void startInternal() throws LifecycleException {// 其他逻辑省略.....try {//创建类加载器关键方法classLoader = createClassLoader();classLoader.setResources(container.getResources());classLoader.setDelegate(this.delegate);classLoader.setSearchExternalFirst(searchExternalFirst);if (container instanceof StandardContext) {classLoader.setAntiJARLocking(((StandardContext) container).getAntiJARLocking());classLoader.setClearReferencesRmiTargets(((StandardContext) container).getClearReferencesRmiTargets());classLoader.setClearReferencesStatic(((StandardContext) container).getClearReferencesStatic());classLoader.setClearReferencesStopThreads(((StandardContext) container).getClearReferencesStopThreads());classLoader.setClearReferencesStopTimerThreads(((StandardContext) container).getClearReferencesStopTimerThreads());classLoader.setClearReferencesHttpClientKeepAliveThread(((StandardContext) container).getClearReferencesHttpClientKeepAliveThread());}// 其他逻辑省略.....}

由于Tomcat的设计,WebappLoaderstart方法实际上调用的是父类的模板,而模板中的startinternal方法由各个子类具体实现,其中最关键的方法为createClassLoader()

图5. WebappLoader中createClassLoader方法

上图中的loadClass成员变量的值为org.apache.catalina.loader.WebappClassLoader,所以,实际上该类为每一个web应用创建了一个WebappClassLoader的实例,该实例的parent就是Shared ClassLoader或者Common ClassLoader,至此WebApp ClassLoader在图1中的位置也得以验证。
从理论上分析来看,由于类加载的“双亲委派”机制,一个类加载器只能加载本加载器指定的目录以及使用有“继承”关系的父类加载器加载过的类,而Tomcat为每一个Web应用创建了一个WebappClassLoader,不同的WebappClassLoader是同级关系,不会存在交叉访问的问题,从而达到web应用相互隔离的目的。
那Tomcat是否没有"破坏"双亲委派机制呢?我们通过查看WebappClassLoader及其父类WebappClassLoaderBaseloadClass()findClass()分析一下Tomcat加载web应用相关类的策略


public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLockInternal(name)) {if (log.isDebugEnabled())log.debug("loadClass(" + name + ", " + resolve + ")");Class<?> clazz = null;// Log access to stopped classloaderif (!started) {try {throw new IllegalStateException();} catch (IllegalStateException e) {log.info(sm.getString("webappClassLoader.stopped", name), e);}}//                (1)          // Check our previously loaded local class cacheclazz = findLoadedClass0(name);if (clazz != null) {if (log.isDebugEnabled())log.debug("  Returning class from cache");if (resolve)resolveClass(clazz);return (clazz);}//              (2)//  Check our previously loaded class cacheclazz = findLoadedClass(name);if (clazz != null) {if (log.isDebugEnabled())log.debug("  Returning class from cache");if (resolve)resolveClass(clazz);return (clazz);}//                (3)//  Try loading the class with the system class loader, to prevent//       the webapp from overriding J2SE classestry {clazz = j2seClassLoader.loadClass(name);if (clazz != null) {if (resolve)resolveClass(clazz);return (clazz);}} catch (ClassNotFoundException e) {// Ignore}//  Permission to access this class when using a SecurityManagerif (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;if (name.endsWith("BeanInfo")) {// BZ 57906: suppress logging for calls from// java.beans.Introspector.findExplicitBeanInfo()log.debug(error, se);} else {log.info(error, se);}throw new ClassNotFoundException(error, se);}}}//              (4)boolean delegateLoad = delegate || filter(name);//              (5)//  Delegate to our parent if requestedif (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}}//            (6)//  Search local repositoriesif (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}// Delegate to parent unconditionallyif (!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}}}throw new ClassNotFoundException(name);}

我们首先定位到WebappClassLoaderBaseloadClass方法,(1)处首先看name对应的类是否存在缓存中,缓存是一个ConcurrentHashMap<String, ResourceEntry>的集合,如果没有缓存执行(2)处逻辑,从JVM中查找是否曾今加载过该类,(3)中的代码保证自定义类不会覆盖java基础类库中的类,(4)的逻辑就是是否进行双亲委派的分叉口,其中delegate默认为false,那么就要看filter(String)方法,该方法的内部实际上将待加载类的全路径名称和一个成员变量protected static final String[] packageTriggers中的类名进行比较,如果待加载的类名和packageTriggers数组中的内容前缀匹配,则需要委派父类加载,即执行(5)处代码,否则执行(6),调用重写的findClass(String)方法加载该类


public Class<?> findClass(String name) throws ClassNotFoundException {// 其他代码略去.....// Ask our superclass to locate this class, if possible// (throws ClassNotFoundException if it is not found)Class<?> clazz = null;try {if (log.isTraceEnabled())log.trace("      findClassInternal(" + name + ")");//        (1)if (hasExternalRepositories && searchExternalFirst) {try {clazz = super.findClass(name);} catch(ClassNotFoundException cnfe) {// Ignore - will search internal repositories next} catch(AccessControlException ace) {log.warn("WebappClassLoaderBase.findClassInternal(" + name+ ") security exception: " + ace.getMessage(), ace);throw new ClassNotFoundException(name, ace);} catch (RuntimeException e) {if (log.isTraceEnabled())log.trace("      -->RuntimeException Rethrown", e);throw e;}}//            (2)if ((clazz == null)) {try {clazz = findClassInternal(name);} catch(ClassNotFoundException cnfe) {if (!hasExternalRepositories || searchExternalFirst) {throw cnfe;}} catch(AccessControlException ace) {log.warn("WebappClassLoaderBase.findClassInternal(" + name+ ") security exception: " + ace.getMessage(), ace);throw new ClassNotFoundException(name, ace);} catch (RuntimeException e) {if (log.isTraceEnabled())log.trace("      -->RuntimeException Rethrown", e);throw e;}}//其他代码略去........return (clazz);}

(1)处由于hasExternalRepositoriessearchExternalFirst默认为false,因此执行(2)处逻辑,调用findClassInternal(String)方法

图6. WebappClassLoader类的findClassInternal方法

其主要的思想是根据待加载类的全路径读取该类的二进制数据,进而进行类的预定义、class source的解析等,将该类加载到JVM中
综上所述,我认为Tomcat的类加载机制不能算完全“正统”的双亲委派,WebappClassLoader内部重写了loadClassfindClass方法,实现了绕过“双亲委派”直接加载web应用内部的资源,当然可以通过在Context.xml文件中加上<Loader delegate = "true">开启正统的“双亲委派”加载机制

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/327062.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

红包的技术升级之旅

鸡年春节&#xff0c;红包再次成为年味儿最重要的催化剂。先是腾讯QQ钱包推出“LBSAR天降红包”等三种创新有趣的玩法&#xff0c;支付宝上线AR实景红包&#xff0c;微博亦推出视频红包等形式。虽然微信退出红包营销让人稍有意外&#xff0c;但用户对红包的热情仍未消减。 事实…

java中生成1000~10000之间的随机数

要生成在[min,max]之间的随机整数&#xff0c;可使用Random类进行相关运算&#xff1a; Random random new Random(); int s random.nextInt(max)%(max-min1) min; random.nextInt(max)表示生成[0,max]之间的随机数&#xff0c;然后对(max-min1)取模。 以生成[1000,10000]…

C# 7.0新功能

下面是对C#7.0 版本所有语言功能的描述。随着 Visual Studio “15” preview 4 的发布&#xff0c;大部分功能可以被更灵活的应用。现在正是时候将这些功能介绍给大家&#xff0c;你也可以借此让我们知道你的想法。 C#7.0 增加了很多新的功能&#xff0c;更专注于数据的消费&am…

MybatisPlus学习(四)条件构造器Wrapper方法详解

https://www.cnblogs.com/xianz666/p/13857733.html MybatisPlus学习&#xff08;四&#xff09;条件构造器Wrapper方法详解 文章目录 1、条件构造器2、QueryWrapper 2.1、eq、ne2.2、gt、ge、lt、le2.3、between、notBetween2.4、like、notLike、likeLeft、likeRight2.4、isN…

使用ueditor实现多图片上传案例

UEditor是由百度WEB前端研发部开发的所见即所得的开源富文本编辑器&#xff0c;具有轻量、可定制、用户体验优秀等特点。开源基于BSD协议&#xff0c;所有源代码在协议允许范围内可自由修改和使用。百度UEditor的推出&#xff0c;可以帮助不少网站开发者在开发富文本编辑器所遇…

自定义ClassLoader和双亲委派机制

转载自 自定义ClassLoader和双亲委派机制 ClassLoader ClassLoad&#xff1a;类加载器&#xff08;class loader&#xff09;用来加载 Java 类到 Java 虚拟机中。Java 源程序&#xff08;.java 文件&#xff09;在经过 Java 编译器编译之后就被转换成 Java 字节代码&#xff0…

ASP.NET Core 1.0 开发记录

ASP.NET Core 1.0 更新比较快&#xff08;可能后面更新就不大了&#xff09;&#xff0c;阅读注意时间节点&#xff0c;这篇博文主要记录用 ASP.NET Core 1.0 开发简单应用项目的一些记录&#xff0c;以备查阅。 ASP.NET Core 1.0 相关 Nuget 程序包源&#xff1a;https://api.…

深入浅出ClassLoader

转载自 深入浅出ClassLoader 你真的了解ClassLoader吗&#xff1f; 这篇文章翻译自zeroturnaround.com的 Do You Really Get Classloaders? &#xff0c;融入和补充了笔者的一些实践、经验和样例。本文的例子比原文更加具有实际意义&#xff0c;文字内容也更充沛一些&#xf…

微软任命LinkedIn高级副总裁为首席技术官

Kevin Scott曾是LinkedIn工程方面的高级VP&#xff0c;被任命为微软CTO后&#xff0c;Scott将全面统筹微软战略规划&#xff0c;以主动的姿态推进公司间合作&#xff0c;以最大化微软在伙伴及客户间的影响力。据了解&#xff0c;该职位为新创职位&#xff0c;为微软公司级CTO&a…

jQuery 基础教程 (三)之jQuery的选择器

一、jQuery 选择器 &#xff08;1&#xff09;选择器是 jQuery 的根基, 在 jQuery 中, 对事件处理, 遍历 DOM 和 Ajax 操作都依赖于选择器 &#xff08;2&#xff09;jQuery 选择器的优点: 简洁的写法 $(#id) //documnet.getElementById(id); $(p) //documnet.getEl…

SQL Server 2014内存优化表的使用场景

最近一个朋友找到走起君&#xff0c;咨询走起君内存优化表如何做高可用的问题 大家知道&#xff0c;内存优化表是从SQL Server 2014开始引入&#xff0c;可能大家对内存优化表还是比较陌生&#xff0c;网上也鲜有内存优化表使用场景的文章 朋友公司做的业务是跟蜂鸟配送类似的配…

春节祝福提前到

2017 鸡 年 大 吉 HAPPY NEW YEAR 鸡年起算自二十四节气之立春&#xff0c;因为生肖年依附于干支纪年&#xff0c;而干支纪年又是干支历的纪年方法。历代官方历书&#xff08;即黄历&#xff09;皆如此。农历只是借用干支来纪年&#xff0c;和干支历是两种不同的历法&#xf…

jQuery 基础教程 (一)之jQuery的由来及简介

一、RIA技术 &#xff08;1&#xff09;RIA(Rich Internet Applications) 富互联网应用,具有高度互动性、丰富用户体验以及功能强大的客户端。 &#xff08;2&#xff09;常见的RIA技术 Ajax Flex Sliverlight &#xff08;3&#xff09;JavaScript及其框架是实现RIA的重…

外键

如图有两张表&#xff0c;classId 是T_Student的外键&#xff0c;是T_class 表的主键&#xff0c; 如果我们要删除T_class 表中classId为1的字段&#xff0c;程序是会报错的&#xff0c;因为t_student表中有数据和classId为1的字段关联了&#xff0c;是不能删除的&#xff0c;这…

新春大吉,2017 Make .NET Great

今天年初六&#xff0c;新春好景象&#xff0c;送礼处处有新意。这个春节暂停了几天的公众号更新&#xff0c;今天就和大家回顾下最近几天发生在我们身边的.NET 圈里的信息。 1、 微软正式公布了.NET Core SDK 1.0 RC3的信息&#xff0c;其实早已包含在最近更新的Visual Studio…

头条面试题:请谈谈Redis 9种数据结构以及它们的内部编码实现

转载自 头条面试题&#xff1a;请谈谈Redis 9种数据结构以及它们的内部编码实现 90%的人知道Redis 5种最基本的数据结构&#xff1b; 只有不到10%的人知道8种基本数据结构&#xff0c;5种基本bitmapGeoHashHyperLogLog&#xff1b; 只有不到5%的人知道9种基本数据结构&…

【初码干货】关于.NET玩爬虫这些事

这几天在微信群里又聊到.NET可以救中国但是案例太少不深的问题&#xff0c;我说.NET玩爬虫简直就是宇宙第一&#xff0c;于是大神朱永光说&#xff0c;你为何不来写一篇总结一下&#xff1f; 那么今天就全面的来总结一下&#xff0c;在.NET生态下&#xff0c;如何玩爬虫 关于爬…

【JVM】浅谈双亲委派和破坏双亲委派

转载自 【JVM】浅谈双亲委派和破坏双亲委派 一、前言 笔者曾经阅读过周志明的《深入理解Java虚拟机》这本书&#xff0c;阅读完后自以为对jvm有了一定的了解&#xff0c;然而当真正碰到问题的时候&#xff0c;才发现自己读的有多粗糙&#xff0c;也体会到只有实践才能加深理…