Spring 创建和使用

Spring 创建和使用

  • 创建 Spring 项目
  • Spring 原始的使用方式
    • 存储 Bean 到 Spring 中
      • 1 创建一个 Bean 对象
      • 2 将 Bean 注册到容器中
    • 从 Spring 中获取并使用 Bean 对象
      • 1 得到 Spring 上下文对象
      • 2 通过 Spring 上下文对象,获取并使用指定的 Bean 对象
  • 更简单的使用 Spring
    • 更简单的存储 Bean
      • 1 配置扫描包路径
      • 2 添加注解存储 Bean
        • 2.1 类注解
        • 2.2 Bean 默认命名规则
        • 2.3 方法注解
    • 更简单的获取 Bean
      • 1 属性注入
      • 2 构造方法注入
      • 3 Setter注入
      • 4 依赖注入与依赖查找的区别
      • 5 @Autowired 和 @Resource 的区别
      • 6 三种注入方式的优缺点

Spring 是一个包含众多工具方法的 IoC 容器。
Spring 作为一个 IoC 容器最核心的功能是:存入对象,取出对象


创建 Spring 项目

  1. 创建一个 Maven 项目

  2. 添加依赖,spring-context、spring-beans

<dependencies>
    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-contextartifactId>
        <version>5.2.3.RELEASEversion>
    dependency>

    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-beansartifactId>
        <version>5.2.3.RELEASEversion>
    dependency>
dependencies>
  1. 添加启动类
public class App {
    public static void main(String[] args) {
    }
}

Spring 原始的使用方式

存储 Bean 到 Spring 中

Bean: 对象,严格来说,Bean 指的是被多次使用的对象。

1 创建一个 Bean 对象

public class User {
    public void sayHi() {
        System.out.println("你好!");
    }
}

2 将 Bean 注册到容器中

在 resources 里面创建一个 Spring 的配置文件。

Spring 创建和使用_第1张图片


<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 id="user" class="com.demo.User">bean>
beans>
  • 通过 xml 配置文件将 com.demo.User 这个类,注册到容器中,起名为 user。
  • id 不能重复,否则获取 Spring 上下文对象时会报错。

从 Spring 中获取并使用 Bean 对象

1 得到 Spring 上下文对象

ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
  • spring-config.xml 就是之前创建的配置文件。

获取 Spring 上下文对象的两种方法:

  1. ApplicationContext 作为 Spring 上下文。
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
  1. BeanFactory 作为 Spring 上下文,这个是旧的用法,现在不建议使用了。
BeanFactory context = new XmlBeanFactory(new ClassPathResource("spring-config.xml"));

相同点:

都是容器管理对象,都可以获取 Spring 上下文对象。这两种获取 Spring 上下文对象的效果是一样的。

ApplicationContext 和 BeanFactory 的区别:

  1. ApplicationContext 属于 BeanFactory 的 子类。Application 不仅继承了 BeanFactory 的功能,它还拥有更多的功能,例如对国际化⽀持、资源访问⽀持、以及事件传播等⽅⾯的⽀持。

  2. BeanFactory 是懒加载,需要用到 Bean 对象时,才去加载并初始化需要的那个 Bean 对象。

    ApplicationContext 是在创建 Spring 上下文对象时,一次性加载并初始化所有的 Bean 对象。

    ApplicationContext 创建 Spring 上下文时,会比较慢,但是后续获取 Bean 时,会比较快。

Spring 创建和使用_第2张图片
Spring 创建和使用_第3张图片


2 通过 Spring 上下文对象,获取并使用指定的 Bean 对象

public class App {
    public static void main(String[] args) {
        // 获取 Spring 上下文
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
		// 从 Spring 中,获取 Bean 对象
        User user = (User) context.getBean("user");		// 依赖查找
		// 使用 Bean 对象
        user.sayHi();
    }
}
  • 通过 Spring 上下文对象,调用 getBean 获取 Bean 对象。
  • getBean 通过 Bean 名称得到的是 Object 对象,需要强转。
  • 这是 依赖查找 的方式实现的 IoC

getBean 的用法:

  1. id 获取 Bean,返回的是 Object,需要强转
User user = (User) context.getBean("user");
  1. 类型获取 Bean,不需要强转
User user = context.getBean(User.class);

但是,如果 Spring 中注入了多个类型相同的 Bean 时,使用 类型 获取 Bean,会报错。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. id + 类型获取 Bean,最常用
User user = context.getBean("user", User.class);

更简单的使用 Spring

更简单的存储 Bean

1 配置扫描包路径

resources 包下新建配置文件 spring-config.xml,在 spring-config.xml 中配置添加配置,需要配置存储对象的扫描包路径,配置的包路径下的类,并且添加了注解,才能被识别并存储到 Spring 中。


<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">
    <content:component-scan base-package="com.demo">content:component-scan>

beans>

2 添加注解存储 Bean

有两种注解类型可以实现:

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

2.1 类注解

  1. 不指定 Bean 名称
