更简单的读取和存储对象

在上一篇文章中我们已经介绍在XML文件注册Bean的具体步骤,这一篇文章将会介绍使用更加简洁的方式(使用注解)来存储和读取Bean.这也是最常用的方法.

1. 创建并配置好Spring项目

和上一篇的步骤相同,下面就相当于复习如何创建Spring项目吧

  1. 创建一个 Maven 项目
  2. 为 Spring 项目添加依赖包(spring-beans/spring-context)
  3. 创建一个启动类
  4. 为 Spring 项目 创建配置文件(spring-config.xml)
<?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">
</beans>

2. 存储 Bean

之前存储 Bean 时需要根据我们的需要,添加对应的 Bean 注册内容 才能完成注入, 而现在我们只需要一行内容就可以替代之前的写多行注册内容.那么为了完成这个简单的注入,实现要配置好扫描路径.

2.1 配置扫描路径

扫描路径: 决定 Spring 搜索的文件范围, 默认是从Java文件的根路径开始搜索

根路径: 在IDEA中可以修改文件的属性, 修改为不同类型文件的根目录, 对应的Java文件的根目录的颜色如图是浅蓝色的.
image.png

比如你在根目录下把需要注入到Spring中的对象创建到 "com.bean.controller"中,那么对应的扫描路径就应该为

<content:component-scan base-package="com.bean.controller"></content:component-scan>

注:

  • 只有在当前扫描包下的对象并且添加了注解的类才会被存储到Spring中
  • 同时使用注解注入和bean内容注入是不会发生冲突的

2.2 使用注解注入 Bean

通过两种注解方式使对象注入到Spring中

  1. 类注解: @Controller、@Service、@Repository、@Component、@Configuration
  2. 方法注解: @Bean

2.2.1 不同类注解的作用

相信大家想知道为什么有这么多类注解? 虽然最终完成的工作都是把对象注入,但是它们之间的关系就类似于不同地区的车牌号一样,不同地区的车牌号是不同的,这样就能够直观的辨识一辆车的归属地, 这里的类注解就是为了让程序员直观的了解当前类的用途,例如:

  • @Controller:业务逻辑层(验证前端传递的数据的合法性)
  • @Servie:服务层(服务调用的编排和汇总)
  • @Repository:持久层 (直接操控数据库)
  • @Configuration:配置层 (关于项目的使所有配置)
  • @Component: 组件(通用化的工具类)

程序之间的层次关系如下:

2.2.2 类注解之间的联系


通过查看五大注解的源码,可以发现它们都是@Component的子类.作用都是把 Bean 存储到Spring中

2.2.3 五大类注解使用示例

为了区分不同注解之间的关系,创建不同的包来验证其效果
image.png

那么当前项目的扫描路径就是从"com.bean"下的所有包

<content:component-scan base-package="com.bean"></content:component-scan>
  1. @Component 获取对象
package com.bean.component;

import org.springframework.stereotype.Component;

@Component
public class UserComponent {
    public void doComponent() {
        System.out.println("Do Component");
    }
}
  1. @Configuration 获取对象
package com.bean.config;

import org.springframework.context.annotation.Configuration;

@Configuration
public class UserConfiguration {
    public void doConfiguration() {
        System.out.println("Do Configuration");
    }
}
  1. @Controller 获取对象
package com.bean.controller;

import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    public void doController() {
        System.out.println("Do Controller");
    }
}
  1. @Repository 获取对象
package com.bean.repository;

import org.springframework.stereotype.Repository;

@Repository
public class UserRepository {
    public void doRepository() {
        System.out.println("Do Repository");
    }
}
  1. @Service 获取对象
package com.bean.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {
    public void doService() {
        System.out.println("Do Service");
    }
}
  1. 启动类
import com.bean.component.UserComponent;
import com.bean.config.UserConfiguration;
import com.bean.controller.UserController;
import com.bean.repository.UserRepository;
import com.bean.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    public static void main(String[] args) {
        // 1. 获取上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        // 2. 使用 getBean 方法获取对象
        UserComponent userComponent = context.getBean("userComponent", UserComponent.class);
        UserConfiguration userConfiguration = context.getBean("userConfiguration", UserConfiguration.class);
        UserController userController = context.getBean("userController", UserController.class);
        UserRepository userRepository = context.getBean("userRepository", UserRepository.class);
        UserService userService = context.getBean("userService", UserService.class);
        // 3. 使用 Bean
        userComponent.doComponent();
        userConfiguration.doConfiguration();
        userController.doController();
        userRepository.doRepository();
        userService.doService();
    }
}

成功运行截图:
image.png

2.2.4 Bean 的命名规则

