一、前言

通过之前的Spring初识这一节,从初始者的角度实现Spring的存与取,但是所需要的步骤是比较多的,比较臃肿,冗余

指路——>初识 Spring 容器

编辑

因此需要学习更加简单的Bean对象的存与取。在Spring中想要更加简单的存储和读取对象的核心就是 使用注解 。在之后的学习中,注解将会给写代码带来极大的便利。

二、存储Bean对象

2.1 前置工作

想要将 Bean对象更加简单的存储到 Spring 中, 前置工作 就是在 Spring 的配置文件(就是名字是自己起的 xml 配置文件)中设置组件 Component 的 根路径

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:content="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 配置一下:bean注解扫描的根路径(方面后面更简单存储对象到spring容器)-->
    <content:component-scan base-package="com.bit.beans"></content:component-scan>
</beans>

代码如上(建议使用时 直接复制粘贴 )

在之前的较为复杂的存储 Bean 对象时,就是在该配置文件中进行 Bean 对象的声明,现在不需要写的那么复杂,只需要配置上需要进行扫描的根路径即可, base-package 中的内容就是回头我们 需要扫描的路径 (每个人写的都可以不一样)。

如果想要将某类对象,或者方法的返回值存储到 Spring容器中,只需要添加上相应的注解,并且将其放在扫描路径下,Spring就可以自动的将对象进行存储。

至于为什么需要指定扫描路径,那是因为一个规模较大的项目中将会有太多太多的包、类型、方法,如果统统进行扫描,效率将会非常的低下

编辑

想要将对象存储到 Spring 中,有两种注解类型可以进行实现

类注解
方法注解

2.2 类注解

2.2.1 @Controller

@Controller ,发音 [kənˈtrəʊlə®], 控制器存储

使用方法:

步骤一:需要被存储的对象

@Controller
public class ArtController {
 
    public void func() {
 
        System.out.println("Hello Controller~");
    }
}

需要注意的事:该类 必须要放在扫描路径底下 ;必须要 加上注解 @Controller

步骤二:用之前的方式读取到上面的 ArtController 对象验证出存储的成功

public class Start {
 
    public static void main(String[] args) {
 
        //1.获取 Spring 上下文
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        //2.获取 Bean 对象
        ArtController artController = context.getBean("artController", ArtController.class);
        //3.使用 Bean 对象
        artController.func();;
    }
}

需要注意的事:在这里的 beanName 是类名 ArtController 的 第一个字母小写 得来的(一般情况下)

结果:

编辑

2.2.2 @Service

@Service ,发音 [ˈsɜːvɪs], 服务存储

使用方法:

步骤一:需要被存储的对象

@Service
public class ArtService {
 
    public void func2() {
 
        System.out.println("Hello Service~~");
    }
}

需要注意的事:该类必须要 放在扫描路径底下 ;必须要 加上注解 @Service

步骤二:用之前的方式读取到上面的 ArtService 对象验证出存储的成功

ArtService artService = context.getBean("artService", ArtService.class);
artService.func2();

需要注意的事:在这里的 beanName 是类名 ArtService 的 第一个字母小写 得来的(一般情况下)

结果:

编辑

2.2.3 @Repository

@Repository ,发音 [rɪˈpɒzətri], 仓库存储

使用方法:

步骤一:需要被存储的对象

@Repository
public class ArtRepository {
 
    public void func3() {
 
        System.out.println("hello Repository!");
    }
}

需要注意的事:该类必须要 放在扫描路径底下 ;必须要 加上注解 @Repository

步骤二:用之前的方式读取到上面的 ArtRepository 对象验证出存储的成功

ArtRepository artRepository = context.getBean("artRepository", ArtRepository.class);
artRepository.func3();

需要注意的事:在这里的 beanName 是类名 ArtRepository的 第一个字母小写 得来的(一般情况下)

结果:

编辑

2.2.4 @Component

@Component ,发音 [kəmˈpəʊnənt], 组件存储

使用方法:

步骤一:需要被存储的对象

@Component
public class ArtComponent {
 
    public void func4() {
 
        System.out.println("hello Component!!");
    }
}

需要注意的事:该类必须要 放在扫描路径底下 ;必须要 加上注解 @Component

步骤二:用之前的方式读取到上面的 ArtComponent 对象验证出存储的成功

ArtComponent artComponent = context.getBean("artComponent", ArtComponent.class);
artComponent.func4();

需要注意的事:在这里的 beanName 是类名 ArtComponent 的 第一个字母小写 得来的(一般情况下)

结果:

编辑

2.2.5 @Configuration

@Configuration ,发音 [kənˌfɪɡəˈreɪʃn], 配置存储

使用方法:

步骤一:需要被存储的对象

@Configuration
public class ArtConfiguration {
 
    public void func5() {
 
        System.out.println("Hello Configuration!~");
    }
}

