转载--浅谈spring4泛型依赖注入

转载自某SDN-4O4NotFoundjava

 

Spring 4.0版本中更新了不少新功能,其中比较重要的一个就是对带泛型的Bean进行依赖注入的支持。Spring4的这个改动使得代码能够利用泛型进行进一步的精简优化。spring

 

1. 泛型依赖注入的优势

泛型依赖注入就是容许咱们在使用spring进行依赖注入的同时,利用泛型的优势对代码进行精简,将可重复使用的代码所有放到一个类之中,方便之后的维护和修改。同时在不增长代码的状况下增长代码的复用性。下面咱们用一个例子来简单讲解一下泛型的优势:数据库

假设咱们已经定义好了两个实体类Student和Faculty:ide

public class Student {  
    private int id;  
    private String name;  
    private double grade;  
      
    public Student(int id, String name, double grade) {  
        this.id = id;  
        this.name = name;  
        this.grade = grade;  
    }  
  
    @Override  
    public String toString() {  
        return "Student [id=" + id + ", name=" + name + ", grade=" + grade + "]";  
    }  
}
public class Faculty {  
    private int id;  
    private String name;  
    private String evaluation;  
      
    public Faculty(int id, String name, String evaluation) {  
        this.id = id;  
        this.name = name;  
        this.evaluation = evaluation;  
    }  
  
    @Override  
    public String toString() {  
        return "Faculty [id=" + id + ", name=" + name + ", evaluation=" + evaluation + "]";  
    }  
}

  而后咱们须要持久层Bean来调用Student和Faculty里面的方法。在Spring支持泛型依赖注入以前,咱们须要为两个实体类分别定义一个持久层Bean,而后从数据库获取咱们的实体类,再调用实体类中的方法。使用这种原始方法的代码以下:工具

public class StudentRepository {    
    public Student getBean(String beanName) {  
        //获取对应的Student  
    }  
      
    public void printString(Student s) {  
        System.out.println(s);  
    }  
}
public class FacultyRepository {    
    public Faculty getBean(String beanName) {  
        //获取对应的Faculty  
    }  
      
    public void printString(Faculty f) {  
        System.out.println(f);  
    }  
}

  你们能够看到,这样的代码每一个实体类都须要编写一个新的持久层Bean,每个持久层Bean中的实体类类型都是写死的,复用性不好。更重要的是,因为每一个持久层Bean中所包含的实体类不一样,持久层Bean中重复的方法(如上面例子中的printString)须要在每个持久层Bean中都实现一次,这大大增长了代码的维护成本。学习

固然,有一些方法能够部分解决这个问题。好比咱们能够定义一个持久层Bean的父类BaseRepository,而后在里面编写一个通用的pirntString方法:测试

public class BaseRepository {  
    public void printString(Object o) {  
        System.out.println(o);  
    }  
}

  接着,咱们能够在各个持久层Bean中调用BaseRepository的方法来实现printString:优化

public class StudentRepository extends BaseRepository{      
    public Student getBean(String beanName) {  
        //获取对应的Student  
    }  
      
    public void printString(Student s) {  
        super.printString(s);  
    }  
}

  

这样的话,printString的实现实际上只编写了一遍,所以咱们提升了代码的复用性。同时,当printString方法不是简单的打印到控制台,而具备复杂的代码和逻辑时,咱们能够把代码所有放在BaseRepository中,方便之后的修改和维护。可是,这种方法仍然要求每个持久层Bean编写一个printSring方法来调用父类的方法,尽管这个方法只有简单的一行,当相似的方法多起来以后代码的数量仍是很可观的。this

除了加入父类以外,还有一些其余的方法能够减小代码量,提升代码的复用性。好比咱们能够在父类中加入setter方法使得业务层能够为持久层手工注入实体类的类别(如Student.class),可是并无很是好的解决方案。lua

 

