02|Spring Bean 依赖注入常见错误(上)

案例 1:过多赠予,无所适从

1、在使用 @Autowired 时,应该制造或者遭遇过类似的错误:

required a single bean, but 2 were found

即,我们仅需要一个 Bean,但实际却提供了 2 个(这里的“2”在实际错误中可能是其它大于 1 的任何数字)。

2、为了重现这个错误,我们可以先写一个案例来模拟下

假设我们在开发一个学籍管理系统案例,需要提供一个 API 根据学生的学号(ID)来移除学生,学生的信息维护肯定需要一个数据库来支撑,所以大体上可以实现如下:


@RestController
@Slf4j
@Validated
public class StudentController {
    @Autowired
    DataService dataService;

    @RequestMapping(path = "students/{id}", method = RequestMethod.DELETE)
    public void deleteStudent(@PathVariable("id") @Range(min = 1,max = 100) int id){
        dataService.deleteStudent(id);
    };
}

其中 DataService 是一个接口,其实现依托于 Oracle,代码示意如下:


public interface DataService {
    void deleteStudent(int id);
}

@Repository
@Slf4j
public class OracleDataService implements DataService{
    @Override
    public void deleteStudent(int id) {
        log.info("delete student info maintained by oracle");
    }
}

截止目前,运行并测试程序是毫无问题的。但是需求往往是源源不断的,某天我们可能接到节约成本的需求,希望把一些部分非核心的业务从 Oracle 迁移到社区版 Cassandra,所以我们自然会先添加上一个新的 DataService 实现,代码如下:


@Repository
@Slf4j
public class CassandraDataService implements DataService{
    @Override
    public void deleteStudent(int id) {
        log.info("delete student info maintained by cassandra");
    }
}

实际上,当我们完成支持多个数据库的准备工作时,程序就已经无法启动了,报错如下:

3、案例解析

要找到这个问题的根源,我们就需要对 @Autowired 实现的依赖注入的原理有一定的了解。首先,我们先来了解下 @Autowired 发生的位置和核心过程。 

当一个 Bean 构建时,核心包括两个基本步骤:

(1)步骤1:执行 AbstractAutowireCapableBeanFactory#createBeanInstance 方法:通过构造器反射构造出这个 Bean,在此案例中相当于构建出 StudentController 的实例

(2)步骤2:执行 AbstractAutowireCapableBeanFactory#populate 方法:填充(即设置)这个 Bean,在本案例中,相当于设置 StudentController 实例中被 @Autowired 标记的 dataService 属性成员

在步骤 2 中,“填充”过程的关键就是执行各种 BeanPostProcessor 处理器,关键代码如下:


protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
      //省略非关键代码
      for (BeanPostProcessor bp : getBeanPostProcessors()) {
         if (bp instanceof InstantiationAwareBeanPostProcessor) {
            InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
            PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
          //省略非关键代码
         }
      }
   }   
}

 在上述代码执行过程中,因为 StudentController 含有标记为 Autowired 的成员属性 dataService,所以会使用到 AutowiredAnnotationBeanPostProcessor(BeanPostProcessor 中的一种)来完成“装配”过程:找出合适的 DataService 的 bean 并设置给 StudentController#dataService,如果深究这个装配过程,又可以细分为两个步骤

1、寻找出所有需要依赖注入的字段和方法,参考 AutowiredAnnotationBeanPostProcessor#postProcessProperties 中的代码行:


InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);

2、根据依赖信息寻找出依赖并完成注入,以字段注入为例,参考 AutowiredFieldElement#inject 方法: 


@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
   Field field = (Field) this.member;
   Object value;
   //省略非关键代码
      try {
          DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
         //寻找“依赖”,desc为"dataService"的DependencyDescriptor
         value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
      }
      
   }
   //省略非关键代码
   if (value != null) {
      ReflectionUtils.makeAccessible(field);
      //装配“依赖”
      field.set(bean, value);
   }
}

由此,我们可知案例1中的错误就发生在上述“寻找依赖”的过程中,那么到底是怎么发生的呢?

//寻找“依赖”,desc为"dataService"的DependencyDescriptor 
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);

由调试可的下图即( DefaultListableBeanFactory#doResolveDependency 中代码片段)

如上图所示,当我们根据 DataService 这个类型来找出依赖时,我们会找出 2 个依赖,分别为 CassandraDataServiceOracleDataService。在这样的情况下,如果同时满足以下两个条件则会抛出本案例的错误: 

1、调用 determineAutowireCandidate 方法来选出优先级最高的依赖,但是发现并没有优先级可依据。具体选择过程可参考 DefaultListableBeanFactory#determineAutowireCandidate:


protected String determineAutowireCandidate(Map<String, Object> candidates, DependencyDescriptor descriptor) {
   Class<?> requiredType = descriptor.getDependencyType();
   String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);
   if (primaryCandidate != null) {
      return primaryCandidate;
   }
   String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);
   if (priorityCandidate != null) {
      return priorityCandidate;
   }
   // Fallback
   for (Map.Entry<String, Object> entry : candidates.entrySet()) {
      String candidateName = entry.getKey();
      Object beanInstance = entry.getValue();
      if ((beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) ||
            matchesBeanName(candidateName, descriptor.getDependencyName())) {
         return candidateName;
      }
   }
   return null;
}

