您现在的位置是:首页 >技术交流 >从0学会Spring框架网站首页技术交流

从0学会Spring框架

X_H学Java 2024-06-01 00:00:03
简介从0学会Spring框架

1. 对Spring的理解

Spring是一个框架,包含了众多工具方法的IoC容器

什么是容器?

容器就是容纳物品的装置,比如:

  • List/Map,用来储存数据,说明它是一个数据存储容器
  • Tomcat,webapps目录下保存多个web应用程序,说明它是一个web容器

什么是IoC?

IoC的意思就是控制反转,是一种思想,主要通过IoC容器实现控制权发生反转,如对象的初始化及依赖关系(对象引用另一个对象),由程序自己控制这部分逻辑转变为由容器统一控制

在传统的开发中,如果A对象依赖B对象,那在A对象中就要new B对象,此时B对象的控制权就在A中,但是如果B对象一旦有所改变,如构造方法改变,那在A中也就要发生改变

控制反转呢就是控制权发生反转,B对象的控制权交给容器统一管理,此时B对象改变的话,A对象不用做任何处理,容器自动的将改动后的B对象交给A,此时A不必关注B的实现的方式,只管用就行

IoC的优点: 实现代码的解耦,将对象的生命周期交给IoC容器来管理,程序员无需管理

2. Spring IoC

前面说了Spring是一个包含众多工具方法的IoC容器,所以关键还是在容器上,所谓容器,就得具有容器的基本功能,取东西,存东西,对应这里的Spring容器,它具有最基本的功能为:

  • 将对象存储到容器
  • 从容器中将对象取出

Spring IoC就是一个实现了IoC思想的框架,具有存储Bean和取Bean的功能

将容器中存储的对象称为Bean对象(Java中的普通对象),Spring是一个IoC容器也就是将对象的创建和销毁的权利都交给Spring来管理,它本身具备了存储对象和获取对象的能力

3. DI

DI的意思为依赖注入,就是由IoC容器在运行期间,动态的将某种依赖关系注入到当前类中,就是将依赖的某个对象拿过来给当前类使用

DI与IoC的区别?

IoC是一种思想,DI为具体实现,它俩是从不同角度描述同一件事情,就是利用IoC容器,利用依赖关系注入的方式实现对象之间的解耦,例如:吃饭(IoC),吃米饭(DI)

4. 如何创建一个Spring项目

4.1 创建一个Maven项目

创建Maven项目
设置好创建项目的路径

设置路径

4.2 添加Spring框架支持

在pom.xml中添加Spring的框架支持,xml配置如下:

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.23</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.3.23</version>
        </dependency>
    </dependencies>

4.3 添加启动类

在java文件夹下,创建一个启动类,包含main方法

启动类

5. 存储Bean对象

5.1 添加配置文件