可是当咱们使用泛型时,这些问题就迎刃而解了。咱们只须要定义一个持久层Bean,BaseRepository,也就是上面例子中的父类,而不须要任何子类:

public class BaseRepository<T> {  
    public T getBean(String beanName) {  
        //获取对应的t  
    }  
      
    public void printString(T t) {  
        System.out.println(t);  
    }  
}

  

这个持久层Bean能够包含全部咱们在持久层想要复用的方法。经过泛型,咱们的持久层代码能够用在全部实体类身上,而且咱们还能够经过继承方便的添加某些实体类特有的方法。咱们没有增长额外的代码,可是提升了代码复用程度,同时咱们把可重复使用的代码所有集中起来,方便了之后的维护和修改。

上面所讲的内容都是泛型自己的优势,和Spring 4.0的泛型依赖注入并无直接联系。可是,Spring 4.0开始支持的泛型依赖注入对于咱们使用泛型很是重要:在Spring 4.0以前,Spring的依赖注入功能是不能自动识别上面例子中泛型的类,而给不一样的持久层Bean加以区分的。所以在Spring 4.0以前,BaseRepository<Student>和BaseRepository<Faculty>会被认为是同一类型,通常须要用名字等其余方式加以区分。可是如今,Spring会正确的识别声明的泛型类别,而且根据泛型给持久层Bean进行分类。因此Student和Faculty的持久层Bean能够被正确的区分,而且注入到上一层。这为咱们在代码中使用泛型提供了极大的便利。

 

2. 泛型依赖注入的实现

下面咱们就来看看使用泛型的Bean的依赖注入应该如何实现:

使用泛型Bean的依赖注入与普通Bean的依赖注入在实现方法上基本相同,一样能够经过xml配置文件和注解两种方式进行依赖注入。可是因为泛型中尖括号(“<>”)的存在,使得xml配置文件依赖注入过程当中会出现编译报错的状况。有的编译器即便对尖括号进行转义也依然会报错。所以,为了不没必要要的麻烦,建议你们使用注解的方式进行带泛型Bean的依赖注入。

使用注解进行依赖注入有以下几种方式:

 

2.1 使用注解@Configuration进行依赖注入

与普通Bean同样,咱们能够利用注解@Configuration来声明咱们须要的使用泛型的Bean,而且进行依赖注入。

首先咱们须要新建一个类,做为咱们的配置类:

@Configuration  
public class MyConfiguration {  
  
}

  其中,注解@Configuration的做用是告诉spring这个类是一个配置类,这样Spring就会自动扫描这个类中声明的全部Bean,并把它们加入Spring容器中。不过在此以前,咱们须要在spring的配置文件中添加component-scan:

<context:component-scan base-package="com.somepackage.*</context:component-scan>

  以后注解@configuration才会被扫描到,里面声明的Bean才会被添加进Spring容器中

在这以后,咱们就可使用注解对带泛型的Bean进行依赖注入了。

 

首先,咱们须要声明两个须要用到的持久层Bean,一个是Student的持久层Bean,另一个是Faculty的。只有在声明了这两个Bean而且添加到Spring容器中后,Spring才能为咱们进行依赖注入。

在配置类中声明这两个Bean的方法以下:

@Configuration  
public class MyConfiguration {  
    @Bean  
    public BaseRepository<Student> studentRepository() {  
        return new BaseRepository<Student>() {};  
    }  
      
    @Bean  
    public BaseRepository<Faculty> facultyRepository() {  
        return new BaseRepository<Faculty>() {};  
    }  
}

  其中,注解@Bean与Spring的正常使用方法相同,就是声明一个新的Bean。Spring在扫描配置类时,就会把这里声明的Bean加入到Spring容器中,供之后使用。这里每一个Bean的名称就是方法名,如studentRepository,而Bean的类型就是返回的Object的类型(不是方法的返回类型,方法的返回类型能够是Interface等不能实例化的类型)。

若是你还须要声明其余的Bean,好比你不须要从数据库获取数据,也能够把它们加入到这个配置类中。

 