如代码所示,优先级的决策是先根据 @Primary 来决策,其次是 @Priority 决策,最后是根据 Bean 名字的严格匹配来决策。如果这些帮助决策优先级的注解都没有被使用,名字也不精确匹配,则返回 null,告知无法决策出哪种最合适。 

2、@Autowired 要求是必须注入的(即 required 保持默认值为 true),或者注解的属性类型不是可以接受多个 Bean 类型,例如数组、Map、集合。这点可以参考 DefaultListableBeanFactory#indicatesMultipleBeans 的实现:


private boolean indicatesMultipleBeans(Class<?> type) {
   return (type.isArray() || (type.isInterface() &&
         (Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type))));
}

4、问题修正 

1、打破上述两个条件中的任何一个即可,即让候选项具有优先级或压根可以不去选择

2、要同时支持多种 DataService,且能在不同业务情景下精确匹配到要选择到的 DataService,我们可以使用下面的方式去修改:


@Autowired
DataService oracleDataService;

 修改方式的精髓在于将属性名Bean 名字 精确匹配,这样就可以让注入选择不犯难:需要 Oracle 时指定属性名为 oracleDataService需要 Cassandra 时则指定属性名为 cassandraDataService

案例2:显式引用 Bean 时首字母忽略大小写

1、针对案例 1 的问题修正,实际上还存在另外一种常用的解决办法,即采用 @Qualifier 来显式指定引用的是那种服务,例如采用下面的方式:


@Autowired()
@Qualifier("cassandraDataService")
DataService dataService;

这种方式之所以能解决问题,在于它能让寻找出的 Bean 只有一个(即精确匹配),所以压根不会出现后面的决策过程,可以参考 DefaultListableBeanFactory#doResolveDependency: 


@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
      @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
      //省略其他非关键代码
      //寻找bean过程
      Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
      if (matchingBeans.isEmpty()) {
         if (isRequired(descriptor)) {
            raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
         }
         return null;
      }
      //省略其他非关键代码
      if (matchingBeans.size() > 1) {
         //省略多个bean的决策过程,即案例1重点介绍内容
      } 
     //省略其他非关键代码
}

不过在使用 @Qualifier 时,我们有时候会犯另一个经典的小错误,就是我们可能会忽略 Bean 的名称首字母大小写。这里我们把校正后的案例稍稍变形如下: 


@Autowired
@Qualifier("CassandraDataService")
DataService dataService;
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'studentController': Unsatisfied dependency expressed through field 'dataService'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.spring.puzzle.class2.example2.DataService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true), @org.springframework.beans.factory.annotation.Qualifier(value=CassandraDataService)}

这里我们很容易得出一个结论:对于 Bean 的名字,如果没有显式指明,就应该是类名,不过首字母应该小写 

但是这个轻松得出的结论成立么?不妨再测试下,假设我们需要支持 SQLite 这种数据库,我们定义了一个命名为 SQLiteDataService 的实现,然后借鉴之前的经验,我们很容易使用下面的代码来引用这个实现:


@Autowired
@Qualifier("sQLiteDataService")
DataService dataService;

满怀信心运行完上面的程序,依然会出现之前的错误,而如果改成 SQLiteDataService,则运行通过了。这和之前的结论又矛盾了。所以,显式引用 Bean 时,首字母到底是大写还是小写呢? 

2、案例解析

报错位置


raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);

即当因为名称问题(例如引用 Bean 首字母搞错了)找不到 Bean 时,会直接抛出 NoSuchBeanDefinitionException。 

不显式设置名字的 Bean,其默认名称首字母到底是大写还是小写呢?

当我们启动基于 Spring Boot 的应用程序时,会自动扫描我们的 Package,以找出直接或间接标记了 @Component 的 Bean 的定义(即 BeanDefinition)。例如 CassandraDataService、SQLiteDataService 都被标记了 @Repository,而 Repository 本身被 @Component 标记,所以它们都是间接标记了 @Component。一旦找出这些 Bean 的信息,就可以生成这些 Bean 的名字,然后组合成一个个 BeanDefinitionHolder 返回给上层。这个过程关键步骤可以查看下图的代码片段(ClassPathBeanDefinitionScanner#doScan):

方法调用 BeanNameGenerator#generateBeanName 即用来产生 Bean 的名字,它有两种实现方式。因为 DataService 的实现都是使用注解标记的,所以 Bean 名称的生成逻辑最终调用的其实是 AnnotationBeanNameGenerator#generateBeanName 这种实现方式,我们可以看下它的具体实现,代码如下 


@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
   if (definition instanceof AnnotatedBeanDefinition) {
      String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
      if (StringUtils.hasText(beanName)) {
         // Explicit bean name found.
         return beanName;
      }
   }
   // Fallback: generate a unique default bean name.
   return buildDefaultBeanName(definition, registry);
}

 大体流程只有两步:看 Bean 有没有显式指明名称,如果有则用显式名称,如果没有则产生一个默认名称。很明显,在我们的案例中,是没有给 Bean 指定名字的,所以产生的 Bean 的名称就是生成的默认名称,查看默认名的产生方法 buildDefaultBeanName,其实现如下:


protected String buildDefaultBeanName(BeanDefinition definition) {
   String beanClassName = definition.getBeanClassName();
   Assert.state(beanClassName != null, "No bean class name set");
   String shortClassName = ClassUtils.getShortName(beanClassName);//
   return Introspector.decapitalize(shortClassName);
}

