第二章 bean的装配

target

掌握依赖注入的三种方式
掌握基于xml的bean装配
掌握基于注解的bean装配
掌握bean的自动装配
掌握注解 @Autowired 和 @Resource

1. 依赖注入

当一个bean创建出来后,是一个空对象,需要给bean进行赋值,这个bean才有意义。依赖注入就是给IoC中的bean进行赋值,一般可以分为三种。

1.1 set注入

以前的开发中,当创建完bean对象后,一般会使用set方法给对象进行赋值。

set注入,顾名思义就是用实体类的setter方法进行注入。
set注入是用的是最多的注入方式。
注入用的标签是property。

新建 Java项目Spring-02,给Teacher和Course进行注入:

第一步:在src下新建实体类:

Teacher.java:

package com.lee.spring.bean;

public class Teacher {
   private int no;//工号
   private String name;//姓名

   // getter、setter、toString略
}

Course.java

package com.lee.spring.bean;

public class Course {

   private String courseName;//课程名字
   private int courseHours;//课时量
   private Teacher courseTeacher;//授课老师

   / / getter、setter、toString略

}

第二步:在src下新建配置文件 applicationContext.xml:




 
 
     
     
 

 
 
     
     
     
 


注意:
在赋值的时候,如果是简单类型(8个基本类型 + String),用value赋值。如果是对象类型,用ref(ref是reference的简写,引用的意思)。

第三步:编写一个测试类进行测试:

package com.lee.spring.test;

public class TestDemo {

    @Test
    public void test01() {
      //获取上下文对象:Context
      ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
      //获取bean对象
      Course course = (Course)context.getBean("course");
      System.out.println(course);
    }
}

输出:

Course [courseName=语文, courseHours=200, courseTeacher=Teacher [no=1001, name=张三]]

原理解释:

setter注入用到了实体类的setter方法,和getter方法、构造方法都没关系。我们可以在setter方法里面加一行输出语句验证。

set注入底层用的是反射技术,bean标签里的class是全类名,所以可以用Class.forName("全类名")拿到这个类的所有信息,然后用new Instance方法创建一个对象,getmethod()方法拿到所有的set方法,在调用setter方法将值注入。

Class clz = Class.forName("全类名");
Object obj = clz.newInstance();//创建对象
Method method = clz.getMethod("set方法名字");//获取setter方法
method.invoke(obj, value里面的值);//调用setter方法赋值

1.2 构造器注入

对象的创建,使用的是构造方法。

构造器注入用的是实体类的构造方法。
注入用的标签是constructor-arg

第一步:在src下新建实体类,并且生成构造方法

Teacher.java

package com.lee.spring.bean;

public class Teacher {
    private int no;//工号
    private String name;//姓名
    
    public Teacher(int no, String name) {
        this.no = no;
        this.name = name;
    }

    public Teacher() {
    }

    @Override
    public String toString() {
        return "Teacher [no=" + no + ", name=" + name + "]";
    }
    
}

Course.java

package com.lee.spring.bean;

public class Course {

    private String courseName;//课程名字
      private int courseHours;//课时量
      private Teacher courseTeacher;//授课老师
    
    public Course(String courseName, int courseHours, Teacher courseTeacher) {
        this.courseName = courseName;
        this.courseHours = courseHours;
        this.courseTeacher = courseTeacher;
    }

    public Course() {
    }

    @Override
    public String toString() {
        return "Course [courseName=" + courseName + ", courseHours=" + courseHours + ", courseTeacher=" + courseTeacher
                + "]";
    }
      
}

第二步:在 src下新建配置文件applicationContext.xml




  
  
        
        
  

  
  
        
        
        
  


第三步:编写一个测试类,进行测试

package com.lee.spring.test;

public class TestDemo {

    @Test
    public void test02() {
         //获取上下文对象:Context
          ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
          //获取bean对象
          Course course = (Course)context.getBean("course");
          System.out.println(course);
    }
}