而后咱们就能够定义咱们的业务层Bean,而且用业务层Bean调用持久层的方法来对数据进行操做。咱们这里使用printString方法做为例子:

>@Service  
public class ExampleService {  
    @Autowired private BaseRepository<Student> studentRepo; //自动注入BaseRepository<Student>() {}  
    @Autowired private BaseRepository<Faculty> facultyRepo; //自动注入BaseRepository<Faculty>() {}  
      
    public void test() {  
        Student s = studentRepo.getBean("studentBean");  
        studentRepo.printString(s);  
          
        Faculty f = facultyRepo.getBean("facultyBean");  
        facultyRepo.printString(f);  
    }  
}

  在业务层中,咱们可使用注解@Autowired进行依赖注入。@Autowired默认按照字段的类进行依赖注入,而Spring4的新特性就是把泛型的具体类型(如上文业务层中BaseRepository<Student>中的Student)也做为类的一种分类方法(Qualifier)。这样咱们的studentRepo和facultyRepo虽然是同一个类BaseRepository,可是由于泛型的具体类型不一样,也会被区分开。

这里我先建立了两个实体类实例,而且加入到了刚才提到的配置类中。这样这两个Bean就会被加入到Spring容器之中,而咱们能够在getBean方法当中获取他们。这两个Bean的名字分别是studentBean和facultyBean,与业务层Bean中填写的名字保持一致:

@Bean  
public Student studentBean() {  
    return new Student(1, "Anna", 3.9);  
}  
  
@Bean  
public Faculty facultyBean() {  
    return new Faculty(2, "Bob", "A");  
}

  

固然,若是你有其余方法可以获取到实体类,好比你的工程整合了Hibernate ORM或者其余工具来链接数据库,就不须要向Spring容器中加入对应的Bean了,getBean方法的实现也能够相应的改变。我这里只是用这两个实体类的Bean做为例子。

而后当咱们调用业务层的test方法时,控制台打印的结果是:

Student [id=1, name=Anna, grade=3.9]
Faculty [id=2, name=Bob, evaluation=A]

而当咱们在业务层里试图错误的调用方法:

facultyRepo.printString(s);

的时候,会出现编译错误。

读到这里,可能有的人已经发现了,这个例子存在两个疑点。第一,这个例子不能证实咱们在运行期成功实现了依赖注入,由于咱们在运行期为printString方法传入了Student和Faculty的实例。第二,咱们在声明Bean的时候,声明的类不是BaseRepository,而是BaseRepository的一个匿名子类。

为了解答这两个问题,我在BaseRepository中定义了一个新的方法:

public void printType() {  
    Type genericSuperclass = this.getClass().getGenericSuperclass();  
    Class<T> entityClass = (Class<T>) ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0];  
    System.out.println(entityClass);  
}

  

这段代码前两行的做用是在带泛型的类中,在运行期肯定泛型T的类。若是须要在BaseRepository中使用到T在运行期的具体类型,就应该使用这个方法来获取。下面是一个比较详细的解释:

因为java的擦除机制,泛型T只在编译期有效,在运行期会被擦除,因此咱们在运行期不能直接得到T的类,一些对通常类有效的方法,好比T.class和t.getClass()对泛型都是非法的。所以咱们须要经过反射机制获取T的类。

这段代码第一行的做用是获取当前类的父类,而后在第二行中经过查看父类的类参数得到当前类泛型的类。也就是说,这是一个经过父类的参数来查看子类中泛型的具体类型的方法。所以,这个方法有一些使用上的要求:首先,这个方法必须被带泛型类的子类所使用,带泛型类自己是不能使用这个方法的。另外,这个子类在泛型的位置必须继承一个具体的类,而不是泛型T。举个例子,当有一个类继承BaseBaen<A>时,这个方法就可使用,而继承BaseBean<T>的时候就不能使用,由于A是具体的类,而T是泛型,在运行期就算咱们从父类中取到了T,由于有擦除机制,咱们仍然没法得知T是一个什么类。