@Controller // 使用类注解将对象存储到 Spring 中
public class User {
    public void sayHi() {
        System.out.println("User: 你好!");
    }
}
public class App {
    public static void main(String[] args) {
        // 1. 得到 Spring 上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        // 2. 通过 Spring 上下文对象, 获取 Bean 对象
        User user = context.getBean("user", User.class);
        // 3. 使用 Bean
        user.sayHi();
    }
}
  • 添加类注解时,不指定 Bean 的名称,那么按照 Bean 默认的命名规则来。
  1. 指定 Bean 名称
@Controller(value = "userinfo")
public class User {
    public void sayHi() {
        System.out.println("User: 你好!");
    }
}
public class App {
    public static void main(String[] args) {
        // 1. 得到 Spring 上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        // 2. 通过 Spring 上下文对象, 获取 Bean 对象
        User user = context.getBean("userinfo", User.class);
        // 3. 使用 Bean
        user.sayHi();
    }
}
  • 添加类注解时,指定了 Bean 的名称,那么只能用这个名称获取这个 Bean。

  • 其他几个类注解的用法一样,达到的效果也一样。

为什么要有这么的多类注解?

这么多类注解的作用是标识,让我们看到一个类注解,就能知道这个类的用途:

  • @Controller:控制层,校验参数的合法性。
  • @Service:服务层,业务组装,调用持久层实现相应的功能。
  • @Repository:持久层,实际的业务处理,直接与数据库进行交互。
  • @Configuration:配置层,项目的一些配置。
  • @Component:组件层,提供一些公共的方法。

项目分层和调用关系如下:

Spring 创建和使用_第4张图片
@Component 是其他几个类注解的父类

Spring 创建和使用_第5张图片


2.2 Bean 默认命名规则

通常我们创建的类都是以大驼峰的方式命名的,然后我们通过类名的首字母小写就可以获取到 Bean 了。

  1. Bean 的名称默认是将原类名的首字母小写
@Controller
public class User {
    public void sayHi() {
        System.out.println("User: 你好!");
    }
}
public class App {
    public static void main(String[] args) {
        // 1. 得到 Spring 上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        // 2. 通过 Spring 上下文对象, 获取 Bean 对象
        User user = context.getBean("user", User.class);
        // 3. 使用 Bean
        user.sayHi();
    }
}
  1. 如果首字母已经是小写,那么 Bean 的名称为原类名
@Controller
public class admin {
    public void sayHi() {
        System.out.println("admin: 你好!");
    }
}
public class App {
    public static void main(String[] args) {
        // 1. 得到 Spring 上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        // 2. 通过 Spring 上下文对象, 获取 Bean 对象
        admin admin = context.getBean("admin", admin.class);
        // 3. 使用 Bean
        admin.sayHi();
    }
}
  1. 如果首字母和第二个字母都是大写,那么 Bean 的名称为原类名
@Controller
public class UController {
    public void sayHi() {
        System.out.println("UController: 你好!");
    }
}
public class App {
    public static void main(String[] args) {
        // 1. 得到 Spring 上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        // 2. 通过 Spring 上下文对象, 获取 Bean 对象
        UController uController = context.getBean("UController", UController.class);
        // 3. 使用 Bean
        uController.sayHi();
    }
}

为什么 Bean 的命名规则是这样呢?

查看源码,找到 Spring框架 中的 AnnotationBeanNameGenerator 类,里面有个 buildDefaultBeanName 这个方法。

Spring 创建和使用_第6张图片

这里调用的是 JDK Introspector 中的 decapitalize 方法,源码如下:

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);
}

可以看到 Spring 中 Bean 的默认命名是使用了 JDK 中的方法。


2.3 方法注解

方法注解 @Bean 是在方法上使用的注解,@Bean 是将方法的返回值的对象存储到 IoC 容器中。

  1. 不指定 Bean 名称
public class User {
    public String username;
    public String password;

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

@Component     // 方法注解必须配合类注解使用
public class Users {
    @Bean   // Bean 名称默认是方法名
    public User getUser() {
        User user = new User();
        user.username = "张三";
        user.password = "123";
        return user;
    }
}

public class App {
    public static void main(String[] args) {
        // 1. 得到 Spring 上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        // 2. 通过 Spring 上下文对象, 获取 Bean 对象
        User user = context.getBean("getUser", User.class);
        // 3. 使用 Bean
        System.out.println(user);
    }
}
  • 方法注解必须配合类注解一起使用。有方法注解的类,也需要添加类注解。
  • Bean 名称默认是方法名。
  1. 指定 Bean 名称
@Component     // 方法注解必须配合类注解使用
public class Users {
    // 指定 Bean 名称
    // @Bean({"user", "User"})
    // @Bean(value = "user")
    @Bean(name = {"user", "User"})
    public User getUser() {
        User user = new User();
        user.username = "张三";
        user.password = "123";
        return user;
    }
}
  • 指定 Bean 名称后,只能使用指定的名称来获取 Bean。
  • 可以指定多个名称,都能正常获取到 Bean

更简单的获取 Bean

可以使用注解来更简单的获取 Bean 对象,获取 Bean 对象叫做对象装配,也叫对象注入。

对象装配的三种实现方式:

  1. 属性注入
  2. 构造方法注入
  3. Setter 注入

使用 @Autowired 可以实现这三种对象装配的方式。

代码演示,按照实际项目开发的分层结构,将 Service 类注入到 Controller 中。

1 属性注入

public class User {
    public String username;
    public String password;

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}
@Service
public class UserService {

    public User getUser(String username) {
        User user = new User();
        user.username = username;
        return user;
    }
}
@Controller
public class UserController {
    
    // 属性注入
    @Autowired
    private UserService userService;

    public User getUser(String username) {
        return userService.getUser(username);
    }
}
public class App {
    public static void main(String[] args) {
        // 1. 得到 Spring 上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        // 2. 通过 Spring 上下文对象, 获取 Bean 对象
        UserController userController = context.getBean("userController", UserController.class);
        // 3. 使用 Bean
        System.out.println(userController.getUser("李四"));
    }
}

属性注入的关键代码:

@Controller
public class UserController {
    
    // 属性注入
    @Autowired
    private UserService userService;

    public User getUser(String username) {
        return userService.getUser(username);
    }
}
  • @Autowired 添加到成员变量上,可以给这个成员变量赋值,实现属性注入。
  • @Autowired 不支持静态变量。(不能在静态变量上使用)

在这里插入图片描述


2 构造方法注入

@Controller
public class UserController {

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

    public User getUser(String username) {
        return userService.getUser(username);
    }
}
  • @Autowired 可以将 Bean 对象注入到构造方法的参数上,然后再赋值给成员变量。
  • 只有一个构造方法时,@Autowired 可以省略。

3 Setter注入

@Controller
public class UserController {

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

    public User getUser(String username) {
        return userService.getUser(username);
    }
}
  • @Autowired 可以将 Bean 对象注入到实例方法的参数上,然后再赋值给成员变量。
  • @Autowired 不支持静态方法。(不能在静态方法上使用)

在这里插入图片描述


4 依赖注入与依赖查找的区别

  • 依赖查找根据 Bean 名称来获取 Bean。
  • 依赖注入首先根据类型来获取 Bean,如果获取到多个 Bean,那就根据名称去匹配。

代码演示依赖注入流程:

// 类注解存储Bean
@Service(value = "userService1")
public class UserService {

    public User getUser(String username) {
        User user = new User();
        user.username = username;
        return user;
    }

    // 方法注解存储Bean
    @Bean("userService2"public UserService get() {
        return new UserService();
    }
}
  • 这里将两个同类型的 Bean 存储到容器中,两个 Bean 的名称不同。
@Controller
public class UserController {

    // 属性注入
    @Autowired
    private UserService userService;

    public User getUser(String username) {
        return userService.getUser(username);
    }
}
  • 这里通过属性注入的方式获取 Bean。

报错信息:

在这里插入图片描述

  • 报错原因是找到了两个可用的 Bean 对象。
  • 通过类型获取到了两个 Bean 对象,一个叫 userService1,一个叫 userService2,而接收的变量名叫 userService

解决方法:

  1. 修改接收的变量名。
@Autowired
private UserService userService1;
  1. @Qualifier(不支持构造方法注入)
@Autowired
@Qualifier("userService2")
private UserService userService;
  1. @Resource(不支持构造方法注入)
@Resource(name = "userService1")
private UserService userService;

5 @Autowired 和 @Resource 的区别

@Resource 也可以实现对象注入,用法于 @Autowired 一样。

  1. @Resouce 是 JDK 的注解,@Autowired 是 Spring 的注解。
  2. @Resource 支持更多的参数设置,例如 name 设置获取 Bean 的名称。
  3. @Resource 不支持构造方法注入。
  4. @Autowried 在 idea 专业版下会误报(标红),不影响运行。

6 三种注入方式的优缺点

属性注入:

优点:使用简单。

缺点:

  1. 无法注入 final 修饰的变量。(final 修饰的变量,必须给初始值,直接赋值或者构造方法赋值)
  2. 通用性问题:只能适应于 IoC 容器。(只能通过属性注入方式赋值,非 IoC 就不能使用)
  3. 设计原则问题:更容易违背单一设计原则。(使用起来简单)

构造方法注入:

优点:

  1. 可以注入 final 修饰的变量。
  2. 通用性更好,没有IoC容器也可以通过构造方法赋值。

缺点:

  1. 无法解决循环依赖的问题。
  2. 写法属性注入复杂。

Setter 注入:

优点:通常 Setter 只设置一个属性,所以更符合单一设计原则。

缺点:

  1. 无法注入 final 修饰的变量。
  2. 注入的对象可以被修改。(setter 本来就是一个方法,那么就可以调用这个方法去修改)
  3. 写法比较属性注入复杂。

静态方法中只能用依赖查找的方式从Spring 中获取 Bean,因为静态方法的加载时机在 Spring 对象注入之前。


你可能感兴趣的:(spring,java,后端)