虎行有雨,定义标记类型Aware接口,实现感知容器对象

开发 前端
本章节关于 Aware 的感知接口的四个继承接口 BeanNameAware, BeanClassLoaderAware, ApplicationContextAware, BeanFactoryAware 的实现,又扩展了 Spring 的功能。

 [[407936]]

本文转载自微信公众号「bugstack虫洞栈」,作者小傅哥 。转载本文请联系bugstack虫洞栈公众号。

目录

  • 一、前言
  • 二、目标
  • 三、设计
  • 四、实现
    • 1. 工程结构
    • 2. 定义标记接口
    • 3. 容器感知类
    • 4. 包装处理器(ApplicationContextAwareProcessor)
    • 5. 注册 BeanPostProcessor
    • 6. 感知调用操作
  • 五、测试
    • 1. 事先准备
    • 2. 配置文件
    • 3. 单元测试
  • 六、总结

一、前言

同事写的代码,我竟丝毫看不懂!

大佬的代码,就像“赖蛤蟆泡青蛙,张的丑玩的花”:一个类实现了多个接口、继承的类又继承了其他类、接口还可以和接口继承、实现接口的抽象类再由类实现抽象类方法、类A继承的类B实现了类A实现的接口C,等等。

看上去复杂又难懂的代码,却又能一次次满足需求的高效迭代和顺利扩展,而像螺丝钉一样搬砖的你,只是在大佬写的代码里,完成某个接口下的一小块功能,甚至写完了也不知道怎么就被调用运行了,整个过程像看 Spring 源码一样神奇,跳来跳去的摸不着头绪!

其实这主要是因为你的代码是否运用了设计模式,当然设计模式也没那么神奇,就像你们两家都是120平米的房子,他家有三室两厅一厨一卫,南北通透,全阳采光。但你家就不一样了,你家是锅碗瓢盆、卫浴马桶、沙发茶几还有那1.8的双人床,在120平米的房子里敞开了放,没有动静隔离,也没有干湿分离,纯自由发挥。所以你的代码看上去就乱的很!

二、目标

目前已实现的 Spring 框架,在 Bean 操作上能提供出的能力,包括:Bean 对象的定义和注册,以及在操作 Bean 对象过程中执行的,BeanFactoryPostProcessor、BeanPostProcessor、InitializingBean、DisposableBean,以及在 XML 新增的一些配置处理,让我们可以 Bean 对象有更强的操作性。

那么,如果我们想获得 Spring 框架提供的 BeanFactory、ApplicationContext、BeanClassLoader等这些能力做一些扩展框架的使用时该怎么操作呢。所以我们本章节希望在 Spring 框架中提供一种能感知容器操作的接口,如果谁实现了这样的一个接口,就可以获取接口入参中的各类能力。

三、设计

如果说我希望拿到 Spring 框架中一些提供的资源,那么首先需要考虑以一个什么方式去获取,之后你定义出来的获取方式,在 Spring 框架中该怎么去承接,实现了这两项内容,就可以扩展出你需要的一些属于 Spring 框架本身的能力了。

在关于 Bean 对象实例化阶段我们操作过一些额外定义、属性、初始化和销毁的操作,其实我们如果像获取 Spring 一些如 BeanFactory、ApplicationContext 时,也可以通过此类方式进行实现。那么我们需要定义一个标记性的接口,这个接口不需要有方法,它只起到标记作用就可以,而具体的功能由继承此接口的其他功能性接口定义具体方法,最终这个接口就可以通过 instanceof 进行判断和调用了。整体设计结构如下图:

  • 定义接口 Aware,在 Spring 框架中它是一种感知标记性接口,具体的子类定义和实现能感知容器中的相关对象。也就是通过这个桥梁,向具体的实现类中提供容器服务
  • 继承 Aware 的接口包括:BeanFactoryAware、BeanClassLoaderAware、BeanNameAware和ApplicationContextAware,当然在 Spring 源码中还有一些其他关于注解的,不过目前我们还是用不到。
  • 在具体的接口实现过程中你可以看到,一部分(BeanFactoryAware、BeanClassLoaderAware、BeanNameAware)在 factory 的 support 文件夹下,另外 ApplicationContextAware 是在 context 的 support 中,这是因为不同的内容获取需要在不同的包下提供。所以,在 AbstractApplicationContext 的具体实现中会用到向 beanFactory 添加 BeanPostProcessor 内容的 ApplicationContextAwareProcessor 操作,最后由 AbstractAutowireCapableBeanFactory 创建 createBean 时处理相应的调用操作。关于 applyBeanPostProcessorsBeforeInitialization 已经在前面章节中实现过,如果忘记可以往前翻翻

