热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Hibernate延迟加载深入分析-集合属性的延迟加载策略

本文深入分析了Hibernate延迟加载的机制,特别是集合属性的延迟加载策略。通过延迟加载,可以降低系统的内存开销,提高Hibernate的运行性能。对于集合属性,推荐使用延迟加载策略,即在系统需要使用集合属性时才从数据库装载关联的数据,避免一次加载所有集合属性导致性能下降。

Hibernae 的延迟加载是一个非常常用的技术,实体的集合属性默认会被延迟加载,实体所关联的实体默认也会被延迟加载。Hibernate 通过这种延迟加载来降低系统的内存开销,从而保证 Hibernate 的运行性能。

下面先来剖析 Hibernate 延迟加载的“秘密”。

集合属性的延迟加载

当 Hibernate 从数据库中初始化某个持久化实体时,该实体的集合属性是否随持久化类一起初始化呢?如果集合属性里包含十万,甚至百万的记录,在初始化持久化实体的同时, 完成所有集合属性的抓取,将导致性能急剧下降。完全有可能系统只需要使用持久化类集合属性中的部分记录,而完全不是集合属性的全部,这样,没有必要一次加 载所有的集合属性。

对于集合属性,通常推荐使用延迟加载策略。所谓延迟加载就是等系统需要使用集合属性时才从数据库装载关联的数据。

例如下面 Person 类持有一个集合属性,该集合属性里的元素的类型为 Address,该 Person 类的代码片段如下:


清单 1. Person.java

Displaycode代码  收藏代码
  1.                
  2. public class Person   
  3. {   
  4. // 标识属性  
  5. private Integer id;   
  6. // Person 的 name 属性  
  7. private String name;   
  8. // 保留 Person 的 age 属性  
  9. private int age;   
  10. // 使用 Set 来保存集合属性  
  11. private Set
     addresses = new HashSet
    ();   
  12. // 下面省略了各属性的 setter 和 getter 方法  
  13. ...   
  14. }   

 

为了让 Hibernate 能管理该持久化类的集合属性,程序为该持久化类提供如下映射文件:


清单 2. Person.hbm.xml

Displaycode代码  收藏代码
  1.                    
  2.  "1.0" encoding="GBK"?>   
  3.  
  4. "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
  5. "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">   
  6.  "org.crazyit.app.domain">   
  7.     
  8.  "Person" table="person_inf">   
  9.     
  10.  "id" column="person_id">   
  11.     
  12.  "identity"/>   
  13.     
  14.     
  15.  "name" type="string"/>   
  16.  "age" type="int"/>   
  17.     
  18.  "addresses" table="person_address" lazy="true">   
  19.     
  20.  "person_id"/>   
  21.  "Address">   
  22.     
  23.  "detail"/>   
  24.     
  25.  "zip"/>   
  26.     
  27.     
  28.     
  29.     

 

从上面映射文件的代码可以看出,Person 的集合属性中的 Address 类只是一个普通的 POJO。该 Address 类里包含 detail、zip 两个属性。由于 Address 类代码非常简单,故此处不再给出该类的代码。

上面映射文件中 元素里的代码指定了 lazy="true"(对于 元素来说,lazy="true"是默认值),它指定 Hibernate 会延迟加载集合属性里 Address 对象。

例如通过如下代码来加载 ID 为 1 的 Person 实体:

Displaycode代码  收藏代码
  1. Session session = sf.getCurrentSession();   
  2. Transaction tx = session.beginTransaction();   
  3. Person p &#61; (Person) session.get(Person.class, 1);  //<1>   
  4. System.out.println(p.getName());   

 

上面代码只是需要访问 ID 为 1 的 Person 实体&#xff0c;并不想访问这个 Person 实体所关联的 Address 对象。此时有两种情况&#xff1a;

  • 如果不延迟加载&#xff0c;Hibernate 就会在加载 Person 实体对应的数据记录时立即抓取它关联的 Address 对象。
  • 如果采用延迟加载&#xff0c;Hibernate 就只加载 Person 实体对应的数据记录。

很明显&#xff0c;第二种做法既能减少与数据库的交互&#xff0c;而且避免了装载 Address 实体带来的内存开销——这也是 Hibernate 默认启用延迟加载的原因。

现在的问题是&#xff0c;延迟加载到底是如何实现的呢&#xff1f; Hibernate 在加载 Person 实体时&#xff0c;Person 实体的 addresses 属性值是什么呢&#xff1f;