值得一提的是,Spring4也是经过一样的方法添加了对泛型依赖注入的支持。所以咱们若是想使用Spring4的新功能,在定义Bean的时候就必须定义为泛型类的子类,如上面例子中的new BaseBean<A>() {}。这个Bean是BaseBean的一个匿名子类,继承的是BaseBean<A>。这样的话Spring就能够正确获取到泛型T的类(A),而且以此为根据帮助咱们实行依赖注入。

Spring的文档和源代码里都有关于注解依赖注入的说明,你们有兴趣的话能够去看一下。

 

与此同时,咱们会发现上面的printType方法是不接收任何实例的,所以这个方法能够帮咱们判断泛型的依赖注入是否成功。为了测试,我对业务层的test方法进行了以下修改:

public void test() {  
    Student s = studentRepo.getBean("studentBean");  
    //studentRepo.printString(s);  
    studentRepo.printType();  
          
    Faculty f = facultyRepo.getBean("facultyBean");  
    //facultyRepo.printString(f);  
    facultyRepo.printType();  
}

而后当咱们调用test方法进行测试时,控制台会打印如下信息:

class com.somepackage.Student
class com.somepackage.Faculty

这些信息说明咱们在没有传入实例的状况下也正确获取到了泛型T的类,泛型的依赖注入成功了。

 

注:

 

1. 前文提到的@Bean注解声明Bean的方法也可使用在@Component注解标注的类当中,可是Spring建议这种作法只在工厂类中使用,并不建议大规模使用。另外,Spring对不在@Component注解标注的配置类中声明的Bean的关联上有一些限制,详细的状况请参照Spring文档。关于注解@Component的正确使用方法,请看下一小节。

 

2. 除了使用@Autowired注解进行依赖注入外,咱们还可使用@Resource注解进行依赖注入。由于@Resource是优先根据名字进行依赖注入,咱们最好让字段的名字与Bean名字相同。

 

 

 

2.2 使用注解@Component等进行依赖注入

 

上一小节咱们讲述了利用@Configuration注解标注的配置类进行泛型依赖注入的实现方法和部分原理。其中咱们提到,若是想要Spring的泛型依赖注入成功,咱们必须把Bean定义为使用泛型的类的子类。而定义一个子类最多见的方法是定义一个新的类,而后进行继承。

 

所以,咱们可使用@Component注解以及它的子注解(如@Controller,@Service和@Repository)来声明一个新的Bean。如上一小节所说,这个子类在泛型的位置必须继承一个具体的类型,而不能继承泛型T,不然Spring的自动依赖注入不会成功。

 

除此以外,这些注解的使用方法都与没有泛型时彻底相同,下面咱们就来看一下具体的代码:

 

我为前面的两种BaseRepository编写了两个子类StudentRepository和FacultyRepository:

@Repository  
public class StudentRepository extends BaseRepository<Student> {  
  
}
@Repository  
public class FacultyRepository extends BaseRepository<Faculty> {  
  
}

而且使用注解@Repository进行标注。这样的话,Spring在扫描时将会扫描到这两个类,并建立两个对应的Bean加入到Spring容器中。固然,若是你想要正确使用Spring的自动扫描功能,须要在Spring配置文件中加入component-scan,详细的作法请参考上一小节。

须要注意的是,使用了@Repository注解就已经往Spring容器中加入了一个Bean。所以,若是你在上一小节编写了@Configuration配置类,请务必把@Configuration注解注释掉,让Spring再也不扫描这个配置类,或者把配置类中两个持久层Bean的@Bean注解注释掉,让Spring再也不扫描这两个持久层。不然Spring在使用@Autowired注解进行依赖注入时会由于同一类型的Bean有两个而报错。

下面是咱们的业务层代码:

public class ExampleService {  
    @Autowired private BaseRepository<Student> studentRepo; //自动注入BaseRepository<Student>() {}  
    @Autowired private BaseRepository<Faculty> facultyRepo; //自动注入BaseRepository<Faculty>() {}  
      
    public void test() {  
        Student s = studentRepo.getBean("studentBean");  
        studentRepo.printString(s);  
        studentRepo.printType();  
          
        Faculty f = facultyRepo.getBean("facultyBean");  
        facultyRepo.printString(f);  
        facultyRepo.printType();  
    }  
}

业务层的代码与上一小节相同,没有作任何修改。注意在须要依赖注入的两个字段中,咱们声明的类型仍然是使用泛型的类BaseRepository,而不是咱们刚才定义的子类StudentRepository和FacultyRepository。实际上,咱们根本不须要知道这些子类的类型,就能够调用子类的方法,这正是Spring依赖注入的强大之处。

当咱们调用test方法时,控制台会打印出如下信息:

Student [id=1, name=Anna, grade=3.9]
class com.somepackage.Student
Faculty [id=2, name=Bob, evaluation=A]
class com.somepackage.Faculty

从这些信息咱们能够看到,Spring在声明Bean的类型与依赖注入目标类型不一样的状况下也能够成功注入。这是由于Spring4开始将泛型的具体类型做为Bean分类的一种方法(Qualifier),所以Spring可以成功区分BaseRepository<Student>和BaseRepository<Faculty>,以及他们的子类。

 可是,这个例子也存在一个问题:由于依赖注入的地方声明的是父类BaseRepository,咱们如何断定Spring为咱们注入的是子类StudentRepository和FacultyRepository,仍是父类BaseRepository呢?实际上咱们根本不用担忧这个问题,由于咱们根本没有声明任何父类BaseRepository类型的Bean,只声明了子类类型的Bean。因此若是Spring依赖注入成功了,就必定注入的是子类类型的Bean。可是在这里,咱们也经过代码验证一下咱们的这个猜测。

为了进行验证,我在StudentRepository和FacultyRepository中覆盖了父类BaseRepository的printString方法:

 

 

@Repository  
public class StudentRepository extends BaseRepository<Student> { @Override public void printString(Student s) { System.out.println("I am StudentRepo - " + s.toString()); } }

 

@Repository  
public class FacultyRepository extends BaseRepository<Faculty> {  
    @Override  
    public void printString(Faculty f) {  
        System.out.println("I am FacultyRepo - " + f.toString());  
    }  
}

 

  

而后当咱们调用业务层的test方法进行测试时,控制台打出了以下信息:

I am StudentRepo - Student [id=1, name=Anna, grade=3.9]
class com.hpe.bboss.autotest.dao.Student
I am FacultyRepo - Faculty [id=2, name=Bob, evaluation=A]
class com.hpe.bboss.autotest.dao.Faculty

这说明Spring为咱们注入的是咱们所但愿的子类StudentRepository和FacultyRepository,而不是父类BaseRepository。

 

注:

1. 当@Component注解标注的多个子类同时继承一个父类,而且泛型的具体类型也相同时,按照以上方法进行依赖注入会抛出异常。这是由于@Autowired注解默认只有一个Bean与指定字段的类型相同,当拥有多个Bean知足条件的时候,就会抛出异常。这个问题的解决办法有使用@Primary注解,使用@Qualifier注解和它的子注解,使用Bean名字注入等。因为这个问题是Spring依赖注入的问题,而不是泛型依赖注入独有的,所以再也不赘述,请你们查阅Spring文档和其余资料来得到具体解决办法。

2.    泛型赖注入并不只限于在持久层使用。咱们也能够在持久层使用泛型依赖注入的基础上,在业务层等其余地方也使用泛型依赖注入。相关的例子在网上很好找到,我就不复制粘贴了,有兴趣的话请自行查阅。

 

2.3   两种依赖注入方式的比较