需要注意的事:该类必须要 放在扫描路径底下 ;必须要 加上注解 @Configuration

步骤二:用之前的方式读取到上面的 ArtConfiguration 对象验证出存储的成功

ArtConfiguration artConfiguration = context.getBean("artConfiguration",                     ArtConfiguration.class);
artConfiguration.func5();

需要注意的事:在这里的 beanName 是类名 ArtConfiguration 的 第一个字母小写 得来的(一般情况下)

结果:

编辑

2.2.6 五大注解的区别和关系

写到这,可能就会有疑问了,上面五种注解有什么区别,用法、作用不是一模一样嘛,直接都使用其中的一个不就行了,为啥要五种

事实上,虽然用法一样,但是上面的 五种类注解指向了五种不同的类的用途 ,让别人一看见这个注解就知道这个类实现的都是些什么功能,就相当于车牌号,我们可以通过看车牌就可以知道车辆的归属地(京XXX——北京来的,浙XXX——浙江来的)

区别

        
  • @Controller : 业务逻辑层 (用于 验证前端参数是否合法 ,如果不合法就直接返回,不进行接下来的操作)
  •     
  • @Service : 服务层 (用于 数据的组装 和 调用数据库接口的管理 ,前端传来的数据,也许需要被组装成完整的对象,通过该层来指定要调用那些数据呢,添加一个新用户,也许需要在用户表中,日志表中添加数据…)
  •     
  • @Repository : 数据持久层 (真正的 进行数据的查询和管理 操作)
  •     
  • @Configuration : 配置层 (设置当前项目中的所有 配置信息 )
  •     
  • @Component : 实体类 (可用于做 数据的载体 ,用户类想要添加到 Spring 中,它是用户数据的载体,就相当于一个组件,如果一个需要存储到 Spring 中的类不属于上面的四种类型的话,就可以当成组件)

编辑

关系

通过 Ctrl + 左键 点击对应的注释,发现 Controller、Repository、Service和 Configuration注解 ,都实现了 Component 注解的功能,它们 都算是 Component 的 “子类”

编辑

2.2.7 Bean 的命名

通过上面的例子,我们发现 beanName 是类的第一个字母小写,但如果类的名字第一个和第二个字母都是大写,再用相同的方法,就会报错,说没有这样的 beanName

查看原码:

public static String decapitalize(String name) {
 
    if (name == null || name.length() == 0) {
 
        return name;
    }
    //长度大于1 且 第一个字母和第二个字母是大写
    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);
}

通过查看原码,发现如果 第一个字母和第二个字母都为大写 ,beanName 就是 原来的类名 , 否则 就将 beanName 就将 首字母小写

2.3 方法注解

2.3.1 @Bean

方法注解是放在某个方法上,将 方法返回的值 存储到 Spring 容器中。

并且需要配合类注解进行使用,因为扫描路径下的类中也许会有很多很多的方法,如果只扫描有类注解的类中的方***提高效率

使用方法:

步骤一:需要被存储的方法返回值对象

@Component
public class ArtComponent {
 
    @Bean
    public Article func() {
 
        Article article = new Article();
        article.setId(1);
        article.setName("人与自然");
        article.setContent("内容介绍");
        return article;
    }
}

步骤二:获取到存储的Bean对象

public class Start {
 
    public static void main(String[] args) {
 
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        Article article = context.getBean("addArticle",Article.class);
        System.out.println(article);
    }
}

结果:

编辑

2.3.2 Bean 的重命名

在此之前使用方法注解的 beanName 是方法名,但是有些方法名带有一定的功能属性,比如上面的 addArticle ,这样的名字显得有点不合适,因此可以给 Bean 对象进行 重命名操作

@Bean(name = "article") //重命名了一个名字,name = 可以省略
@Bean(name = {
 "article","article2"}) //重命名多个名字,name = 可以省略

注意

花括号
同一个

三、获取Bean对象

获取对象,就是将对象取出来放到某个类中,也叫 对象注入 ,就是在前一节讲到的 DI

在此之前,获取对象的方式是先获取 Spring 上下文,再获取 Bean 对象,最后再使用 Bean 对象。

如果依赖关系非常多的时候,A依赖于B,B依赖于C,C依赖于D,用上面的方法进行获取对象将会变得非常的麻烦,需要用更加简洁的方式进行对象的注入

对象的方法

属性注入
构造方法注入
Setter 注入

下面将模拟将 Service 类注入到 Controller 类中,三种方式的使用方法

3.1 属性注入

属性注入是三种注入方式中最简单的注入方式,使用注解 @Autowired 实现

步骤一:创建 Service 类

@Service
public class ArtService {
 
    public Article func(int id) {
 
        //伪代码 通过数据库查询用户信息
        //。。。
        Article article = new Article();
        article.setId(id);
        article.setName("三国演义");
        article.setContent("三国内容简介");
        return article;
    }
}