在Java中通常对类名的命名规则是大驼峰命名(例如: UserComponent, UserConfiguration等), 而在读取时使用的是首字母小写获取 Bean (例如:userComponent, userConfiguration等),但是这是普遍规则吗?
假如现在的类名的首字母和第二个字母都是大写时是否满足呢?

package com.bean.component;

import org.springframework.stereotype.Component;

@Component
public class UComponent {
    public void doComponent() {
        System.out.println("Do Component");
    }
}

image.png

使用首字母小写的方法结果是错误的,那么就需要知道 Bean 的命名规则

  1. 在 Idea中搜索 BeanName

image.png

  1. 点击进入查看, 找默认生成 Bean 名称方法

image.png

  1. 最后发现使用的名称生成器是JDK中 Introspector类中的方法

image.png

image.png

通过查看源代码得出结论:

  • 当类名的前两个字母都是大写的情况下, 那么 Bean 的名称默认为原类名
  • 其他情况都是首字母小写为 Bean 名称

知道问题的解决方案后,修改后就能够成功读取了
image.png

2.2.5 方法注解 @Bean

前面学习类注解是使用在方法上的,那么方法注解是不是使用在方法上呢?

package com.bean.model;

import org.springframework.context.annotation.Bean;

public class User {
    public String name;
    public int age;

    @Bean
    public User user1() {
        User user = new User();
        user.setAge(10);
        user.setName("李四");
        return user;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

我们发现尝试获取当前 Bean 时发生了异常
image.png
原来方法注解是需要搭配类注解共同完成注入的
image.png

image.png

同样的通过@Bean 注解 Bean 名称生成规则和前面一样,不同的是 @Bean 可以有多个名称,我们通过查看源码就可以发现,

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
    @AliasFor("name")
    String[] value() default {};

    @AliasFor("value")
    String[] name() default {};

    /** @deprecated */
    @Deprecated
    Autowire autowire() default Autowire.NO;

    boolean autowireCandidate() default true;

    String initMethod() default "";

    String destroyMethod() default "(inferred)";
}

其 name是 一个String[]数组,也就意味着可以有0个或者多个名称.

    @Bean(name = {"u1", "u2"})
    public User user() {
        User user = new User();
        user.setAge(10);
        user.setName("李四");
        return user;
    }

并且name={}可以省略

    @Bean({"u1", "u2"})
    public User user() {
        User user = new User();
        user.setAge(10);
        user.setName("李四");
        return user;
    }

注: 对 Bean 重命名后就不能使用方法名获取 对象 了

2.2.6 注解方式的对比

相同点:

  • 都是把通过注解的方式把对象存储到Spring容器中

不同点:

  • @Component :通用的注解,可标注任意类为 Spring 的组件。如果一个 Bean 不知道属于哪个层,可以使用 @Component 注解标注
  • @Configuration :声明该类为一个配置类,可以在此类中声明一个或多个 @Bean 方法。
  • @Controller :对应 Spring MVC 控制层,主要用来接受用户请求并调用 Service 层返回数据给前端页面
  • @Service :对应服务层,主要设计一些复杂的逻辑,需要用到 Dao 层
  • @Repository :对应持久层即 Dao 层,主要用于数据库相关操作。
  • 类注解是通过路径扫描来自动侦测以及自动装配到 Spring 容器中,@Bean 注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean 告诉了 Spring 这是某个类的实例并且产生这个 Bean 对象的方法Spring 只会调用一次,随后Spring 就会把该对象存储在自己的IoC容器中.
  • @Component , @Repository , @ Controller , @Service 这些注解只局限于自己编写的类,而@Bean注解能把第三方库中的类实例加入IOC容器中并交给Spring管理

3. 获取 Bean

获取 Bean 也叫做 对象装配, 是把对象取出来放入某个类中, 有时候也叫做 对象注入, 其实现方式有下面三种

  1. 属性注入 (Field Injection)
  2. 构造方法注入 (Constructor Injection)
  3. Setter注入 (Setter Injection)

下面采用的是将 Service 类注入到Controller类中

3.1 属性注入

package com.bean.controller;

import com.bean.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {

    // 1. 属性注入
    @Autowired
    private UserService userService;
    
    public void doController() {
        System.out.println("Do Controller");
        userService.doService();
    }
}

运行成功截图:
image.png

3.1.1 优点

实现简单,只需要把需要的属性上加入@Autowired 注解即可

3.1.2 缺点

  1. 功能性问题

使用属性注入无法注入一个不可变对象(被final修饰对象)
image.png

原因: 在 Java中的 被 final 修饰的变量 要么直接初始化,要么使用构造方法初始化,当使用属性注入时都没有满足上面任一条件当然会出错

