侧边栏壁纸
博主头像
快乐江湖的博客博主等级

更多内容请点击CSDN关注“快乐江湖”

  • 累计撰写 127 篇文章
  • 累计创建 33 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

第三章:Spring更简单的存储和读取Bean

快乐江湖
2023-11-17 / 0 评论 / 0 点赞 / 5 阅读 / 42967 字

经过前文的学习,我们已经可以实现基本的Spring存储和读取Bean了,但是在操作过程中却发现还是比较繁琐——存储一个对象就需要在xml中写入一次(远古方法)。因此本文将会使用注解这种方式实现更为简单的存储和读取(现代方法)

一:存储Bean

(1)准备工作:配置扫描路径

要想使用注解将对象存储的Spring中,需要配置一下Bean的扫描包路径,只有被配置的包下的所有类,添加了注解才能被正确识别并存储到Spring中

如下,项目包结构设置为这样

在spring-config.xml中添加如下配置,其中base-package处填入扫描根路径

<?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">  
    <content:component-scan base-package="com.service"></content:component-scan>  
</beans>

(2)利用注解存储Bean

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

  • 类注解
    • @Controller:控制器存储
    • @Service:服务存储
    • @Repository:仓库存储
    • @Component:组件存储
    • @Configuration:配置存储
  • 方法注解 将这个方法返回的对象存储到Spring中
    • @Bean

A:五大类注解

这五大类注解它们的功能是一模一样的,之所以要这样设置,其实是为了标识。具体来说,当程序员看到类注解后,就能直接了解当前类的用途,例如

  • @Controller:业务逻辑层,用于控制用户行为,检测用户参数合法性
  • @Service:服务层,调用持久化类实现相应功能(不直接和数据库交互、类似于控制中心)
  • @Repository:持久层,是直接和数据库进行交互的,通常来说每一张表对应一个@Repository
  • @Component:组件层,归属于公共工具类
  • @Configuration:配置层,是用来配置当前项目的一些信息

@Controller(控制器存储)

package com.service;  
  
import org.springframework.stereotype.Controller;  
  
@Controller  
public class StudyController {  
    public String SayHello(String name) {  
        return name + ":Hello!";  
    }  
}
package com.service;  
  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
public class App {  
    public static void main(String[] args) {  
        // 创建Spring上下文  
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");  
        // 获取指定Bean  
        StudyController studyController = (StudyController) context.getBean(StudyController.class);
        // 使用Bean  
        System.out.println(studyController.SayHello("ZhangSan"));  
  
    }  
}

@Service(服务存储)

package com.service;  
import org.springframework.stereotype.Service;  
  
@Service  
public class StudyService {  
    public String SayHello(String name) {  
        return name + ":Hello!";  
    }  
}
package com.service;  
  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
public class App {  
    public static void main(String[] args) {  
        // 创建Spring上下文  
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");  
        // 获取指定Bean  
        StudyService studyService = (StudyService) context.getBean(StudyService.class);  
        // 使用Bean  
        System.out.println(studyService.SayHello("Service"));  
  
    }  
}

@Repository(仓库存储)

package com.service;  
import org.springframework.stereotype.Repository;  
  
@Repository  
public class StudyRepository {  
    public String SayHello(String name) {  
        return name + ":Hello!";  
    }  
}
package com.service;  
  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
public class App {  
    public static void main(String[] args) {  
        // 创建Spring上下文  
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");  
        // 获取指定Bean  
        StudyRepository studyRepository = (StudyRepository) context.getBean(StudyRepository.class);  
        // 使用Bean  
        System.out.println(studyRepository.SayHello("Repository"));  
  
    }  
}

@Component(组件存储)

package com.service;  
import org.springframework.stereotype.Component;  
  
@Component  
public class StudyComponent {  
    public String SayHello(String name) {  
        return name + ":Hello!";  
    }  
}
package com.service;  
  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
public class App {  
    public static void main(String[] args) {  
        // 创建Spring上下文  
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");  
        // 获取指定Bean  
        StudyComponent studyComponent = (StudyComponent) context.getBean(StudyComponent.class);  
        // 使用Bean  
        System.out.println(studyComponent.SayHello("Component"));  
  
    }  
}