首先,获取一个简短的 ClassName,然后调用 Introspector#decapitalize 方法,设置首字母大写或小写,具体参考下面的代码实现: 

 


public static String decapitalize(String name) {
    if (name == null || name.length() == 0) {
        return name;
    }
    if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
                    Character.isUpperCase(name.charAt(0))){//若第一个第二个都为大写就不变
        return name;
    }
    char chars[] = name.toCharArray();
    chars[0] = Character.toLowerCase(chars[0]);
    return new String(chars);
}

到这,我们很轻松地明白了前面两个问题出现的原因:如果一个类名是以两个大写字母开头的,则首字母不变,其它情况下默认首字母变成小写 

3、问题修正

(1)引用处纠正首字母大小写问题:


@Autowired
@Qualifier("cassandraDataService")
DataService dataService;

(2) 定义处显式指定 Bean 名字


@Repository("CassandraDataService")//显性指定
@Slf4j
public class CassandraDataService implements DataService {
  //省略实现
}

案例 3:引用内部类的 Bean 遗忘类名 

1、例如我们需要定义一个内部类来实现一种新的 DataService,代码如下:


public class StudentController {
    @Repository
    public static class InnerClassDataService implements DataService{
        @Override
        public void deleteStudent(int id) {
          //空实现
        }
    }
    //省略其他非关键代码
 }

遇到这种情况,我们一般都会很自然地用下面的方式直接去显式引用这个 Bean:


@Autowired
@Qualifier("innerClassDataService")
DataService innerClassDataService;

实际上,仍然会报错“找不到 Bean”,这是为什么?

2、案例解析

实际上,我们遭遇的情况是“如何引用内部类的 Bean”。如何产生默认 Bean 名的方法(即 AnnotationBeanNameGenerator#buildDefaultBeanName),有一行语句是对 class 名字的处理,代码如下:

String shortClassName = ClassUtils.getShortName(beanClassName);

参考 ClassUtils#getShortName 方法:


public static String getShortName(String className) {
   Assert.hasLength(className, "Class name must not be empty");
   int lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR);
   int nameEndIndex = className.indexOf(CGLIB_CLASS_SEPARATOR);
   if (nameEndIndex == -1) {
      nameEndIndex = className.length();
   }
   String shortName = className.substring(lastDotIndex + 1, nameEndIndex);
   shortName = shortName.replace(INNER_CLASS_SEPARATOR, PACKAGE_SEPARATOR);
   return shortName;
}

很明显,假设我们是一个内部类,例如下面的类名:

com.spring.puzzle.class2.example3.StudentController.InnerClassDataService

在经过这个方法的处理后,我们得到的其实是下面这个名称:

StudentController.InnerClassDataService

最后经过 Introspector.decapitalize 的首字母变换,最终获取的 Bean 名称如下:

studentController.InnerClassDataService

3、问题修正 