为了解决这个问题&#xff0c;我们在 <1>号代码处设置一个断点&#xff0c;在 Eclipse 中进行 Debug&#xff0c;此时可以看到 Eclipse 的 Console 窗口有如图 1 所示的输出&#xff1a;


图 1. 延迟加载集合属性的 Console 输出
图 1. 延迟加载集合属性的 Console 输出 

正如图 1 输出所看到的&#xff0c;此时 Hibernate 只从 Person 实体对应的数据表中抓取数据&#xff0c;并未从 Address 对象对应的数据表中抓取数据&#xff0c;这就是延迟加载。

那么 Person 实体的 addresses 属性是什么呢&#xff1f;此时可以从 Eclipse 的 Variables 窗口看到如图 2 所示的结果&#xff1a;


图 2. 延迟加载的集合属性值
图 2. 延迟加载的集合属性值 

从图 2 的方框里的内容可以看出&#xff0c;这个 addresses 属性并不是我们熟悉的 HashSet、TreeSet 等实现类&#xff0c;而是一个 PersistentSet 实现类&#xff0c;这是 Hibernate 为 Set 接口提供的一个实现类。

PersistentSet 集合对象并未真正抓取底层数据表的数据&#xff0c;因此自然也无法真正去初始化集合里的 Address 对象。不过 PersistentSet 集合里持有一个 session 属性&#xff0c;这个 session 属性就是 Hibernate Session&#xff0c;当程序需要访问 PersistentSet 集合元素时&#xff0c;PersistentSet 就会利用这个 session 属性去抓取实际的 Address 对象对应的数据记录。

那么到底抓取那些 Address 实体对应的数据记录呢&#xff1f;这也难不倒 PersistentSet&#xff0c;因为 PersistentSet 集合里还有一个 owner 属性&#xff0c;该属性就说明了 Address 对象所属的 Person 实体&#xff0c;Hibernate 就会去查找 Address 对应数据表中外键值参照到该 Person 实体的数据。

例如我们单击图 2 所示窗口中 addresses 行&#xff0c;也就是告诉 Eclipse 要调试、输出 addresses 属性&#xff0c;这就是要访问 addresses 属性了&#xff0c;此时就可以在 Eclipse 的 Console 窗口看到输出如下 SQL 语句&#xff1a;

Displaycode代码  收藏代码
  1. select   
  2.     addresses0_.person_id as person1_0_0_,   
  3.     addresses0_.detail as detail0_,   
  4.     addresses0_.zip as zip0_   
  5. from   
  6.     person_address addresses0_   
  7. where   
  8.     addresses0_.person_id&#61;?   

 

这就是 PersistentSet 集合跟据 owner 属性去抓取特定 Address 记录的 SQL 语句。此时可以从 Eclipse 的 Variables 窗口看到图 3 所示的输出&#xff1a;


图 3. 已加载的集合属性值
图 3. 已加载的集合属性值 

从图 3 可以看出&#xff0c;此时的 addresses 属性已经被初始化了&#xff0c;集合里包含了 2 个 Address 对象&#xff0c;这正是 Person 实体所关联的两个 Address 对象。

通过上面介绍可以看出&#xff0c;Hibernate 对于 Set 属性延迟加载关键就在于 PersistentSet 实现类。在延迟加载时&#xff0c;开始 PersistentSet 集合里并不持有任何元素。但 PersistentSet 会持有一个 Hibernate Session&#xff0c;它可以保证当程序需要访问该集合时“立即”去加载数据记录&#xff0c;并装入集合元素。

与 PersistentSet 实现类类似的是&#xff0c;Hibernate 还提供了 PersistentList、PersistentMap、PersistentSortedMap、PersistentSortedSet 等实现类&#xff0c;它们的功能与 PersistentSet 的功能大致类似。

熟悉 Hibernate 集合属性读者应该记得&#xff1a;Hibernate 要求声明集合属性只能用 Set、List、Map、SortedSet、SortedMap 等接口&#xff0c;而不能用 HashSet、ArrayList、HashMap、TreeSet、TreeMap 等实现类&#xff0c;其原因就是因为 Hibernate 需要对集合属性进行延迟加载&#xff0c;而 Hibernate 的延迟加载是依靠 PersistentSet、PersistentList、PersistentMap、PersistentSortedMap、 PersistentSortedSet 来完成的——也就是说&#xff0c;Hibernate 底层需要使用自己的集合实现类来完成延迟加载&#xff0c;因此它要求开发者必须用集合接口、而不是集合实现类来声明集合属性。