@Configuration(配置存储)

package com.service;  
import org.springframework.context.annotation.Configuration;  
  
@Configuration  
public class StudyConfiguration {  
    public String SayHello(String name) {  
        return name + ":Hello!";  
    }  
}
package com.service;  
  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
public class App {  
    public static void main(String[] args) {  
        // 创建Spring上下文  
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");  
        // 获取指定Bean  
        StudyConfiguration studyConfiguration = (StudyConfiguration) context.getBean(StudyConfiguration.class);  
        // 使用Bean  
        System.out.println(studyConfiguration.SayHello("Configuration"));  
  
    }  
}

B:类注解之间的关系

通过查阅源码可以看到,@Controller / @Service / @Repository / @Configuration均为@Component的子类

// @Controller
@Target({ElementType.TYPE})  
@Retention(RetentionPolicy.RUNTIME)  
@Documented  
@Component  
public @interface Controller {  
    @AliasFor(  
        annotation = Component.class  
    )  
    String value() default "";  
}

...

C:Bean命名规则

  • 之前获取Bean的方法只是临时展示

Bean命名规则:当使用五大类注解时,默认情况下获取Bean,只需将类名首字母小写即可。(注意如果类名首字母本身是小写也是没有问题的)

package com.service;  
import org.springframework.stereotype.Controller;  
  
@Controller  
public class StudyController {  
    public String SayHello(String name) {  
        return name + ":Hello!";  
    }  
}
package com.service;  
  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
public class App {  
    public static void main(String[] args) {  
        // 创建Spring上下文  
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");  
        // 获取指定Bean  
        StudyController studyController = (StudyController) context.getBean("studyController");  
        // 使用Bean  
        System.out.println(studyController.SayHello("Configuration"));  
  
    }  
}

特殊情况:当对象首字母和第二个字母均为大写时,需要使用原类名才能正确获取,否则报错

  • 报错:
  • 究其原因是因为它调用的是JDKintrospector中的decapitalize方法
package com.service;  
  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
public class App {  
    public static void main(String[] args) {  
        // 创建Spring上下文  
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");  
        // 获取指定Bean  
        SController sController = (SController) context.getBean("SController");  
        // 使用Bean  
        System.out.println(sController.SayHello("Configuration"));  
  
    }  
}

D:方法注解@Bean

方法注解@Bean:方法注解在使用必须配合类注解使用,否则将会出现错误。会将被注解的这个方法返回的对象存储Spring中,id为方法名

// Java类
package com.model;  
  
public class Student {  
    public int id;  
    public String name;  
    public int age;  
  
    public int getId() {  
        return id;  
    }  
  
    public void setId(int id) {  
        this.id = id;  
    }  
  
    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 "Student{" +  
                "id=" + id +  
                ", name='" + name + '\'' +  
                ", age=" + age +  
                '}';  
    }  
}
package com.service;  
  
import com.model.Student;  
import org.springframework.context.annotation.Bean;  
import org.springframework.stereotype.Controller;  
  
@Controller  
public class StudentBeans {  
    @Bean  
    public Student students() {  
        Student student = new Student();  
        student.setAge(18);  
        student.setId(1);  
        student.setName("张三");  
        return student;  
    }  
}
package com.service;  
  
import com.model.Student;  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
public class App {  
    public static void main(String[] args) {  
        // 创建Spring上下文  
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");  
        // 获取指定Bean  
        Student student = context.getBean("students", Student.class);  
        // 使用Bean  
        System.out.println(student);  
  
    }  
}

类名不太可能重复,但是方法名重复的概率很大。如下,对于下面的情况,程序虽然不会报错,但是会出现歧义、不可控

因此,可以通过设置name属性给Bean进行命名,并且一旦设置name属性后,就不能使用之前的方法获取Bean了

注意

  • 一个Bean可以有多个名字
  • name={}可以省略