@Autowired
@Qualifier("studentController.InnerClassDataService")
DataService innerClassDataService;

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
1. 简介 1.1. 概览 1.2. 使用场景 2. Spring 2.0 的新特性 2.1. 简介 2.2. 控制反转(IoC)容器 2.2.1. 更简单的XML配置 2.2.2. 新的bean作用域 2.2.3. 可扩展的XML编写 2.3. 面向切面编程(AOP) 2.3.1. 更加简单的AOP XML配置 2.3.2. 对@AspectJ 切面的支持 2.4. 间层 2.4.1. 在XML里更为简单的声明性事务配置 2.4.2. JPA 2.4.3. 异步的JMS 2.4.4. JDBC 2.5. Web层 2.5.1. Spring MVC的表单标签库 2.5.2. Spring MVC合理的默认值 2.5.3. Portlet 框架 2.6. 其他特性 2.6.1. 动态语言支持 2.6.2. JMX 2.6 .3. 任务规划 2.6.4. 对Java 5(Tiger)的支持 2.7. 移植到Spring 2.0 2.7.1. 一些变化 2.7.1.1. Jar包 2.7.1.2. XML配置 2.7.1.3. Deprecated的类和方法 2.7.1.4. Apache OJB 2.7.1.5. iBatis 2.8. 更新的样例应用 2.9. 改进的文档 I. 核心技术 3. 控制反转容器 3.1. 简介 3.2. 容器和bean的基本原理 3.2.1. 容器 3.2.1.1. 配置元数据 3.2.2. 实例化容器 3.2.2.1. 组成基于XML配置元数据 3.2.3. 多种bean 3.2.3.1. 命名bean 3.2.3.2. 实例化bean 3.2.4. 使用容器 3.3. 依赖 3.3.1. 注入依赖 3.3.1.1. Setter注入 3.3.1.2. 构造器注入 3.3.1.3. 一些例子 3.3.2. 构造器参数的解析 3.3.2.1. 构造器参数类型匹配 3.3.2.2. 构造器参数的索引 3.3.3. bean属性及构造器参数详解 3.3.3.1. 直接量(基本类型、Strings类型等。) 3.3.3.2. 引用其它的bean(协作者) 3.3.3.3. 内部bean 3.3.3.4. 集合 3.3.3.5. Nulls 3.3.3.6. XML-based configuration metadata shortcuts 3.3.3.7. 组合属性名称 3.3.4. 使用depends-on 3.3.5. 延迟初始化bean 3.3.6. 自动装配(autowire)协作者 3.3.6.1. 设置Bean使自动装配失效 3.3.7. 依赖检查 3.3.8. 方法注入 3.3.8.1. Lookup方法注入 3.3.8.2. 自定义方法的替代方案 3.4. bean的作用域 3.4.1. Singleton作用域 3.4.2. Prototype作用域 3.4.3. 其他作用域 3.4.3.1. 初始化web配置 3.4.3.2. Request作用域 3.4.3.3. Session作用域 3.4.3.4. global session作用域 3.4.3.5. 作用域bean与依赖 3.4.4. 自定义作用域 3.5. 定制bean特性 3.5.1. Lifecycle接口 3.5.1.1. 初始化回调 3.5.1.2. 析构回调 3.5.2. 了解自己 3.5.2.1. BeanFactoryAware 3.5.2.2. BeanNameAware 3.6. bean定义的继承 3.7. 容器扩展点 3.7.1. 用BeanPostProcessor定制bean 3.7.1.1. 使用BeanPostProcessor的Hello World示例 3.7.1.2. RequiredAnnotationBeanPostProcessor示例 3.7.2. 用BeanFactoryPostProcessor定制配置元数据 3.7.2.1. PropertyPlaceholderConfigurer示例 3.7.2.2. PropertyOverrideConfigurer示例 3.7.3. 使用FactoryBean定制实例化逻辑 3.8. ApplicationContext 3.8.1. 利用MessageSource实现国际化 3.8.2. 事件 3.8.3. 底层资源的访问 3.8.4. ApplicationContext在WEB应用的实例化 3.9. 粘合代码和可怕的singleton 3.9.1. 使用Singleton-helper类 4. 资源 4.1. 简介 4.2. Resource 接口 4.3. 内置 Resource 实现 4.3.1. UrlResource 4.3.2. ClassPathResource 4.3.3. FileSystemResource 4.3.4. ServletContextResource 4.3.5. InputStreamResource 4.3.6. ByteArrayResource 4.4. ResourceLoader 4.5. ResourceLoaderAware 接口 4.6. 把Resource作为属性来配置 4.7. Application context 和Resource 路径 4.7.1. 构造application context 4.7.1.1. 创建 ClassPathXmlApplicationContext 实例 - 简介 4.7.2. Application context构造器资源路径的通配符 4.7.2.1. Ant风格的pattern 4.7.2.2. classpath*: 前缀 4.7.2.3. 其他关于通配符的说明 4.7.3. FileSystemResource 提示 5. 校验,数据绑定,BeanWrapper,与属性编辑器 5.1. 简介 5.2. 使用Spring的Validator接口进行校验 5.3. 从错误代码到错误信息 5.4. Bean处理和BeanWrapper 5.4.1. 设置和获取属性值以及嵌套属性 5.4.2. 内建的PropertyEditor实现 5.4.2.1. 注册用户自定义的PropertyEditor 6. 使用Spring进行面向切面编程(AOP) 6.1. 简介 6.1.1. AOP概念 6.1.2. Spring AOP的功能和目标 6.1.3. Spring的AOP代理 6.2. @AspectJ支持 6.2.1. 启用@AspectJ支持 6.2.2. 声明一个切面 6.2.3. 声明一个切入点(pointcut) 6.2.3.1. 切入点指定者的支持 6.2.3.2. 合并切入点表达式 6.2.3.3. 共享常见的切入点(pointcut)定义 6.2.3.4. 示例 6.2.4. 声明通知 6.2.4.1. 前置通知(Before advice) 6.2.4.2. 返回后通知(After returning advice) 6.2.4.3. 抛出后通知(After throwing advice) 6.2.4.4. 后通知(After (finally) advice) 6.2.4.5. 环绕通知(Around Advice) 6.2.4.6. 通知参数(Advice parameters) 6.2.4.7. 通知(Advice)顺序 6.2.5. 引入(Introductions) 6.2.6. 切面实例化模型 6.2.7. 例子 6.3. Schema-based AOP support 6.3.1. 声明一个切面 6.3.2. 声明一个切入点 6.3.3. 声明通知 6.3.3.1. 通知(Advice) 6.3.3.2. 返回后通知(After returning advice) 6.3.3.3. 抛出异常后通知(After throwing advice) 6.3.3.4. 后通知(After (finally) advice) 6.3.3.5. 通知 6.3.3.6. 通知参数 6.3.3.7. 通知顺序 6.3.4. 引入 6.3.5. 切面实例化模型 6.3.6. Advisors 6.3.7. 例子 6.4. AOP声明风格的选择 6.4.1. Spring AOP还是完全用AspectJ? 6.4.2. Spring AOP使用@AspectJ还是XML? 6.5. 混合切面类型 6.6. 代理机制 6.7. 编程方式创建@AspectJ代理 6.8. 在Spring应用使用AspectJ 6.8.1. 在Spring使用AspectJ来为domain object进行依赖注入 6.8.1.1. @Configurable object的单元测试 6.8.1.2. 多application context情况下的处理 6.8.2. Spring其他的AspectJ切面 6.8.3. 使用Spring IoC来配置AspectJ的切面 6.8.4. 在Spring应用使用AspectJ Load-time weaving(LTW) 6.9. 其它资源 7. Spring AOP APIs 7.1. 简介 7.2. Spring的切入点API 7.2.1. 概念 7.2.2. 切入点实施 7.2.3. AspectJ切入点表达式 7.2.4. 便利的切入点实现 7.2.4.1. 静态切入点 7.2.4.2. 动态切入点 7.2.5. 切入点的基类 7.2.6. 自定义切入点 7.3. Spring的通知API 7.3.1. 通知的生命周期 7.3.2. Spring里的通知类型 7.3.2.1. 拦截around通知 7.3.2.2. 前置通知 7.3.2.3. 异常通知 7.3.2.4. 后置通知 7.3.2.5. 引入通知 7.4. Spring里的advisor(Advisor) API 7.5. 使用ProxyFactoryBean创建AOP代理 7.5.1. 基础 7.5.2. JavaBean属性 7.5.3. 基于JDK和CGLIB的代理 7.5.4. 对接口进行代理 7.5.5. 对类进行代理 7.5.6. 使用“全局”advisor 7.6. 简化代理定义 7.7. 使用ProxyFactory通过编程创建AOP代理 7.8. 操作被通知对象 7.9. 使用“自动代理(autoproxy)”功能 7.9.1. 自动代理bean定义 7.9.1.1. BeanNameAutoProxyCreator 7.9.1.2. DefaultAdvisorAutoProxyCreator 7.9.1.3. AbstractAdvisorAutoProxyCreator 7.9.2. 使用元数据驱动的自动代理 7.10. 使用TargetSources 7.10.1. 热交换目标源 7.10.2. 池化目标源 7.10.3. 原型目标源 7.10.4. ThreadLocal目标源 7.11. 定义新的通知类型 7.12. 更多资源 8. 测试 8.1. 简介 8.2. 单元测试 8.3. 集成测试 8.3.1. Context管理和缓存 8.3.2. 测试fixture的依赖注入 8.3.3. 事务管理 8.3.4. 方便的变量 8.3.5. 示例 8.3.6. 运行集成测试 8.4. 更多资源 II. 间层数据访问 9. 事务管理 9.1. 简介 9.2. 动机 9.3. 关键抽象 9.4. 使用资源同步的事务 9.4.1. 高层次方案 9.4.2. 低层次方案 9.4.3. TransactionAwareDataSourceProxy 9.5. 声明式事务管理 9.5.1. 理解Spring的声明式事务管理实现 9.5.2. 第一个例子 9.5.3. 回滚 9.5.4. 为不同的bean配置不同的事务语义 9.5.5. <tx:advice/> 有关的设置 9.5.6. 使用 @Transactional 9.5.6.1. @Transactional 有关的设置 9.5.7. 插入事务操作 9.5.8. 结合AspectJ使用 @Transactional 9.6. 编程式事务管理 9.6.1. 使用 TransactionTemplate 9.6.2. 使用 PlatformTransactionManager 9.7. 选择编程式事务管理还是声明式事务管理 9.8. 与特定应用服务器集成 9.8.1. BEA WebLogic 9.8.2. IBM WebSphere 9.9. 公共问题的解决方案 9.9.1. 对一个特定的 DataSource 使用错误的事务管理器 9.10. 更多的资源 10. DAO支持 10.1. 简介 10.2. 一致的异常层次 10.3. 一致的DAO支持抽象类 11. 使用JDBC进行数据访问 11.1. 简介 11.1.1. Spring JDBC包结构 11.2. 利用JDBC核心类实现JDBC的基本操作和错误处理 11.2.1. JdbcTemplate类 11.2.2. NamedParameterJdbcTemplate类 11.2.3. SimpleJdbcTemplate类 11.2.4. DataSource接口 11.2.5. SQLExceptionTranslator接口 11.2.6. 执行SQL语句 11.2.7. 执行查询 11.2.8. 更新数据库 11.3. 控制数据库连接 11.3.1. DataSourceUtils类 11.3.2. SmartDataSource接口 11.3.3. AbstractDataSource类 11.3.4. SingleConnectionDataSource类 11.3.5. DriverManagerDataSource类 11.3.6. TransactionAwareDataSourceProxy类 11.3.7. DataSourceTransactionManager类 11.4. 用Java对象来表达JDBC操作 11.4.1. SqlQuery类 11.4.2. MappingSqlQuery类 11.4.3. SqlUpdate类 11.4.4. StoredProcedure类 11.4.5. SqlFunction类 12. 使用ORM工具进行数据访问 12.1. 简介 12.2. Hibernate 12.2.1. 资源管理 12.2.2. 在Spring的application context创建 SessionFactory 12.2.3. HibernateTemplate 12.2.4. 不使用回调的基于Spring的DAO实现 12.2.5. 基于Hibernate3的原生API实现DAO 12.2.6. 编程式的事务划分 12.2.7. 声明式的事务划分 12.2.8. 事务管理策略 12.2.9. 容器资源 vs 本地资源 12.2.10. 在应用服务器使用Hibernate的注意点 12.3. JDO 12.3.1. 建立PersistenceManagerFactory 12.3.2. JdoTemplate和JdoDaoSupport 12.3.3. 基于原生的JDO API实现DAO 12.3.4. 事务管理 12.3.5. JdoDialect 12.4. Oracle TopLink 12.4.1. SessionFactory 抽象层 12.4.2. TopLinkTemplate 和 TopLinkDaoSupport 12.4.3. 基于原生的TopLink API的DAO实现 12.4.4. 事务管理 12.5. iBATIS SQL Maps 12.5.1. iBATIS 1.x和2.x的概览与区别 12.5.2. iBATIS SQL Maps 1.x 12.5.2.1. 创建SqlMap 12.5.2.2. 使用 SqlMapTemplate 和 SqlMapDaoSupport 12.5.3. iBATIS SQL Maps 2.x 12.5.3.1. 创建SqlMapClient 12.5.3.2. 使用 SqlMapClientTemplate 和 SqlMapClientDaoSupport 12.5.3.3. 基于原生的iBATIS API的DAO实现 12.6. JPA 12.6.1. 在Spring环境建立JPA 12.6.1.1. LocalEntityManagerFactoryBean 12.6.1.2. LocalContainerEntityManagerFactoryBean 12.6.1.3. 处理多个持久化单元 12.6.2. JpaTemplate 和 JpaDaoSupport 12.6.3. 基于原生的JPA实现DAO 12.6.4. 异常转化 12.6.5. 事务管理 12.6.6. JpaDialect III. Web 13. Web框架 13.1. 介绍 13.1.1. 与其他web框架的集成 13.1.2. Spring Web MVC框架的特点 13.2. DispatcherServlet 13.3. 控制器 13.3.1. AbstractController 和 WebContentGenerator 13.3.2. 其它的简单控制器 13.3.3. MultiActionController 13.3.4. 命令控制器 13.4. 处理器映射(handler mapping) 13.4.1. BeanNameUrlHandlerMapping 13.4.2. SimpleUrlHandlerMapping 13.4.3. 拦截器(HandlerInterceptor) 13.5. 视图与视图解析 13.5.1. 视图解析器 13.5.2. 视图解析链 13.5.3. 重定向(Rediret)到另一个视图 13.5.3.1. RedirectView 13.5.3.2. redirect:前缀 13.5.3.3. forward:前缀 13.6. 本地化解析器 13.6.1. AcceptHeaderLocaleResolver 13.6.2. CookieLocaleResolver 13.6.3. SessionLocaleResolver 13.6.4. LocaleChangeInterceptor 13.7. 使用主题 13.7.1. 简介 13.7.2. 如何定义主题 13.7.3. 主题解析器 13.8. Spring对分段文件上传(multipart file upload)的支持 13.8.1. 介绍 13.8.2. 使用MultipartResolver 13.8.3. 在表单处理分段文件上传 13.9. 使用Spring的表单标签库 13.9.1. 配置标签库 13.9.2. form标签 13.9.3. input标签 13.9.4. checkbox标签 13.9.5. radiobutton标签 13.9.6. password标签 13.9.7. select标签 13.9.8. option标签 13.9.9. options标签 13.9.10. textarea标签 13.9.11. hidden标签 13.9.12. errors标签 13.10. 处理异常 13.11. 惯例优先原则(convention over configuration) 13.11.1. 对控制器的支持: ControllerClassNameHandlerMapping 13.11.2. 对模型的支持:ModelMap (ModelAndView) 13.11.3. 对视图的支持: RequestToViewNameTranslator 13.12. 其它资源 14. 集成视图技术 14.1. 简介 14.2. JSP和JSTL 14.2.1. 视图解析器 14.2.2. 'Plain-old' JSPs versus JSTL 'Plain-old' JSP与JSTL 14.2.3. 帮助简化开发的额外的标签 14.3. Tiles 14.3.1. 需要的资源 14.3.2. 如何集成Tiles 14.3.2.1. InternalResourceViewResolver 14.3.2.2. ResourceBundleViewResolver 14.4. Velocity和FreeMarker 14.4.1. 需要的资源 14.4.2. Context 配置 14.4.3. 创建模板 14.4.4. 高级配置 14.4.4.1. velocity.properties 14.4.4.2. FreeMarker 14.4.5. 绑定支持和表单处理 14.4.5.1. 用于绑定的宏 14.4.5.2. 简单绑定 14.4.5.3. 表单输入生成宏 14.4.5.4. 重载HTML转码行为并使你的标签符合XHTML 14.5. XSLT 14.5.1. 写在段首 14.5.1.1. Bean 定义 14.5.1.2. 标准MVC控制器代码 14.5.1.3. 把模型数据转化为XML 14.5.1.4. 定义视图属性 14.5.1.5. 文档转换 14.5.2. 小结 14.6. 文档视图(PDF/Excel) 14.6.1. 简介 14.6.2. 配置和安装 14.6.2.1. 文档视图定义 14.6.2.2. Controller 代码 14.6.2.3. Excel视图子类 14.6.2.4. PDF视图子类 14.7. JasperReports 14.7.1. 依赖的资源 14.7.2. 配置 14.7.2.1. 配置ViewResolver 14.7.2.2. 配置View 14.7.2.3. 关于报表文件 14.7.2.4. 使用 JasperReportsMultiFormatView 14.7.3. 构造ModelAndView 14.7.4. 使用子报表 14.7.4.1. 配置子报表文件 14.7.4.2. 配置子报表数据源 14.7.5. 配置Exporter的参数 15. 集成其它Web框架 15.1. 简介 15.2. 通用配置 15.3. JavaServer Faces 15.3.1. DelegatingVariableResolver 15.3.2. FacesContextUtils 15.4. Struts 15.4.1. ContextLoaderPlugin 15.4.1.1. DelegatingRequestProcessor 15.4.1.2. DelegatingActionProxy 15.4.2. ActionSupport 类 15.5. Tapestry 15.5.1. 注入 Spring 托管的 beans 15.5.1.1. 将 Spring Beans 注入到 Tapestry 页面 15.5.1.2. 组件定义文件 15.5.1.3. 添加抽象访问方法 15.5.1.4. 将 Spring Beans 注入到 Tapestry 页面 - Tapestry 4.0+ 风格 15.6. WebWork 15.7. 更多资源 16. Portlet MVC框架 16.1. 介绍 16.1.1. 控制器 - MVC的C 16.1.2. 视图 - MVC的V 16.1.3. Web作用范围的Bean 16.2. DispatcherPortlet 16.3. ViewRendererServlet 16.4. 控制器 16.4.1. AbstractController和PortletContentGenerator 16.4.2. 其它简单的控制器 16.4.3. Command控制器 16.4.4. PortletWrappingController 16.5. 处理器映射 16.5.1. PortletModeHandlerMapping 16.5.2. ParameterHandlerMapping 16.5.3. PortletModeParameterHandlerMapping 16.5.4. 增加 HandlerInterceptor 16.5.5. HandlerInterceptorAdapter 16.5.6. ParameterMappingInterceptor 16.6. 视图和它们的解析 16.7. Multipart文件上传支持 16.7.1. 使用PortletMultipartResolver 16.7.2. 处理表单里的文件上传 16.8. 异常处理 16.9. Portlet应用的部署 IV. 整合 17. 使用Spring进行远程访问与Web服务 17.1. 简介 17.2. 使用RMI暴露服务 17.2.1. 使用 RmiServiceExporter 暴露服务 17.2.2. 在客户端链接服务 17.3. 使用Hessian或者Burlap通过HTTP远程调用服务 17.3.1. 为Hessian配置DispatcherServlet 17.3.2. 使用HessianServiceExporter暴露你的bean 17.3.3. 客户端连接服务 17.3.4. 使用Burlap 17.3.5. 对通过Hessian或Burlap暴露的服务使用HTTP基础认证 17.4. 使用HTTP调用器暴露服务 17.4.1. 暴露服务对象 17.4.2. 在客户端连接服务 17.5. Web服务 17.5.1. 使用JAXI-RPC暴露服务 17.5.2. 访问Web服务 17.5.3. 注册bean映射 17.5.4. 注册自己的处理方法 17.5.5. 使用XFire来暴露Web服务 17.6. 对远程接口不提供自动探测 17.7. 在选择这些技术的一些考虑 18. Enterprise Java Bean(EJB)集成 18.1. 简介 18.2. 访问EJB 18.2.1. 概念 18.2.2. 访问本地的无状态Session Bean(SLSB) 18.2.3. 访问远程SLSB 18.3. 使用Spring提供的辅助类实现EJB组件 19. JMS 19.1. 简介 19.2. 使用Spring JMS 19.2.1. JmsTemplate 19.2.2. 连接工厂 19.2.3. (消息)目的地管理 19.2.4. 消息侦听容器 19.2.4.1. SimpleMessageListenerContainer 19.2.4.2. DefaultMessageListenerContainer 19.2.4.3. ServerSessionMessageListenerContainer 19.2.5. 事务管理 19.3. 发送一条消息 19.3.1. 使用消息转换器 19.3.2. SessionCallback 和ProducerCallback 19.4. 接收消息 19.4.1. 同步接收 19.4.2. 异步接收 - 消息驱动的POJOs 19.4.3. SessionAwareMessageListener 接口 19.4.4. MessageListenerAdapter 19.4.5. 事务的多方参与 20. JMX 20.1. 介绍 20.2. 输出bean到JMX 20.2.1. 创建一个MBeanServer 20.2.2. 复用现有的MBeanServer 20.2.3. MBean的惰性初始化 20.2.4. MBean的自动注册 20.2.5. 控制注册行为 20.3. 控制bean的管理接口 20.3.1. MBeanInfoAssembler 接口 20.3.2. 使用源码级元数据 20.3.3. 使用JDK 5.0注解 20.3.4. 源代码级的元数据类型 20.3.5. 接口AutodetectCapableMBeanInfoAssembler 20.3.6. 用Java接口定义管理接口 20.3.7. 使用MethodNameBasedMBeanInfoAssembler 20.4. 控制bean的 ObjectName 20.4.1. 从Properties读取ObjectName 20.4.2. 使用 MetadataNamingStrategy 20.5. JSR-160连接器 20.5.1. 服务器端连接器 20.5.2. 客户端连接器 20.5.3. 基于Burlap/Hessian/SOAP的JMX 20.6. 通过代理访问MBeans 20.7. 通知 20.7.1. 为通知注册监听器 20.7.2. 发布通知 20.8. 更多资源 21. JCA CCI 21.1. 介绍 21.2. 配置CCI 21.2.1. 连接器配置 21.2.2. 在Spring配置ConnectionFactory 21.2.3. 配置CCI连接 21.2.4. 使用一个 CCI 单连接 21.3. 使用Spring的 CCI访问支持 21.3.1. 记录转换 21.3.2. CciTemplate 类 21.3.3. DAO支持 21.3.4. 自动输出记录生成 21.3.5. 总结 21.3.6. 直接使用一个 CCI Connection 接口和Interaction接口 21.3.7. CciTemplate 使用示例 21.4. 建模CCI访问为操作对象 21.4.1. MappingRecordOperation 21.4.2. MappingCommAreaOperation 21.4.3. 自动输出记录生成 21.4.4. 总结 21.4.5. MappingRecordOperation 使用示例 21.4.6. MappingCommAreaOperation 使用示例 21.5. 事务 22. Spring邮件抽象层 22.1. 简介 22.2. Spring邮件抽象结构 22.3. 使用Spring邮件抽象 22.3.1. 可插拔的MailSender实现 22.4. 使用 JavaMail MimeMessageHelper 22.4.1. 创建一条简单的MimeMessage,并且发送出去 22.4.2. 发送附件和嵌入式资源(inline resources) 23. Spring的定调度(Scheduling)和线程池(Thread Pooling) 23.1. 简介 23.2. 使用OpenSymphony Quartz 调度器 23.2.1. 使用JobDetailBean 23.2.2. 使用 MethodInvokingJobDetailFactoryBean 23.2.3. 使用triggers和SchedulerFactoryBean来包装任务 23.3. 使用JDK Timer支持类 23.3.1. 创建定制的timers 23.3.2. 使用 MethodInvokingTimerTaskFactoryBean类 23.3.3. 打包:使用TimerFactoryBean来设置任务 23.4. SpringTaskExecutor抽象 23.4.1. TaskExecutor接口 23.4.2. 何使用TaskExecutor接口 23.4.3. TaskExecutor类型 23.4.4. 使用TaskExecutor接口 24. 动态语言支持 24.1. 介绍 24.2. 第一个例子 24.3. 定义动态语言支持的bean 24.3.1. 公共概念 24.3.1.1. <lang:language/> 元素 24.3.1.2. Refreshable bean 24.3.1.3. 内置动态语言源文件 24.3.1.4. 理解dynamic-language-backed bean context的构造器注入 24.3.2. JRuby beans 24.3.3. Groovy beans 24.3.4. BeanShell beans 24.4. 场景 24.4.1. Spring MVC控制器脚本化 24.4.2. Validator脚本化 24.5. 更多的资源 25. 注解和源代码级的元数据支持 25.1. 简介 25.2. Spring的元数据支持 25.3. 注解 25.3.1. @Required 25.3.2. Spring的其它@Annotations 25.4. 集成Jakarta Commons Attributes 25.5. 元数据和Spring AOP自动代理 25.5.1. 基本原理 25.5.2. 声明式事务管理 25.5.3. 缓冲 25.5.4. 自定义元数据 25.6. 使用属性来减少MVC web层配置 25.7. 元数据属性的其它用法 25.8. 增加对额外元数据API的支持 A. XML Schema-based configuration A.1. Introduction A.2. XML Schema-based configuration A.2.1. Referencing the schemas A.2.2. The util schema A.2.2.1. <util:constant/> A.2.2.2. <util:property-path/> A.2.2.3. <util:properties/> A.2.2.4. <util:list/> A.2.2.5. <util:map/> A.2.2.6. <util:set/> A.2.3. The jee schema A.2.3.1. <jee:jndi-lookup/> (simple) A.2.3.2. <jee:jndi-lookup/> (with single JNDI environment setting) A.2.3.3. <jee:jndi-lookup/> (with multiple JNDI environment settings) A.2.3.4. <jee:jndi-lookup/> (complex) A.2.3.5. <jee:local-slsb/> (simple) A.2.3.6. <jee:local-slsb/> (complex) A.2.3.7. <jee:remote-slsb/> A.2.4. The lang schema A.2.5. The tx (transaction) schema A.2.6. The aop schema A.2.7. The tool schema A.2.8. The beans schema A.3. Setting up your IDE A.3.1. Setting up Eclipse A.3.2. Setting up IntelliJ IDEA A.3.3. Integration issues A.3.3.1. XML parsing errors in the Resin v.3 application server B. Extensible XML authoring B.1. Introduction B.2. Authoring the schema B.3. Coding a NamespaceHandler B.4. Coding a BeanDefinitionParser B.5. Registering the handler and the schema B.5.1. META-INF/spring.handlers B.5.2. META-INF/spring.schemas C. spring-beans-2.0.dtd D. spring.tld D.1. Introduction D.2. The bind tag D.3. The escapeBody tag D.4. The hasBindErrors tag D.5. The htmlEscape tag D.6. The message tag D.7. The nestedPath tag D.8. The theme tag D.9. The transform tag E. spring-form.tld E.1. Introduction E.2. The checkbox tag E.3. The errors tag E.4. The form tag E.5. The hidden tag E.6. The input tag E.7. The label tag E.8. The option tag E.9. The options tag E.10. The password tag E.11. The radiobutton tag E.12. The select tag E.13. The textarea tag F. Spring 2.0 开发手册文化项目 F.1. 声明 F.2. 致谢 F.3. 参与人员及任务分配 F.4. Spring 2.0 正式版开发手册翻译说明 F.5. 项目历程 F.5.1. Spring 2.0 RC2 开发手册翻译项目 F.5.2. Spring 2.0 正式版开发手册翻译项目

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一点知趣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值