Hibernate 对集合属性默认采用延迟加载&#xff0c;在某些特殊的情况下&#xff0c;为 等元素设置 lazy&#61;"false"属性来取消延迟加载。

回页首

关联实体的延迟加载

默认情况下&#xff0c;Hibernate 也会采用延迟加载来加载关联实体&#xff0c;不管是一对多关联、还是一对一关联、多对多关联&#xff0c;Hibernate 默认都会采用延迟加载。

对于关联实体&#xff0c;可以将其分为两种情况&#xff1a;

  • 关联实体是多个实体时&#xff08;包括一对多、多对多&#xff09;&#xff1a;此时关联实体将以集合的形式存在&#xff0c;Hibernate 将使用 PersistentSet、PersistentList、PersistentMap、PersistentSortedMap、 PersistentSortedSet 等集合来管理延迟加载的实体。这就是前面所介绍的情形。
  • 关联实体是单个实体时&#xff08;包括一对一、多对一&#xff09;&#xff1a;当 Hibernate 加载某个实体时&#xff0c;延迟的关联实体将是一个动态生成代理对象。

当关联实体是单个实体时&#xff0c;也就是使用 映射关联实体的情形&#xff0c;这两个元素也可通过 lazy 属性来指定延迟加载。

下面例子把 Address 类也映射成持久化类&#xff0c;此时 Address 类也变成实体类&#xff0c;Person 实体与 Address 实体形成一对多的双向关联。此时的映射文件代码如下&#xff1a;


清单 3. Person.hbm.xml

Displaycode代码  收藏代码
  1.                    
  2.  "1.0" encoding&#61;"GBK"?>   
  3.     
  4.  
  5. "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
  6. "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">   
  7.  "org.crazyit.app.domain">   
  8.     
  9.  "Person" table&#61;"person_inf">   
  10.     
  11.  "id" column&#61;"person_id">   
  12.     
  13.  "identity"/>   
  14.     
  15.     
  16.  "name" type&#61;"string"/>   
  17.  "age" type&#61;"int"/>   
  18.     
  19.  "addresses" inverse&#61;"true">   
  20.     
  21.  "person_id"/>   
  22.     
  23.  "Address"/>   
  24.     
  25.     
  26.   
  27.     
  28.  "Address" table&#61;"address_inf">   
  29.     
  30.  "addressId" column&#61;"address_id">   
  31.     
  32.  "identity"/>   
  33.     
  34.     
  35.  "detail"/>   
  36.     
  37.  "zip"/>   
  38.     
  39.  "person" class&#61;"Person"  
  40.  column&#61;"person_id" not-null&#61;"true"/>   
  41.     
  42.     

 

接下来程序通过如下代码片段来加载 ID 为 1 的 Person 实体&#xff1a;

Displaycode代码  收藏代码
  1. // 打开上下文相关的 Session   
  2. Session session &#61; sf.getCurrentSession();   
  3. Transaction tx &#61; session.beginTransaction();   
  4. Address address &#61; (Address) session.get(Address.class , 1); //<1>   
  5. System.out.println(address.getDetail());   

 

为了看到 Hibernate 加载 Address 实体时对其关联实体的处理&#xff0c;我们在 <1>号代码处设置一个断点&#xff0c;在 Eclipse 中进行 Debug&#xff0c;此时可以看到 Eclipse 的 Console 窗口输出如下 SQL 语句&#xff1a;

Displaycode代码  收藏代码
  1. select   
  2.     address0_.address_id as address1_1_0_,   
  3.     address0_.detail as detail1_0_,   
  4.     address0_.zip as zip1_0_,   
  5.     address0_.person_id as person4_1_0_   
  6. from   
  7.     address_inf address0_   
  8. where   
  9.     address0_.address_id&#61;?   

 

从这条 SQL 语句不难看出&#xff0c;Hibernate 加载 Address 实体对应的数据表抓取记录&#xff0c;并未从 Person 实体对应的数据表中抓取记录&#xff0c;这是延迟加载发挥了作用。

从 Eclipse 的 Variables 窗口看到如图 4 所示的输出&#xff1a;