@Controller  
public class StudentBeans {  
    @Bean(name={"s1", "s2"})  
    public Student students() {  
        Student student = new Student();  
        student.setAge(18);  
        student.setId(1);  
        student.setName("李四");  
        return student;  
    }  
}

@Controller  
public class StudentBeans {  
    @Bean({"s1", "s2"})  
    public Student students() {  
        Student student = new Student();  
        student.setAge(18);  
        student.setId(1);  
        student.setName("李四");  
        return student;  
    }  
}

二:获取Bean(对象注入)

如下,一般来说项目会有三级结构,为了简化问题,在第三级结构中我们只创建controllerservice两个层,目的将StudetService注入到ControllerService

(1)属性注入

A:概述

属性注入式是使用@Autowired实现

package com.demo.service;  
  
import org.springframework.stereotype.Service;  
  
@Service  
public class StudentService {  
  
    public void sayHello () {  
        System.out.println("Hello, Service");  
    }  
}
package com.demo.controller;  
  
import com.demo.service.StudentService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Controller;  
  
@Controller  
public class StudentController {  
  
    // 使用属性注入的方式获取Bean  
    @Autowired  
    private StudentService studentService;  
  
    public void sayHello() {  
        studentService.sayHello();  
    }  
}
package com;  
  
import com.demo.controller.StudentController;  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
public class App {  
    public static void main(String[] args) {  
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");  
        StudentController studentController = context.getBean("studentController", StudentController.class);  
        studentController.sayHello();  
    }  
}

B:优缺点分析

优点

  • 实现简单
  • 使用简单

缺点

①:使用属性注入无法注入一个不可变对象(final修饰)

  • 这是因为在Java中final对象要么直接赋值,要么在构造方法中赋值
  • 如果要注入一个不可变对象,可以使用构造方法注入

②:使用属性注入的方式只适用于 IoC 框架(容器),如果将属性注入的代码移植到其他非 IoC 的框架中,那么代码就无效了,所以属性注入的通用性不是很好

③: 由于属性注入使用非常简单,所以非常容易滥用,开发者很容易在一个类中同时注入多个对象

(2)Setter注入

A:概述

Setter注入要设置set方法,然后加上@AutoWired注解

package com.demo.service;  
  
import org.springframework.stereotype.Service;  
  
@Service  
public class StudentService {  
  
    public void sayHello () {  
        System.out.println("Hello, Service");  
    }  
}
package com.demo.controller;  
  
import com.demo.service.StudentService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Controller;  
  
@Controller  
public class StudentController {  
    private StudentService studentService;  
  
    // 使用Setter注入的方式获取Bean  
    @Autowired  
    public void setStudentService(StudentService studentService) {  
        this.studentService = studentService;  
    }  
  
    public void sayHello() {  
        studentService.sayHello();  
    }  
}
package com;  
  
import com.demo.controller.StudentController;  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
public class App {  
    public static void main(String[] args) {  
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");  
        StudentController studentController = context.getBean("studentController", StudentController.class);  
        studentController.sayHello();  
    }  
}

B:优缺点分析

优点:Setter注入完全符合单一职责的设计原则,因为每一个Setter只针对一个对象

缺点

①:不能注入不可变的对象(final修饰)

②:Setter注入提供了set方法,这意味着你可以在任何时候、任何地方通过调用set方法来改变注入对象

(3)构造方法注入

A:概述

构造方法注入是在类的构造方法中实现注入

  • 注意:如果只有一个构造方法,那么@Autowired注解可以省略
package com.demo.service;  
  
import org.springframework.stereotype.Service;  
  
@Service  
public class StudentService {  
  
    public void sayHello () {  
        System.out.println("Hello, Service");  
    }  
}
package com.demo.controller;  
  
import com.demo.service.StudentService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Controller;  
  
@Controller  
public class StudentController {  
    private final StudentService studentService;  
  
    // 使用构造方法注入
    @Autowired  
    public StudentController (StudentService studentService) {  
        this.studentService = studentService;  
    }  
  
    public void sayHello() {  
        studentService.sayHello();  
    }  
}
package com;  
  