输出:Course [courseName=数学, courseHours=300, courseTeacher=Teacher [no=2002, name=李四]]

补充:配置文件还可以这样写

  • 省略name:但是顺序要和构造方法中参数的顺序一致


  
  




  
  
  

  • 用index:下标要从0开始


  
  




  
  
  

原理解释:

构造器注入用到了实体类的构造方法。我们可以在构造方法里面加一行输出语句验证。

构造器注入底层用的也是反射技术,bean标签里的class是全类名,所以可以用Class.forName("全类名")拿到这个类的所有信息,然后用getConstructor()方法获取构造器,调用newInstance()方法就可以创造出对象。

Class clz = Class.forName("全类名");
Constructor constructor = clz.getConstructor(int.class,String.class);
Object instance = constructor.newInstance("value里面的值");

1.3 p命名空间注入

首先需要引入p命名空间,在namespaces里将p勾上。然后在bean标签里面写就行了,注意中间要加上空格。此时直接编写配置文件是没有提示的,当在实体类中加上setter方法,就有提示了,所以p命名空间本质上也是用的setter注入




    
    

    
    

注意:

没有-ref就是简单注入,p:courseName="hadoop"
-ref就是对象注入,p:courseTeacher-ref="teacher"
多个属性赋值时,中间要加空格
三种注入方式,我们绝大多数用set注入。

2. bean装配概述

bean的装配其实做的只有两件事:为bean赋值、组合bean之间的关系。

在 Spring 中提供了 3 种方法进行装配:

  • 基于xml配置装配bean

  • 基于注解装配bean

  • 自动装配

在现实的工作中,这 3 种方式都会被用到,并且在学习和工作之中常常混合使用,所以这里给出一些关于这 3 种优先级的建议:

① 最优先:自动装配

  • 基于约定优于配置的原则,这种方式应该是最优先的

  • 好处:减少程序开发者的决定权,简单又不失灵活。

② 其次:基于注解装配bean

  • 在没有办法使用自动装配原则的情况下应该优先考虑此类方法

  • 好处:避免 XML 配置的泛滥,也更为容易。

  • 典型场景:一个父类有多个子类,比如学生类有两个子类,一个男学生类和女学生类,通过 IoC 容器初始化一个学生类,容器将无法知道使用哪个子类去初始化,这个时候可以使用 Java 的注解配置去指定。

③ 最后:XML 方式配置

  • 在上述方法都无法使用的情况下,那么也只能选择 XML 配置的方式。

  • 好处:简单易懂

通俗来讲,现在开发中,优先考虑 注解装配 + 自动装配。老项目的维护,可以考虑使用 xml方式 装配。

3. 基于xml的装配

前面所讲到的装配都是基于xml方式的装配,因为基于xml的装配更容易理解。

3.1 简单装配

简单装配就是给一个8个基本数据类型+String的属性赋值,比如:



  
  

3.2 集合属性装配

对于一些集合属性的赋值,比如list集合、set集合、map集合、数组、Properties等,我们可以这样来赋值:

第一步:新建实体类

public class CollectionInject {

  private List listElement;
  private Set setElement;
  private String[] strElement;
  private Map mapElement;
  private Properties propElement;

  //getter、setter省略

  @Override
  public String toString() {
    return "listElement=" + listElement + "\nsetElement=" + setElement + "\nstrElement="
      + Arrays.toString(strElement) + "\nmapElement=" + mapElement + "\npropElement=" + propElement ;
  }

}

第二步:在 src新建配置文件 applicationContext.xml




    
    
    
        
        
            
                足球
                篮球
                乒乓球
            
        
        
        
        
            
                语文
                数学
                英语
            
        
        
        
        
            
                手机
                电脑
                平板
            
        
        
        
        
            
                
                    
                        shoes
                    
                    鞋子
                
                
                    
                        cap
                    
                    帽子
                
                
                    
                        coat
                    
                    外套
                
            
        
        
        
        
            
                面包
                巧克力
                牛奶
            
        
    