图 4. 延迟加载的实体
图 4. 延迟加载的实体 

从图 4 可以清楚地看到&#xff0c;此时 Address 实体所关联的 Person 实体并不是 Person 对象&#xff0c;而是一个 Person_$$_javassist_0 类的实例&#xff0c;这个类是 Hibernate 使用 Javassist 项目动态生成的代理类——当 Hibernate 延迟加载关联实体时&#xff0c;将会采用 Javassist 生成一个动态代理对象&#xff0c;这个代理对象将负责代理“暂未加载”的关联实体。

只要应用程序需要使用“暂未加载”的关联实体&#xff0c;Person_$$_javassist_0 代理对象会负责去加载真正的关联实体&#xff0c;并返回实际的关联实体——这就是最典型的代理模式。

单击图 4 所示 Variables 窗口中的 person 属性&#xff08;也就是在调试模式下强行使用 person 属性&#xff09;&#xff0c;此时看到 Eclipse 的 Console 窗口输出如下的 SQL 语句&#xff1a;

Displaycode代码  收藏代码
  1. select   
  2.     person0_.person_id as person1_0_0_,   
  3.     person0_.name as name0_0_,   
  4.     person0_.age as age0_0_   
  5. from   
  6.     person_inf person0_   
  7. where   
  8.     person0_.person_id&#61;?   

 

上面 SQL 语句就是去抓取“延迟加载”的关联实体的语句。此时可以看到 Variables 窗口输出图 5 所示的结果&#xff1a;


图 5. 已加载的实体
图 5. 已加载的实体 

Hibernate 采用“延迟加载”管理关联实体的模式&#xff0c;其实就在加载主实体时&#xff0c;并未真正去抓取关联实体对应数据&#xff0c;而只是动态地生成一个对象作为关联实体的代理。当应用程序真正需要使用关联实体时&#xff0c;代理对象会负责从底层数据库抓取记录&#xff0c;并初始化真正的关联实体。

在 Hibernate 的延迟加载中&#xff0c;客户端程序开始获取的只是一个动态生成的代理对象&#xff0c;而真正的实体则委托给代理对象来管理——这就是典型的代理模式。

回页首

代理模式

代理模式是一种应用非常广泛的设计模式&#xff0c;当客户端代码需要调用某个对象时&#xff0c;客户端实际上也不关心是否准确得到该对象&#xff0c;它只要一个能提供该功能的对象即可&#xff0c;此时我们就可返回该对象的代理&#xff08;Proxy&#xff09;。

在这种设计方式下&#xff0c;系统会为某个对象提供一个代理对象&#xff0c;并由代理对象控制对源对象的引用。代理就是一个 Java 对象代表另一个 Java 对象来采取行动。在某些情况下&#xff0c;客户端代码不想或不能够直接调用被调用者&#xff0c;代理对象可以在客户和目标对象之间起到中介的作用。

对客户端而言&#xff0c;它不能分辨出代理对象与真实对象的区别&#xff0c;它也无须分辨代理对象和真实对象的区别。客户端代码并不知道真正的被代理对象&#xff0c;客户端代码面向接口编程&#xff0c;它仅仅持有一个被代理对象的接口。

总而言之&#xff0c;只要客户端代码不能或不想直接访问被调用对象——这种情况有很多原因&#xff0c;比如需要创建一个系统开销很大的对象&#xff0c;或者被调用对象在远程主机上&#xff0c;或者目标对象的功能还不足以满足需求……&#xff0c;而是额外创建一个代理对象返回给客户端使用&#xff0c;那么这种设计方式就是代理模式。

下面示范一个简单的代理模式&#xff0c;程序首先提供了一个 Image 接口&#xff0c;代表大图片对象所实现的接口&#xff0c;该接口代码如下&#xff1a;


清单 3. Image.java

Displaycode代码  收藏代码
  1.                
  2. public interface Image   
  3. {   
  4. void show();   
  5. }   

 

该接口提供了一个实现类&#xff0c;该实现类模拟了一个大图片对象&#xff0c;该实现类的构造器使用 Thread.sleep() 方法来暂停 3s。下面是该 BigImage 的程序代码。


清单 4. BigImage.java