四、实现

1. 工程结构

  1. small-spring-step-08 
  2. └── src 
  3.     ├── main 
  4.     │   └── java 
  5.     │       └── cn.bugstack.springframework 
  6.     │           ├── beans 
  7.     │           │   ├── factory 
  8.     │           │   │   ├── factory 
  9.     │           │   │   │   ├── AutowireCapableBeanFactory.java 
  10.     │           │   │   │   ├── BeanDefinition.java 
  11.     │           │   │   │   ├── BeanFactoryPostProcessor.java 
  12.     │           │   │   │   ├── BeanPostProcessor.java 
  13.     │           │   │   │   ├── BeanReference.java 
  14.     │           │   │   │   ├── ConfigurableBeanFactory.java 
  15.     │           │   │   │   └── SingletonBeanRegistry.java 
  16.     │           │   │   ├── support 
  17.     │           │   │   │   ├── AbstractAutowireCapableBeanFactory.java 
  18.     │           │   │   │   ├── AbstractBeanDefinitionReader.java 
  19.     │           │   │   │   ├── AbstractBeanFactory.java 
  20.     │           │   │   │   ├── BeanDefinitionReader.java 
  21.     │           │   │   │   ├── BeanDefinitionRegistry.java 
  22.     │           │   │   │   ├── CglibSubclassingInstantiationStrategy.java 
  23.     │           │   │   │   ├── DefaultListableBeanFactory.java 
  24.     │           │   │   │   ├── DefaultSingletonBeanRegistry.java 
  25.     │           │   │   │   ├── DisposableBeanAdapter.java 
  26.     │           │   │   │   ├── InstantiationStrategy.java 
  27.     │           │   │   │   └── SimpleInstantiationStrategy.java   
  28.     │           │   │   ├── support 
  29.     │           │   │   │   └── XmlBeanDefinitionReader.java 
  30.     │           │   │   ├── Aware.java 
  31.     │           │   │   ├── BeanClassLoaderAware.java 
  32.     │           │   │   ├── BeanFactory.java 
  33.     │           │   │   ├── BeanFactoryAware.java 
  34.     │           │   │   ├── BeanNameAware.java 
  35.     │           │   │   ├── ConfigurableListableBeanFactory.java 
  36.     │           │   │   ├── DisposableBean.java 
  37.     │           │   │   ├── HierarchicalBeanFactory.java 
  38.     │           │   │   ├── InitializingBean.java 
  39.     │           │   │   └── ListableBeanFactory.java 
  40.     │           │   ├── BeansException.java 
  41.     │           │   ├── PropertyValue.java 
  42.     │           │   └── PropertyValues.java  
  43.     │           ├── context 
  44.     │           │   ├── support 
  45.     │           │   │   ├── AbstractApplicationContext.java  
  46.     │           │   │   ├── AbstractRefreshableApplicationContext.java  
  47.     │           │   │   ├── AbstractXmlApplicationContext.java  
  48.     │           │   │   ├── ApplicationContextAwareProcessor.java  
  49.     │           │   │   └── ClassPathXmlApplicationContext.java  
  50.     │           │   ├── ApplicationContext.java  
  51.     │           │   ├── ApplicationContextAware.java  
  52.     │           │   └── ConfigurableApplicationContext.java 
  53.     │           ├── core.io 
  54.     │           │   ├── ClassPathResource.java  
  55.     │           │   ├── DefaultResourceLoader.java  
  56.     │           │   ├── FileSystemResource.java  
  57.     │           │   ├── Resource.java  
  58.     │           │   ├── ResourceLoader.java  
  59.     │           │   └── UrlResource.java 
  60.     │           └── utils 
  61.     │               └── ClassUtils.java 
  62.     └── test 
  63.         └── java 
  64.             └── cn.bugstack.springframework.test 
  65.                 ├── bean 
  66.                 │   ├── UserDao.java 
  67.                 │   └── UserService.java 
  68.                 └── ApiTest.java 