  1. 通用性问题

使用属性注入只适用于IoC框架, 如果在其他非IoC容器中使用就无法使用了

  1. 设计原则问题

因为属性注入的简易,所有可能会导致开发者在一个类中注入多个类,但是对于这些类是否需要呢? 所以有很大可能会违反单一设计原则.

3.2 Setter 注入

@Controller
public class UserController {
    private UserService userService;
    // 2. Setter 注入
    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void doController() {
        System.out.println("Do Controller");
        userService.doService();
    }
}

3.2.1 优点

因为一个Setter方法只会针对一个对象,所以它的优点很明显: 符合单一设计原则

3.2.2 缺点

  1. 功能性问题

使用属性注入无法注入一个不可变对象(被final修饰对象)
image.png

  1. 注入对象可以被修改

对于当前类setXXX()方法是可见的,所以你可以在某处调用setXXX()方法从而改变注入对象

3.3 构造方法注入

package com.bean.controller;

import com.bean.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    private UserService userService;

    // 3. 构造方法注入
    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    public void doController() {
        System.out.println("Do Controller");
        userService.doService();
    }
}

注:

  • 如果当前类只有一个构造方法那么 @Autowired 可以省略
  • 如果存在多个属性需要注入,那么只有被@Autowired 注解的构造方法可以成功注入,并且有且仅有一个构造方法可以被@Autowired注解

image.png

image.png

  • 可以在一个构造方法中注入多个对象

image.png

  • 对于注入的类,要确保不为空,否则会报错

image.png

对于List集合类含有默认的构造器不为空,但是Integer类的默认构造器需要参数,所以报错
在Spring 4.2 之前官方推荐使用注入方法是Setter注入,因为Setter更符合单一设计原则; 但在Spring 4.2 后官方推荐使用构造方法注入的方式, 因为构造方法有以下优点:

  1. 可以注入不可变对象

原因是通过构造方法注入就符合Java设计规范

  1. 注入对象不会被改变
    1. 因为构造方法只会执行一次
  2. 完全初始化
    1. 因为构造方法是在对象创建前优先调用的,所以注入对象在使用前一定被完全初始化
  3. 通用性更好
    1. 因为构造方法是Java最底层的框架, 所以在不同的框架下都能使用

3.4 @Resource 另一种注入关键字

@Resource 有两种注入方式:

  1. 属性注入
package com.bean.controller;

import com.bean.service.UserService;
import org.springframework.stereotype.Controller;

import javax.annotation.Resource;

@Controller
public class UserController2 {
    // 1. 属性注入
    @Resource
    private UserService userService;
    
    public void doController() {
        System.out.println("Do Controller 2.0");
        userService.doService();
    }
}

image.png

  1. Setter()注入
package com.bean.controller;

import com.bean.service.UserService;
import org.springframework.stereotype.Controller;

import javax.annotation.Resource;

@Controller
public class UserController2 {
    //2. Setter()注入
    private UserService userService;
    @Resource
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void doController() {
        System.out.println("Do Controller 2.0");
        userService.doService();
    }
}

image.png

3.5 @Resource 和 @Autowired的异同点

  1. 相同点

@Autowired 和 @Resource都可以用来装配bean,都可以用于属性注入或setter()注入

  1. 不同点
  1. 来源不同: @Autowired 是Spring 提供的, @Resource是JDK的注解
  2. @Autowire 默认按类型装配,默认情况下必须要求依赖对象必须存在,如果要允许 null 值,可以设置它的 required 属性为 false
  3. @Resource 默认按名称装配,当找不到与名称匹配的 bean 时才按照类型进行装配。名称可以通过 name 属性指定,如果没有指定 name 属性,当注解写在字段上时,默认取字段名,当注解写在 setter 方法上时,默认取属性名进行装配.
  4. 装配顺序不同.
Autowired的装配顺序只根据type进行注入,不会去匹配name
Resource 的装配顺序
1. 如果同时指定 name 和 type,则从容器中查找唯一匹配的 bean 装配,找不到则抛出异常;
2. 如果指定 name 属性,则从容器中查找名称匹配的 bean 装配,找不到则抛出异常;
3. 如果指定 type 属性,则从容器中查找类型唯一匹配的 bean 装配,找不到或者找到多个抛出异常;
4. 如果不指定,则自动按照 byName 方式装配,如果没有匹配,则回退一个原始类型进行匹配,如果匹配则自动装配

注:

可以使用@Qualifier 指定名称,下面的两种写法效果是相同的

@Autowired(required = false) @Qualifier("userService")
private UserService userService;

@Resource(name = "userService")
private UserService userService;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Zzt.opkk

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

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

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

打赏作者

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

抵扣说明:

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

余额充值