前文所讲的两种依赖注入方式,本质上是两种不一样的声明Bean的方式。如前文所说,Spring对这两种声明方式都拥有很好的支持,可是这两种声明方式自己仍是拥有比较大的差别。第一种方式中,咱们经过@Configuration注解标注配置类来进行声明。第二种方式中,咱们经过注解直接在子类进行声明。下面我就来简单探讨一下两种方式的优劣。

 

第一种声明方式中,全部的Bean都会在配置类中进行声明。所以在后续进行维护时,咱们不须要查看每一个类的源代码就能够对Bean的状态进行一些修改。另外,这种方式也意味着咱们不须要为每个Bean都建立一个子类,使得目录的管理变得简单。可是使用这种方法意味着咱们每声明一个新的Bean就须要对配置类添加一个方法和至少一个注解,而且有时还须要向匿名子类中添加一些方法,在Bean数量不少时配置类的长度会变得很长,不便于理解和管理。

而第二种声明方式中,咱们根本不须要维护配置文件,全部声明Bean所须要的工做,例如名字,类,加入Spring容器等,都由一个注解完成。与此同时,因为子类的存在,咱们能够很方便的进行添加方法和字段,覆盖方法等工做。可是使用这种方法也意味着当咱们须要对Bean的状态进行修改时,咱们必须找到相应的类才能进行操做。并且大量的子类会让咱们的目录更加繁杂,尤为是空子类,自己没有太大意义,却让目录的管理变得很麻烦。

 

综上所述,两种方式各有各的优缺点,而使用哪一种方法应该根据项目的具体状况而定。通常来讲,当子类中空类较多时,可能使用第一种方法比较合适,反之第二种方法比较合适。在一些难以决定的状况下,两种方法同时使用有时也是一种能够考虑的选择。可是两种方法同时使用会提升维护的难度,建议谨慎使用。

 

3. 泛型依赖注入总结与展望

Spring 4.0版本新加入的泛型依赖注入功能是一个很实用的功能。它帮助咱们利用泛型极大的精简了代码,下降了维护成本。根据我此次的学习和使用来看,Spring对泛型依赖注入的支持整体质量仍是很不错的。泛型依赖注入的实现与普通依赖注入差异并不大,学习起来简单易懂,使用上也没有什么难度。但愿看到这篇文章的你们在之后使用Spring的时候也试着试用一下泛型依赖注入。

 

不过,Spring4的泛型依赖注入也有一些能够改进的地方。我此次研究Spring泛型注入的初衷就是找到一种简单的注入方法,可让我在使用Spring依赖注入的同时,尽量的减小声明的类的数量。可是通过我这段时间的学习,我发现Spring目前必须为每个Bean声明一个新的类,不管是匿名子类仍是空子类,不然Spring就不能正确进行依赖注入。可是当咱们不须要往子类里添加任何功能时,匿名子类或者空子类过多,这个配置类就变得很低效,不管是声明仍是维护管理都很是麻烦。我但愿之后的Spring更新时,可以自动为咱们建立这些匿名子类,或者经过一些别的方式,让咱们既不须要配置类又不须要子类就能够成功的声明一些使用泛型的Bean,而且根据泛型的类型进行依赖注入。好比我但愿这样声明Bean

@Repository  
public class BaseRepository<T> {    
    public void printString(T t) {  
        System.out.println(t);  
    }  
      
    public void printType() {  
        Type genericSuperclass = this.getClass().getGenericSuperclass();  
        Class<T> entityClass = (Class<T>) ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0];  
        System.out.println(entityClass);  
    }  
}

  而后在进行依赖注入的时候,Spring能够经过字段的类型来自动生成匿名子类,并进行注入:

@Autowired private BaseRepository<Student> studentRepo; //自动注入BaseRepository<Student>() {}  
@Autowired private BaseRepository<Faculty> facultyRepo; //自动注入BaseRepository<Faculty>() {}

  若是Spring能够作到这样的话,我相信咱们的开发会变的更加高效。

相关文章
相关标签/搜索