Spring 感知接口的设计和实现类关系,如图 9-2

图 9-2

  • 以上整个类关系就是关于 Aware 感知的定义和对容器感知的实现。
  • Aware 有四个继承的接口,其他这些接口的继承都是为了继承一个标记,有了标记的存在更方便类的操作和具体判断实现。
  • 另外由于 ApplicationContext 并不是在 AbstractAutowireCapableBeanFactory 中 createBean 方法下的内容,所以需要像容器中注册 addBeanPostProcessor ,再由 createBean 统一调用 applyBeanPostProcessorsBeforeInitialization 时进行操作。

2. 定义标记接口

  1. /** 
  2.  * Marker superinterface indicating that a bean is eligible to be 
  3.  * notified by the Spring container of a particular framework object 
  4.  * through a callback-style method.  Actual method signature is 
  5.  * determined by individual subinterfaces, but should typically 
  6.  * consist of just one void-returning method that accepts a single 
  7.  * argument. 
  8.  * 
  9.  * 标记类接口,实现该接口可以被Spring容器感知 
  10.  * 
  11.  */ 
  12. public interface Aware { 

在 Spring 中有特别多类似这样的标记接口的设计方式,它们的存在就像是一种标签一样,可以方便统一摘取出属于此类接口的实现类,通常会有 instanceof 一起判断使用。

3. 容器感知类

3.1 BeanFactoryAware

  1. public interface BeanFactoryAware extends Aware { 
  2.  
  3.    void setBeanFactory(BeanFactory beanFactory) throws BeansException; 
  4.  
  • Interface to be implemented by beans that wish to be aware of their owning {@link BeanFactory}.
  • 实现此接口,既能感知到所属的 BeanFactory

3.2 BeanClassLoaderAware

cn.bugstack.springframework.beans.factory.BeanClassLoaderAware

  1. public interface BeanClassLoaderAware extends Aware{ 
  2.  
  3.     void setBeanClassLoader(ClassLoader classLoader); 
  4.  
  • Callback that allows a bean to be aware of the bean{@link ClassLoader class loader}; that is, the class loader used by the present bean factory to load bean classes.
  • 实现此接口,既能感知到所属的 ClassLoader

3.3 BeanNameAware

cn.bugstack.springframework.beans.factory.BeanNameAware

  1. public interface BeanClassLoaderAware extends Aware{ 
  2.  
  3.     void setBeanClassLoader(ClassLoader classLoader); 
  4.  
  • Interface to be implemented by beans that want to be aware of their bean name in a bean factory.
  • 实现此接口,既能感知到所属的 BeanName

3.4 ApplicationContextAware

cn.bugstack.springframework.context.ApplicationContextAware

  1. public interface ApplicationContextAware extends Aware { 
  2.  
  3.     void setApplicationContext(ApplicationContext applicationContext) throws BeansException; 
  4.  
  • Interface to be implemented by any object that wishes to be notifiedof the {@link ApplicationContext} that it runs in.
  • 实现此接口,既能感知到所属的 ApplicationContext

4. 包装处理器(ApplicationContextAwareProcessor)

cn.bugstack.springframework.context.support.ApplicationContextAwareProcessor

  1. public class ApplicationContextAwareProcessor implements BeanPostProcessor { 
  2.  
  3.     private final ApplicationContext applicationContext; 
  4.  
  5.     public ApplicationContextAwareProcessor(ApplicationContext applicationContext) { 
  6.         this.applicationContext = applicationContext; 
  7.     } 
  8.  
  9.     @Override 
  10.     public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { 
  11.         if (bean instanceof ApplicationContextAware){ 
  12.             ((ApplicationContextAware) bean).setApplicationContext(applicationContext); 
  13.         } 
  14.         return bean; 
  15.     } 
  16.  
  17.     @Override 
  18.     public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { 
  19.         return bean; 
  20.     } 
  21.  

由于 ApplicationContext 的获取并不能直接在创建 Bean 时候就可以拿到,所以需要在 refresh 操作时,把 ApplicationContext 写入到一个包装的 BeanPostProcessor 中去,再由 AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization 方法调用。

5. 注册 BeanPostProcessor

cn.bugstack.springframework.context.support.AbstractApplicationContext

  1. public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext { 
  2.  
  3.     @Override 
  4.     public void refresh() throws BeansException { 
  5.         // 1. 创建 BeanFactory,并加载 BeanDefinition 
  6.         refreshBeanFactory(); 
  7.  
  8.         // 2. 获取 BeanFactory 
  9.         ConfigurableListableBeanFactory beanFactory = getBeanFactory(); 
  10.  
  11.         // 3. 添加 ApplicationContextAwareProcessor,让继承自 ApplicationContextAware 的 Bean 对象都能感知所属的 ApplicationContext 
  12.         beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this)); 
  13.  
  14.         // 4. 在 Bean 实例化之前,执行 BeanFactoryPostProcessor (Invoke factory processors registered as beans in the context.) 
  15.         invokeBeanFactoryPostProcessors(beanFactory); 
  16.  
  17.         // 5. BeanPostProcessor 需要提前于其他 Bean 对象实例化之前执行注册操作 
  18.         registerBeanPostProcessors(beanFactory); 
  19.  
  20.         // 6. 提前实例化单例Bean对象 
  21.         beanFactory.preInstantiateSingletons(); 
  22.     } 
  23.      
  24.   // ...    
  25. }     
  • refresh() 方法就是整个 Spring 容器的操作过程,与上一章节对比,本次新增加了关于 addBeanPostProcessor 的操作。
  • 添加 ApplicationContextAwareProcessor,让继承自 ApplicationContextAware 的 Bean 对象都能感知所属的 ApplicationContext。

6. 感知调用操作

cn.bugstack.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory

  1. public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { 
  2.  
  3.     private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy(); 
  4.  
  5.     @Override 
  6.     protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException { 
  7.         Object bean = null
  8.         try { 
  9.             bean = createBeanInstance(beanDefinition, beanName, args); 
  10.             // 给 Bean 填充属性 
  11.             applyPropertyValues(beanName, bean, beanDefinition); 
  12.             // 执行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置处理方法 
  13.             bean = initializeBean(beanName, bean, beanDefinition); 
  14.         } catch (Exception e) { 
  15.             throw new BeansException("Instantiation of bean failed", e); 
  16.         } 
  17.  
  18.         // 注册实现了 DisposableBean 接口的 Bean 对象 
  19.         registerDisposableBeanIfNecessary(beanName, bean, beanDefinition); 
  20.  
  21.         addSingleton(beanName, bean); 
  22.         return bean; 
  23.     } 
  24.  
  25.     private Object initializeBean(String beanName, Object bean, BeanDefinition beanDefinition) { 
  26.  
  27.         // invokeAwareMethods 
  28.         if (bean instanceof Aware) { 
  29.             if (bean instanceof BeanFactoryAware) { 
  30.                 ((BeanFactoryAware) bean).setBeanFactory(this); 
  31.             } 
  32.             if (bean instanceof BeanClassLoaderAware){ 
  33.                 ((BeanClassLoaderAware) bean).setBeanClassLoader(getBeanClassLoader()); 
  34.             } 
  35.             if (bean instanceof BeanNameAware) { 
  36.                 ((BeanNameAware) bean).setBeanName(beanName); 
  37.             } 
  38.         } 
  39.  
  40.         // 1. 执行 BeanPostProcessor Before 处理 
  41.         Object wrappedBean = applyBeanPostProcessorsBeforeInitialization(bean, beanName); 
  42.  
  43.         // 执行 Bean 对象的初始化方法 
  44.         try { 
  45.             invokeInitMethods(beanName, wrappedBean, beanDefinition); 
  46.         } catch (Exception e) { 
  47.             throw new BeansException("Invocation of init method of bean[" + beanName + "] failed", e); 
  48.         } 
  49.  
  50.         // 2. 执行 BeanPostProcessor After 处理 
  51.         wrappedBean = applyBeanPostProcessorsAfterInitialization(bean, beanName); 
  52.         return wrappedBean; 
  53.     } 
  54.  
  55.  
  56.  
  57.     @Override 
  58.     public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) throws BeansException { 
  59.         Object result = existingBean; 
  60.         for (BeanPostProcessor processor : getBeanPostProcessors()) { 
  61.             Object current = processor.postProcessBeforeInitialization(result, beanName); 
  62.             if (null == currentreturn result; 
  63.             result = current
  64.         } 
  65.         return result; 
  66.     } 
  67.  
  68.     @Override 
  69.     public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException { 
  70.         Object result = existingBean; 
  71.         for (BeanPostProcessor processor : getBeanPostProcessors()) { 
  72.             Object current = processor.postProcessAfterInitialization(result, beanName); 
  73.             if (null == currentreturn result; 
  74.             result = current
  75.         } 
  76.         return result; 
  77.     } 
  78.  
  • 这里我们去掉了一些类的内容,只保留关于本次 Aware 感知接口的操作。
  • 首先在 initializeBean 中,通过判断 bean instanceof Aware,调用了三个接口方法,BeanFactoryAware.setBeanFactory(this)、BeanClassLoaderAware.setBeanClassLoader(getBeanClassLoader())、BeanNameAware.setBeanName(beanName),这样就能通知到已经实现了此接口的类。
  • 另外我们还向 BeanPostProcessor 中添加了 ApplicationContextAwareProcessor,此时在这个方法中也会被调用到具体的类实现,得到一个 ApplicationContex 属性。

五、测试

1. 事先准备

cn.bugstack.springframework.test.bean.UserDao

  1. public class UserDao { 
  2.  
  3.     private static Map<String, String> hashMap = new HashMap<>(); 
  4.  
  5.     public void initDataMethod(){ 
  6.         System.out.println("执行:init-method"); 
  7.         hashMap.put("10001""小傅哥"); 
  8.         hashMap.put("10002""八杯水"); 
  9.         hashMap.put("10003""阿毛"); 
  10.     } 
  11.  
  12.     public void destroyDataMethod(){ 
  13.         System.out.println("执行:destroy-method"); 
  14.         hashMap.clear(); 
  15.     } 
  16.  
  17.     public String queryUserName(String uId) { 
  18.         return hashMap.get(uId); 
  19.     } 
  20.  

cn.bugstack.springframework.test.bean.UserService

  1. public class UserService implements BeanNameAware, BeanClassLoaderAware, ApplicationContextAware, BeanFactoryAware { 
  2.  
  3.     private ApplicationContext applicationContext; 
  4.     private BeanFactory beanFactory; 
  5.  
  6.     private String uId; 
  7.     private String company; 
  8.     private String location; 
  9.     private UserDao userDao; 
  10.  
  11.     @Override 
  12.     public void setBeanFactory(BeanFactory beanFactory) throws BeansException { 
  13.         this.beanFactory = beanFactory; 
  14.     } 
  15.  
  16.     @Override 
  17.     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 
  18.         this.applicationContext = applicationContext; 
  19.     } 
  20.  
  21.     @Override 
  22.     public void setBeanName(String name) { 
  23.         System.out.println("Bean Name is:" + name); 
  24.     } 
  25.  
  26.     @Override 
  27.     public void setBeanClassLoader(ClassLoader classLoader) { 
  28.         System.out.println("ClassLoader:" + classLoader); 
  29.     } 
  30.  
  31.     // ...get/set 

UserDao 本次并没有什么改变,还是提供了关于初始化的方法,并在 Spring.xml 中提供 init-method、destroy-method 配置信息。

UserService 新增加,BeanNameAware, BeanClassLoaderAware, ApplicationContextAware, BeanFactoryAware,四个感知的实现类,并在类中实现相应的接口方法。

2. 配置文件

基础配置,无BeanFactoryPostProcessor、BeanPostProcessor,实现类

  1. <?xml version="1.0" encoding="UTF-8"?> 
  2. <beans> 
  3.  
  4.     <bean id="userDao" class="cn.bugstack.springframework.test.bean.UserDao" init-method="initDataMethod" destroy-method="destroyDataMethod"/> 
  5.  
  6.     <bean id="userService" class="cn.bugstack.springframework.test.bean.UserService"
  7.         <property name="uId" value="10001"/> 
  8.         <property name="company" value="腾讯"/> 
  9.         <property name="location" value="深圳"/> 
  10.         <property name="userDao" ref="userDao"/> 
  11.     </bean> 
  12.  
  13. </beans> 

 本章节中并没有额外新增加配置信息,与上一章节内容相同。

3. 单元测试

  1. @Test 
  2. public void test_xml() { 
  3.     // 1.初始化 BeanFactory 
  4.     ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml"); 
  5.     applicationContext.registerShutdownHook();       
  6.  
  7.     // 2. 获取Bean对象调用方法 
  8.     UserService userService = applicationContext.getBean("userService", UserService.class); 
  9.     String result = userService.queryUserInfo(); 
  10.     System.out.println("测试结果:" + result); 
  11.     System.out.println("ApplicationContextAware:"+userService.getApplicationContext()); 
  12.     System.out.println("BeanFactoryAware:"+userService.getBeanFactory()); 
  • 测试方法中主要是添加了一写关于新增 Aware 实现的调用,其他不需要调用的也打印了相应的日志信息,可以在测试结果中看到。

测试结果

  1. 执行:init-method 
  2. ClassLoader:sun.misc.Launcher$AppClassLoader@14dad5dc 
  3. Bean Name is:userService 
  4. 测试结果:小傅哥,腾讯,深圳 
  5. ApplicationContextAware:cn.bugstack.springframework.context.support.ClassPathXmlApplicationContext@5ba23b66 
  6. BeanFactoryAware:cn.bugstack.springframework.beans.factory.support.DefaultListableBeanFactory@2ff4f00f 
  7. 执行:destroy-method 
  8.  
  9.  
  10.  
  11. Process finished with exit code 0 

从测试结果可以看到,本章节新增加的感知接口对应的具体实现(BeanNameAware, BeanClassLoaderAware, ApplicationContextAware, BeanFactoryAware),已经可以如期输出结果了。

六、总结

目前关于 Spring 框架的实现中,某些功能点已经越来趋向于完整,尤其是 Bean 对象的生命周期,已经有了很多的体现。整体总结如下图:

本章节关于 Aware 的感知接口的四个继承接口 BeanNameAware, BeanClassLoaderAware, ApplicationContextAware, BeanFactoryAware 的实现,又扩展了 Spring 的功能。如果你有做过关于 Spring 中间件的开发那么一定会大量用到这些类,现在你不只是用过,而且还知道他们都是什么时候触达的,在以后想排查类的实例化顺序也可以有一个清晰的思路了。

每一章节内容的实现都是在以设计模式为核心的结构上填充各项模块的功能,单纯的操作编写代码并不会有太多收获,一定是要理解为什么这么设计,这么设计的好处是什么,怎么就那么多接口和抽象类的应用,这些才是 Spring 框架学习的核心所在。

 

责任编辑:武晓燕 来源: bugstack虫洞栈
相关推荐

2021-05-14 06:15:48

SpringAware接口

2019-05-14 09:05:16

SerializablJava对象

2023-11-03 08:19:18

SpringBean容器

2022-04-17 10:29:10

TSTypeScript对象类型

2010-01-06 16:33:04

JSON对象标记

2011-03-18 19:37:38

Eventable接口QtWidget

2011-06-28 11:05:19

Qt QWidget Eventable

2017-04-11 16:07:35

政务云嘉兴

2009-08-21 15:38:45

ControllerF

2010-01-15 15:26:46

VB.NET自定义类型

2009-08-25 10:44:08

C#接口定义接口

2009-01-04 09:08:30

面向对象继承接口

2024-10-21 09:18:47

2011-08-02 17:53:23

Oracle对象类型对象类型表

2017-05-01 16:29:40

2009-12-22 11:29:27

WCF自定义集合类型

2019-02-14 13:19:40

容器存储CSI

2009-06-24 09:19:56

JSF标记JSTL标记
点赞
收藏

51CTO技术栈公众号