记忆技巧:除了properties,所有的集合赋值都在value标签里进行。

第三步:在测试类中测试

@Test
public void test04() {
  //获取上下文对象:Context
  ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  //获取bean对象
  CollectionInject coll = (CollectionInject)context.getBean("coll");
  System.out.println(coll);
}

输出:

listElement=[足球, 篮球, 乒乓球]
setElement=[语文, 数学, 英语]
strElement=[手机, 电脑, 平板]
mapElement={shoes=鞋子, cap=帽子, coat=外套}
propElement={bread=面包, chocolate=巧克力, milk=牛奶}

注意:

  • 其中list、set、array可以混合使用

  • 也可以用构造器赋值,和set注入一样,就是换个标签而已


  
    足球
    篮球
    乒乓球
  

3.3 特殊值装配

如果我们想注入一些特殊值:>、<、&等,可以考虑标签。

value和注入的区别:

标签 value属性
参数位置 写在首尾标签()的中间,不加双引号 写在value的属性值中,必须加双引号
参数值包含特殊符号 方式一:使用标记
方式二:使用xml预定义的实体引用
使用xml预定义的实体引用
参数值带双引号输出 可以 不可以

其中,xml预定义实体引用:

实体引用 符号
> >
< <
& &

注意有分号。

① 字符串

配置文件:



  
  

测试:

@Test
public void test05() {
  //获取上下文对象:Context
  ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  //获取bean对象
  Teacher teacher = (Teacher)context.getBean("teacher");
  System.out.println(teacher);
}

输出:[no=5005, name=王五]

还可以使用写:



  王五
  5005

我们如果想输出:[no=5005, name="王五"],就必须用标签:



  "王五"
  5005

② 特殊字符

a. 使用标签


  
    ]]>
  
  

或者


  
    ><&
  
  

b. 使用value属性


  
  

开发中绝大多数使用value属性的方式注入

③ 赋空值

a. 空字符串""

装配空字符串有两种方式:


  

或者


  

b. null

null表示该对象是空的,只能这样做:用标签


   

注意:不能用下面方法做


  

这样表示这个人的名字叫null,而不是空。

4. 基于注解的装配

基于注解的装配就是不使用xml配置文件,使用注解来进行装配。

用三层结构中的Service层来模拟一下注解装配。

用配置文件的方法需要这样做:

StudentService.java

package com.lee.spring.service;

public class StudentService {
    public void addStudent() {
        System.out.println("增加学生。。。");
    }
}

applicationContext.xml



    
    

此时,StudentService类就已经纳入到IOC容器中了,可以通过eclipse左边StudentService类旁边的S来确定是不是纳入成功。

4.1 用注解装配步骤

首先需要在配置文件中打开包扫描功能:



    
  
    
  

然后在StudentService类头上加相关注解即可:

package com.lee.spring.service;

import org.springframework.stereotype.Component;

@Component("studentService")
public class StudentService {
    public void addStudent() {
        System.out.println("增加学生。。。");
    }
}

说明

第一点:

@Component("studentService")
就等价于
注解括号里面的值就是bean的id值。

第二点:

在Spring启动的时候,会根据
中的base-package去扫描这个包下的所有类,如果发现该包下有哪个类存在组件相关的注解,就将他纳入到IOC容器中。

4.2 其他注解

开发中会用到下面四个注解:

@Component:可以在三层中的任何一层中使用,还有一些功能性的bean使用。

@Controller:控制器层使用。

@Service:service层使用。

@Repository:dao层使用。

下面演示一个用@Component注解给普通的bean赋值:

第一步:直接在实体类上标注 @Component("p") 注解,p是bean的名字。

第二步:用@Value("小花")注解直接在属性上赋值,不需要getter、setter方法。

  • 实体类 Person.java:
@Component("p")
public class Person {

    @Value("1001")
    private int id;
    @Value("小花")
    private String name;
    
    @Override
    public String toString() {
        return "Person [id=" + id + ", name=" + name + "]";
    }
}
  • 配置文件 applicationContext.xml :


    
  
    
  

  • 测试
@Test
public void test08() {
  ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  Person p = (Person)context.getBean("p");
  System.out.println(p);
}

输出:Person [id=1001, name=小花]

5. 自动装配

开发中有一个原则:约定优于配置。所以能够按照约定自动去装配的尽量自动装配。

但是需要注意:自动装配仅适用于对象类型,对于简单类型不适用

自动装配分为两种,一种是基于xml方式的自动装配,一种是基于注解方式的自动装配。基于注解方式的自动装配是比较常用的。

5.1 基于xml的自动装配

自动装配分为三种,分别是 通过名字装配、通过类型装配、使用构造器装配。

① byName

  • 新建实体类:

Teacher.java

public class Teacher {

  private int no;
  private String teaName;

  //getter、setter省略
  @Override
  public String toString() {
    return "Teacher [no=" + no + ", teaName=" + teaName + "]";
  }
}

Course.java

public class Course {

    private String courseName;//课程名字
    private int courseHours;//课时量
    private Teacher courseTeacher;//授课老师
    
    //getter、setter省略
    @Override
    public String toString() {
        return "[课程名字:" + courseName + ", 课时量:" + courseHours + ", 老师信息:" + courseTeacher + "]";
    }
    
}
  • 编写配置文件applicationContext.xml如下:



    
    
        
        
    

    
    
        
        
        
    


以上的装配是手工装配的。

自动装配就是我不写这行代码,想办法让程序自动在IOC容器中去寻找teacher这个bean,答案是可以的。删掉这行代码,然后在course的标签中加一句 autowire="byName" 。

运行程序,发现老师信息是null,说明没有程序没有找到对应的teacher的bean。既然是按照名字自动装配,所以得保证属性的名字和bean的名字一致




    
    
        
        
    

    
    
        
        
    


  • 测试
@Test
public void testAutowire() {
  //获取上下文对象:Context
  ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  //获取bean对象
  Course course = (Course)context.getBean("course");
  System.out.println(course);

}

按照名字装配,必须保证属性名和被装配bean的id名一致。

byName装配

② byType

byType自然就是按照类型装配。

在被装配的bean中加入autowire="byType"




   
   
       
       
   

   
   
       
       
   


如果IOC容器中有一个bean的类型恰好是该类的引用类型,则可以按照类型自动装配。

byType装配

弊端:

如果IOC容器中有两个类型一样的bean,则程序不知道该装配哪一个,就会装配失败。




    
    
        
        
    

    
    
        
        
    
    
    
    
        
        
    


会抛这样一个异常:

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.lee.entity.Teacher' available: expected single matching bean but found 2: com.lee.entity.Teacher#0,com.lee.entity.Teacher#1

③ constructor

根据构造器去自动装配。所以实体类要有构造方法。
配置文件: autowire="constructor"




  
      
      
  
  
  
      
      
  

注意:同样IOC容器中不能有两个teacher类型的bean,否则装配失败,并且抛异常。

全局自动装配

全局自动装配使用:default-autowire

可以给每一个类进行自动装配,也能给全局bean都设置自动装配。




    
    
        
        
    
    
    
    
        
        
    


注意:

局部bean的自动装配可以覆盖全局bean自动装配。

5.2 基于注解的自动装配

基于注解的自动装配有两种,分别是 通过类型自动装配 和 通过名字自动装配。

基于注解的装配在开发中是最常用的,一般三层架构的装配都是使用注解自动装配。

① @Autowired

@Autowired默认根据类型进行装配。和 byType是一样的。

用配置文件方式模拟一个开发中的三层架构:

Dao层:

package com.lee.spring.dao;

public interface StudentDao {
    void addStudent();
}
package com.lee.spring.dao.impl;

public class StudentDaoImpl implements StudentDao {

    @Override
    public void addStudent() {
        System.out.println("添加学生...");
    }
}

Service层:

package com.lee.spring.service;

public interface StudentService {
    void addStudent();
}
package com.lee.spring.service.impl;

public class StudentServiceImpl implements StudentService{
  
  StudentDao studentDao;
    public void setStudentDao(StudentDao studentDao) {
        this.studentDao = studentDao;
    }
  
    @Override
    public void addStudent() {
        studentDao.addStudent();
    }
}

Controller层:

package com.lee.spring.controller;

public class AddStudentController {

     public void setStudentService(StudentService studentService) {
            this.studentService = studentService;
          }

          StudentService studentService;

          public void addStudent() {
            studentService.addStudent();
          }
}

配置文件:



    
        
    
        
     
    
    
    
        
    
    
    
    


测试:

@Test
public void test08() {
  //获取上下文对象:Context
  ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  //获取bean对象
  AddStudentController addStudentController = (AddStudentController)context.getBean("addStudentController");
  addStudentController.addStudent();
}

用注解方式实现:

Dao层:

package com.lee.spring.dao;

public interface StudentDao {
    void addStudent();
}
package com.lee.spring.dao.impl;

@Repository
public class StudentDaoImpl implements StudentDao  {
    @Override
    public void addStudent() {
        System.out.println("添加学生...");
    }
}

Service层:

package com.lee.spring.service;

public interface StudentService {
    void addStudent();
}
package com.lee.spring.service.impl;

@Service
public class StudentServiceImpl implements StudentService{
    
    @Autowired
    StudentDao studentDao;
  
    @Override
    public void addStudent() {
        studentDao.addStudent();
    }
}

Controller层:

package com.lee.spring.controller;

@Controller
public class AddStudentController {

    @Autowired
    StudentService studentService;

    public void addStudent() {
        studentService.addStudent();
    }
}

配置文件:



    
    


如果IOC容器没有StudentDao这个bean,StudentService层源码是这样:

package com.lee.spring.service.impl;

@Service
public class StudentServiceImpl implements StudentService{
    
    @Autowired
    StudentDao studentDao;
  
    @Override
    public void addStudent() {
        studentDao.addStudent();
    }
}

当容器获取stuService组件时,程序会报错,因为stuService依赖StudentDao,但是IOC容器还没有StudentDao,所以报错。怎么让程序不依赖StudentDao:在@AutoWired注解后加:required = false

package com.lee.spring.service.impl;

@Service
public class StudentServiceImpl implements StudentService{
    
    @Autowired(required = false)
    StudentDao studentDao;
  
    @Override
    public void addStudent() {
        studentDao.addStudent();
    }
}

② @Qualifier

当一个接口有多个实现类时,需要用@Autowired + @Qualifier组合来区分到底调用哪个类的实现。

@Controller("addStudentController")
public class AddStudentController {

    @Autowired
    @Qualifier("stuService")
    StudentService studentService;
    public void addStudent() {
        studentService.addStudent();
    }
}

其中@Qualifier("stuService")括号中的stuService就是我们之前定义@Service注解的名称。

注意:@Qualifier必须和@Autowired一起使用才有效。

③ @Resource

@Resource注解是通过byName注入的。

@Controller("addStudentController")
public class AddStudentController {

    @Resource(name = "stuService")
    StudentService studentService;
  
    public void addStudent() {
        studentService.addStudent();
    }
}

④ @Resource和@Autowired区别

① @Autowired为Spring提供的注解。@Resource并不是Spring的注解,它的包是javax.annotation.Resource

② @Autowired按照byType注入,@Resource默认按照ByName自动注入。

你可能感兴趣的:(第二章 bean的装配)