第一次添加需要在项目中添加Spring配置文件(只需第一次添加

resources目录下创建一个spring-config.xml(此文件名可以为别的,建议为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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>

5.2 创建Bean对象

Bean对象就是Java中普通的对象,这里我们创建一个Dog类

public class Dog {
    public void say(String name){
        System.out.println(name+":旺旺~");
    }
}

5.3 注册Bean

将创建好的Bean对象通过配置文件注册到Spring中

<bean id="dog" class="com.beans.Dog"></bean>

id为bean的名称,相当于给bean对象起一个名字,class为bean的路径(包名+类名)

6. 获取并使用Bean对象

使用Spring上下文的方式获取Bean对象

  1. 先获取Spring上下文
  2. 再通过上下文对象提供的方法获取指定的Bean对象
public class App {
    public static void main(String[] args) {
    //ApplicationContext是一个接口,new其实现类,构造方法的参数为刚才创建的配置文件的名称
    //得到spring容器
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        //使用spring容器获取bean对象,这里使用bean的名称获取,就是配置文件中bean的id
        //getBean的返回类型为Object,所以强转一下
        Dog dog = (Dog)context.getBean("dog"); //该句代码就是上述说的DI
        dog.say("旺财");
    }
}

打印结果

getBean的更多用法:

  • 使用bean id获取bean(需强转,返回类型为Object,id必须唯一
Dog dog = (Dog)context.getBean("dog");
  • 使用bean class获取bean(该类必须有唯一的bean,也就是多个该类型被注入到Spring中会报错)
Dog dog = context.getBean(Dog.class);

使用此种方式不用强转,但是该类必须唯一

  • 使用bean id和class获取bean
Dog dog = context.getBean("dog",Dog.class);

此种方式是为了解决使用class获取bean时存在多个相同类型的bean对象,此时加上id(因为id的唯一性)就可以获取唯一的bean对象,当相同的类多次注入到spring容器中时,此时多个相同类型的bean对象各不相同(每个bean都有其唯一的地址)

不管使用哪种方式获取,都必须保证能获取唯一的bean对象,否则会报错

推荐使用最后一种id和class获取bean对象

使用Bean工厂的方式获取Bean对象

  1. 先获取bean工厂对象
  2. 再使用该对象的getBean方法获取Bean对象
public class App {
    public static void main(String[] args) {
        BeanFactory factory = new XmlBeanFactory(new ClassPathResource("spring-config.xml"));
        Dog dog = factory.getBean("dog",Dog.class);
        dog.say("旺财");
    }
}

打印结果

ApplicationContext与BeanFactory有什么区别?

  • ApplicationContext属于BeanFactory的子类
  • BeanFactory只提供了基础访问Bean的方法,而ApplicationContext除了拥有BeanFactory的所有功能外,还提供了更多的方法实现,如对国际化的支持,资源访问的支持以及事件和传播等方面的支持
  • 性能两者不同,BeanFactory是按需加载Bean类似懒加载,而ApplicationContext在创建时会将所有Bean加载起来,属于饿汉式

在这里插入图片描述

使用ApplicationContext的方式,当代码执行完上述步骤的时候,所有的bean对象都会被初始化好加载到spring容器中

在这里插入图片描述

使用BeanFactory的方式,只有真正调用getBean的时候才会初始化对应的对象然后加载到spring容器中,类似懒加载的方式

7. 更简单存储Bean对象的方式

7.1 前置工作

更简单存储Bean的方式就是使用包扫描+注解的方式,此种方法可以和上述添加bean标签的方式共存

在Spring配置文件中设置bean扫描路径,将前面配置的xml文件换成下面的,但是注意下面存放bean对象的包名要和自己的创建的bean对象的包名要对应

<?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">
<!--    com.beans 是所有要存放bean对象的根路径-->
    <content:component-scan base-package="com.beans"></content:component-scan>
</beans>

7.2 添加存储对象的注解

7.2.1 类注解

  • @Controller,控制层(前端参数校验)
  • @Service,服务层(数据组装和接口调用)
  • @Repository,数据持久层(负责和数据库进行交互)
  • @Component,组件(非其他四类)
  • @Configuration,配置层(系统运行前,提前加载一些资源)
@Controller
public class UserController {
    public void sayHello(){
        System.out.println("hello Controller");
    }
}

使用注解注入Bean对象时,bean的名称也就是bean id为类名小驼峰的表示
如果原类名的第一个字母和第二个字母都是大写,那么此时的bean id为原类名不变

public class Application1 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        //bean id为类名改写为小驼峰方式
        UserController userController = context.getBean("userController",UserController.class);
        userController.sayHello();
    }
}

结果

其他四个注解与此用法相同

为什么需要五大类注解

五大类注解
提高代码的可读性,让程序员能够直观的判断当前类的用途

类注解的关系

@Controller,@Service,@Repository,@Configuration都是基于@Component实现的,所以可以认为@Component是它们的父类

7.2.2 方法注解

@Bean注解加到方法上,将方法的返回值添加到容器中

注意: @Bean注解必须结合五大类注解来使用,并且方法必须有返回值

@Component
public class Students {
    @Bean
    public Student s1(){
        Student student = new Student();
        student.setName("张三");
        student.setAge(23);
        return student;
    }
}
public class App1 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        Student student = context.getBean("s1",Student.class);
        System.out.println(student);
    }
}

打印结果

@Bean重命名

@Bean命名规则:

  • 当没有设置name属性时,方法名为bean名称
  • 当设置了name属性,只能通过name属性对应的值来获取,使用方法名就获取不到
@Component
public class Students {
    @Bean(name = "student1")
    public Student s1(){
        Student student = new Student();
        student.setName("张三");
        student.setAge(23);
        return student;
    }
}
public class App1 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        Student student = context.getBean("student1",Student.class);
        System.out.println(student);
    }
}

打印结果