步骤二:创建 Controller 类,依赖于 Service 类

@Controller
public class ArtController {
 
    //属性注入
    @Autowired
    private ArtService artService;

    public Article findArticle(int id) {
 
        return artService.func(id);
    }
 }

步骤三:获取 Controller 类

public class Start {
 
    public static void main(String[] args) {
 
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        ArtController artController = context.getBean("artController",ArtController.class);
        System.out.println(artController.findArticle(1));
    }
}

结果:

编辑

补充

在注入 Service 类时,会先进行类型的查询,如果在 Spring 中查询到同类型的对象就直接返回,否则使用类名称进行查询,都没有结果,说明注入失败

3.2 构造方法注入

需要在 构造方法上加 @Autowired

@Controller
public class ArtController {
 
    //构造方法注入
    private ArtService artService;
    @Autowired
    public ArtController(ArtService artService) {
 
        this.artService = artService;
    }

    public Article findArticle(int id) {
 
        return artService.func(id);
    }
 }

补充

当类中只有一个构造方法时,@Autowired 可以省略,在有多个构造函数的时候,不可以省略

3.3 Setter 注入

设置 set 方法的时候需要加上 @Autowired 注解

@Controller
public class ArtController {
 
    //Setter 注入
    private ArtService artService;
    @Autowired
    public void setArtService(ArtService artService) {
 
        this.artService = artService;
    }
    
    public Article findArticle(int id) {
 
        return artService.func(id);
    }
 }

补充

@Autowired 注释不可以被省略

3.4 三种注入方式的优缺点分析

        
  1. 属性注入    
              
    • 优点:写法是最简单的,使用最方便的;
    •         
    • 缺点:该写法是适用于 IOC 容器,非 IOC 容器无法识别,并且在使用的时候会出现空指针异常,可移植性较差;
    •     
        
  2.     
  3. 构造方法注入(官方最为推荐的注入方案)    
              
    • 优点:通用性最佳,代码的可移植性最高,在使用前一定能保证注入的类不为空;
    •         
    • 缺点:看起来比较臃肿,毕竟构造方法的参数可以传很多个,但是当参数过多时就需要考虑自己写的类是否符合程序的单一职责的设计模式了;
    •     
        
  4.     
  5. Setter 注入    
              
    • 是 Spring 早期版本推荐的注入方式,通用性比较好(没有构造方法好),只能传一个参数,因此比构造方法简洁一些。现在所有的 Spring 版本已经推荐使用构造方法注入来进行类的注入;
    •     
        

3.5 @Resource 注解

在注入时,除了 @Autowired 关键字外,还可以使用 @Resource 注解进行注入,拿属性注入举例

@Resource
private ArtService artService;

3.5.1 @Resource 和@Autowired 的区别

        
  1. 出身    
              
    • @Autowired 来自于 Spring 框架
    •         
    • @Resource 来自于 JDK
    •     
        
  2.     
  3. 参数设置    
              
    • @Autowired 可以设置的参数只有一个
    •         
    • @Resource 有很多参数可设置
    •     
        
  4.     
  5. 修饰对象    
              
    • @Autowired 可以用于属性注入、构造方法注入、Setter 注入
    •         
    • @Resource 不能用于构造方法注入,只能用于属性注入和Setter 注入
    •     
        

3.5.2 对象被多个 Spring注入多个

@Component
public class ArtComponent {
 
    @Bean(name = "article1")
    public Article func1() {
 
        Article article = new Article();
        article.setId(1);
        article.setName("水浒传");
        article.setContent("水浒传内容");
        return article;
    }

    @Bean(name = "article2")
    public Article func2() {
 
        Article article = new Article();
        article.setId(1);
        article.setName("红楼梦");
        article.setContent("红楼梦内容");
        return article;
    }
}

可以看见有两个 Article 类被存储在了 Spring 中,如果直接使用 @Autowired 或者 @Resource 就会报错,不晓得究竟想要注入哪个对象

public class Start {
 
    public static void main(String[] args) {
 
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        ArtController artController = context.getBean("artController",ArtController.class);
        artController.findArticle();
    }
}

获取到 ArtController 类,对其进行使用

方法一:使用@Resource

@Resource 注解是可以设置很多参数,可以进行 beanName 的设置,name = 不可以省略

public class ArtController {
 
    @Resource(name = "article1")
    private Article article;

    public void findArticle() {
 
        System.out.println(article);
    }
 }

方法二:@Autowired + @Qualifier

如果一定要使用 @Autowired 注解,就需要配合 @Qualifier 一起使用,value = 可以省略

@Controller
public class ArtController {
 
    @Autowired
    @Qualifier(value = "article2")
    private Article article;

    public void findArticle() {
 
        System.out.println(article);
    }
 }