import com.demo.controller.StudentController;  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
public class App {  
    public static void main(String[] args) {  
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");  
        StudentController studentController = context.getBean("studentController", StudentController.class);  
        studentController.sayHello();  
    }  
}

B:优缺点分析

优点

①:可注入不可变对象

②:构造方法在对象创建时只会被执行一次,因此它不会存在注入对象被修改的情况

③:由于依赖对象是在构造方法中执行的,而构造方法是在对象创建之初执行的,因此被注入的对象在使用之前会被完全初始化

④:构造方法注入可适用于任何环境(无论是否是IoC框架),构造方法注入的代码都是通用的


总之

  • 属性注入写法最简单,日常使用频率最高,但通用性很差
  • 官方推荐是构造方法注入,通用性很好
  • 如果要注入可变对象,可以考虑使用Setter注入

三:补充

(1)@Resource

在执行Setter注入和属性注入的时候,还可以使用@Resource

package com.demo.controller;  
  
import com.demo.service.StudentService;  
import org.springframework.stereotype.Controller;  
  
import javax.annotation.Resource;  
  
@Controller  
public class StudentController {  
    @Resource  
    private StudentService studentService;  
  
    public void sayHello() {  
        studentService.sayHello();  
    }  
}

@Autowired@Resource区别如下

  • 来源不同
    • @Autowired来自于Spring
    • @Resource来自于JDK注解
  • 使用时参数设置不同
    • @Resource支持更多的参数设置,例如name
  • @Resource只能用于Setter注入和属性注入,不能用于构造方法注入

(2)同一类型多个@Bean报错问题

对于下面的代码,当出现多个@Bean,然后返回同一对象类型时程序会报错

package com.demo.model;  
  
public class Student {  
    public int id;  
    public String name;  
    public int age;  
  
    public int getId() {  
        return id;  
    }  
  
    public void setId(int id) {  
        this.id = id;  
    }  
  
    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 "Student{" +  
                "id=" + id +  
                ", name='" + name + '\'' +  
                ", age=" + age +  
                '}';  
    }  
}
package com.demo.service;  
  
import com.demo.model.Student;  
import org.springframework.context.annotation.Bean;  
import org.springframework.stereotype.Controller;  
  
@Controller  
public class StudentBean {  
    @Bean  
    public Student student1() {  
        Student student = new Student();  
        student.setAge(18);  
        student.setId(1);  
        student.setName("张三");  
        return student;  
    }  
  
    @Bean  
    public Student student2() {  
        Student student = new Student();  
        student.setAge(20);  
        student.setId(2);  
        student.setName("李四");  
        return student;  
    }  
}
package com.demo.controller;  
  
import com.demo.model.Student;  
import com.demo.service.StudentService;  
import org.springframework.stereotype.Controller;  
  
import javax.annotation.Resource;  
  
@Controller  
public class StudentController {  
    @Resource  
    private Student student;  
  
    public void sayHello() {  
        System.out.println(student);  
    }  
}
package com;  
  
import com.demo.controller.StudentController;  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
public class App {  
    public static void main(String[] args) {  
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");  
        StudentController studentController = context.getBean("studentController", StudentController.class);  
        studentController.sayHello();  
    }  
}

解决这个问题有两种方法

①:使用@Resource(name="")声明需要哪个Bean

package com.demo.controller;  
  
import com.demo.model.Student;  
import com.demo.service.StudentService;  
import org.springframework.stereotype.Controller;  
  
import javax.annotation.Resource;  
  
@Controller  
public class StudentController {  
    @Resource(name = "student2")  
    private Student student;  
  
    public void sayHello() {  
        System.out.println(student);  
    }  
}

②:如果使用的是@Autowired,那么可以使用@Qualifier(value="")声明

package com.demo.controller;  
  
import com.demo.model.Student;  
import com.demo.service.StudentService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.beans.factory.annotation.Qualifier;  
import org.springframework.stereotype.Controller;  
  
import javax.annotation.Resource;  
  
@Controller  
public class StudentController {  
    @Autowired  
    @Qualifier(value = "student1")  
    private Student student;  
  
    public void sayHello() {  
        System.out.println(student);  
    }  
}

0

评论区