起名字的时候,可以起多个名字:

    @Bean(name = {"user1","user2","user3"})
    public User getUser(){
        User user = new User();
        user.setUsername("张三");
        user.setAge(10);
        return user;
    }

8. 对象装配/对象注入

8.1 属性注入

使用@Autowired注解获取到Spring中的Student对象,将其注入到UserController1的属性中

@Controller
public class UserController1 {

    @Autowired
    private Student student;

    public void sayHello(){
        System.out.println(student);
    }
}
public class App2 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        UserController1 userController1 = context.getBean(UserController1.class);
        userController1.sayHello();
    }
}

打印结果

8.2 构造方法注入

构造方法注入是在构造方法上加@Autowired,实现注入

@Service
public class UserService {

    private Student student;

    @Autowired
    public UserService(Student student1){
        this.student = student1;
    }
    public void say(){
        System.out.println(student);
    }
}
public class App2 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        UserService userService = context.getBean(UserService.class);
        userService.say();
    }
}

打印结果

如果当前类中只存在一个构造方法,那么@Autowired可以省略,多个构造方法时则不可以省略

8.3 Setter注入

Setter注入是在设置的set方法上加@Autowired注解

@Controller
public class UserController2 {
    private Student student;

    @Autowired
    public void setStudent(Student student) {
        this.student = student;
    }

    public void say(){
        System.out.println(student);
    }
}
public class App1 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        UserController2 userController2 = context.getBean(UserController2.class);
        userController2.say();
    }
}

注意: 属性注入与Setter注入不可往final修饰的变量中注入,但是构造方法注入可以往final修饰的变量中注入

原因:final修饰的变量必须直接赋值,或者使用构造方法赋值,构造方法只会执行一次

8.4 @Resource注解

在进行对象注入的时候,除了可以使用 @Autowired 关键字之外,我们还可以使用 @Resource 进行注入

public class UserService2 {
    @Resource(name = "student1")
    private Student student;
    
    public void say(){
        System.out.println(student);
    }
}

@Autowired与@Resource区别

  1. 出身不同:@Resource来自于jdk,@Autowired时Spring框架提供的
  2. 用法不同:@Autowried可以用于属性注入,构造方法注入,Setter注入,@Resource不支持构造方法注入
  3. 参数不同:@Resource支持更多的参数设置,比如name,type,而@Autowried只支持required参数

8.5 @Bean将一个对象多次注入问题

在spring容器中找bean有两种方式:

  1. 根据bean的名称也就是bean id
  2. 根据bean的类型也就是bean class

先创建一个Cat类,属性有name和color,并提供get,set,toString方法

往Spring中注入多个Cat对象

@Controller
public class Cats {
    @Bean
    public Cat cat1(){
        Cat cat = new Cat();
        cat.setName("糯米");
        cat.setColor("白色");
        return cat;
    }
    
    @Bean
    public Cat cat2(){
        Cat cat = new Cat();
        cat.setName("汤圆");
        cat.setColor("黑色");
        return cat;
    }
}

在另一个类中获取Cat对象

@Controller
public class CatController {

    @Autowired
    private Cat cat;

    public void getCat(){
        System.out.println(cat);
    }
}

创建启动类

public class App3 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        CatController catController = context.getBean(CatController.class);
        catController.getCat();
    }
}

报错

发现报错了,因为Spring中已经注入了两个Cat对象,所以此时不知道获取哪个对象

在这里插入图片描述

三种解决方案

  1. 精确的描述bean的名称(将注入的名称写对)
  2. 使用@Rsource设置name方式来重命名注入对象
  3. 使用@Autowired+@Qualifier来筛选bean对象

方案一:精确的描述bean的名称(将注入的名称写对)

@Controller
public class CatController {

    @Autowired
    private Cat cat1; //精确描述bean名称

    public void getCat(){
        System.out.println(cat1);
    }
}

方案1

方案二: 使用@Rsource设置name方式来重命名注入对象

@Controller
public class CatController {

    @Resource(name = "cat2")
    private Cat cat;

    public void getCat(){
        System.out.println(cat);
    }
}

结果

方案三:使用@Autowired+@Qualifier来筛选bean对象

@Controller
public class CatController {

    @Autowired
    @Qualifier(value = "cat1")
    private Cat cat;

    public void getCat(){
        System.out.println(cat);
    }
}

结果

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。