Displaycode代码  收藏代码
  1.                
  2. // 使用该 BigImage 模拟一个很大图片  
  3. public class BigImage implements Image   
  4. {   
  5. public BigImage()   
  6. {   
  7. try   
  8. {   
  9. // 程序暂停 3s 模式模拟系统开销   
  10.              Thread.sleep(3000);   
  11. System.out.println("图片装载成功 ...");   
  12. }   
  13. catch (InterruptedException ex)   
  14. {   
  15. ex.printStackTrace();   
  16. }   
  17. }   
  18. // 实现 Image 里的 show() 方法  
  19. public void show()   
  20. {   
  21. System.out.println("绘制实际的大图片");   
  22. }   
  23. }   

 

上面的程序代码暂停了 3s&#xff0c;这表明创建一个 BigImage 对象需要 3s 的时间开销——程序使用这种延迟来模拟装载此图片所导致的系统开销。如果不采用代理模式&#xff0c;当程序中创建 BigImage 时&#xff0c;系统将会产生 3s 的延迟。为了避免这种延迟&#xff0c;程序为 BigImage 对象提供一个代理对象&#xff0c;BigImage 类的代理类如下所示。


清单 5. ImageProxy.java

Displaycode代码  收藏代码
  1.                
  2. public class ImageProxy implements Image   
  3. {   
  4. // 组合一个 image 实例&#xff0c;作为被代理的对象  
  5. private Image image;   
  6. // 使用抽象实体来初始化代理对象  
  7. public ImageProxy(Image image)   
  8. {   
  9. this.image &#61; image;   
  10. }   
  11. /**   
  12. * 重写 Image 接口的 show() 方法  
  13. * 该方法用于控制对被代理对象的访问&#xff0c;  
  14. * 并根据需要负责创建和删除被代理对象  
  15. */   
  16. public void show()   
  17. {   
  18. // 只有当真正需要调用 image 的 show 方法时才创建被代理对象  
  19. if (image &#61;&#61; null)   
  20. {   
  21.  image &#61; new BigImage();   
  22.  }   
  23. image.show();   
  24. }   
  25. }   

 

上面的 ImageProxy 代理类实现了与 BigImage 相同的 show() 方法&#xff0c;这使得客户端代码获取到该代理对象之后&#xff0c;可以将该代理对象当成 BigImage 来使用。

在 ImageProxy 类的 show() 方法中增加了控制逻辑&#xff0c;这段控制逻辑用于控制当系统真正调用 image 的 show() 时&#xff0c;才会真正创建被代理的 BigImage 对象。下面程序需要使用 BigImage 对象&#xff0c;但程序并不是直接返回 BigImage 实例&#xff0c;而是先返回 BigImage 的代理对象&#xff0c;如下面程序所示。


清单 6. BigImageTest.java

Displaycode代码  收藏代码
  1.                
  2. public class BigImageTest   
  3. {   
  4. public static void main(String[] args)   
  5. {   
  6. long start &#61; System.currentTimeMillis();   
  7. // 程序返回一个 Image 对象&#xff0c;该对象只是 BigImage 的代理对象  
  8. Image image &#61; new ImageProxy(null);   
  9. System.out.println("系统得到 Image 对象的时间开销 :" &#43;   
  10. (System.currentTimeMillis() - start));   
  11. // 只有当实际调用 image 代理的 show() 方法时&#xff0c;程序才会真正创建被代理对象。  
  12. image.show();   
  13. }   
  14. }   

 

上面程序初始化 image 非常快&#xff0c;因为程序并未真正创建 BigImage 对象&#xff0c;只是得到了 ImageProxy 代理对象——直到程序调用 image.show() 方法时&#xff0c;程序需要真正调用 BigImage 对象的 show() 方法&#xff0c;程序此时才真正创建 BigImage 对象。运行上面程序&#xff0c;看到如图 6 所示的结果。


图 6. 使用代理模式提高性能
图 6. 使用代理模式提高性能 

看到如图 6 所示的运行结果&#xff0c;读者应该能认同&#xff1a;使用代理模式提高了获取 Image 对象的系统性能。但可能有读者会提出疑问&#xff1a;程序调用 ImageProxy 对象的 show() 方法时一样需要创建 BigImage 对象啊&#xff0c;系统开销并未真正减少啊&#xff1f;只是这种系统开销延迟了而已啊&#xff1f;

我们可以从如下两个角度来回答这个问题&#xff1a;

  • 把创建 BigImage 推迟到真正需要它时才创建&#xff0c;这样能保证前面程序运行的流畅性&#xff0c;而且能减少 BigImage 在内存中的存活时间&#xff0c;从宏观上节省了系统的内存开销。
  • 有些情况下&#xff0c;也许程序永远不会真正调用 ImageProxy 对象的 show() 方法——意味着系统根本无须创建 BigImage 对象。在这种情形下&#xff0c;使用代理模式可以显著地提高系统运行性能。

与此完全类似的是&#xff0c;Hibernate 也是通过代理模式来“推迟”加载关联实体的时间&#xff0c;如果程序并不需要访问关联实体&#xff0c;那程序就不会去抓取关联实体了&#xff0c;这样既可以节省系统的内存开销&#xff0c;也可以缩短 Hibernate 加载实体的时间。

回页首

小结

Hibernate 的延迟加载&#xff08;lazy load&#xff09;本质上就是代理模式的应用&#xff0c;我们在过去的岁月里就经常通过代理模式来降低系统的内存开销、提升应用的运行性能。Hibernate 充分利用了代理模式的这种优势&#xff0c;并结合了 Javassist 或 CGLIB 来动态地生成代理对象&#xff0c;这更加增加了代理模式的灵活性&#xff0c;Hibernate 给这种用法一个新名称&#xff1a;延迟加载。无论怎样&#xff0c;充分分析、了解这些开源框架的实现可以更好的感受经典设计模式的优势所在。

转:https://www.cnblogs.com/zhangshitong/p/5306442.html



推荐阅读
  • Oracle seg,V$TEMPSEG_USAGE与Oracle排序的关系及使用方法
    本文介绍了Oracle seg,V$TEMPSEG_USAGE与Oracle排序之间的关系,V$TEMPSEG_USAGE是V_$SORT_USAGE的同义词,通过查询dba_objects和dba_synonyms视图可以了解到它们的详细信息。同时,还探讨了V$TEMPSEG_USAGE的使用方法。 ... [详细]
  • RouterOS 5.16软路由安装图解教程
    本文介绍了如何安装RouterOS 5.16软路由系统,包括系统要求、安装步骤和登录方式。同时提供了详细的图解教程,方便读者进行操作。 ... [详细]
  • 解决.net项目中未注册“microsoft.ACE.oledb.12.0”提供程序的方法
    在开发.net项目中,通过microsoft.ACE.oledb读取excel文件信息时,报错“未在本地计算机上注册“microsoft.ACE.oledb.12.0”提供程序”。本文提供了解决这个问题的方法,包括错误描述和代码示例。通过注册提供程序和修改连接字符串,可以成功读取excel文件信息。 ... [详细]
  • vue使用
    关键词: ... [详细]
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Imtryingtofigureoutawaytogeneratetorrentfilesfromabucket,usingtheAWSSDKforGo.我正 ... [详细]
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
  • GreenDAO快速入门
    前言之前在自己做项目的时候,用到了GreenDAO数据库,其实对于数据库辅助工具库从OrmLite,到litePal再到GreenDAO,总是在不停的切换,但是没有真正去了解他们的 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • 单页面应用 VS 多页面应用的区别和适用场景
    本文主要介绍了单页面应用(SPA)和多页面应用(MPA)的区别和适用场景。单页面应用只有一个主页面,所有内容都包含在主页面中,页面切换快但需要做相关的调优;多页面应用有多个独立的页面,每个页面都要加载相关资源,页面切换慢但适用于对SEO要求较高的应用。文章还提到了两者在资源加载、过渡动画、路由模式和数据传递方面的差异。 ... [详细]
  • Android实战——jsoup实现网络爬虫,糗事百科项目的起步
    本文介绍了Android实战中使用jsoup实现网络爬虫的方法,以糗事百科项目为例。对于初学者来说,数据源的缺乏是做项目的最大烦恼之一。本文讲述了如何使用网络爬虫获取数据,并以糗事百科作为练手项目。同时,提到了使用jsoup需要结合前端基础知识,以及如果学过JS的话可以更轻松地使用该框架。 ... [详细]
  • Asp.net Mvc Framework 七 (Filter及其执行顺序) 的应用示例
    本文介绍了在Asp.net Mvc中应用Filter功能进行登录判断、用户权限控制、输出缓存、防盗链、防蜘蛛、本地化设置等操作的示例,并解释了Filter的执行顺序。通过示例代码,详细说明了如何使用Filter来实现这些功能。 ... [详细]
author-avatar
半宅半学
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有