栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring

声明:本文只阐述关于spring最核心的常考概念,其他概念我们可以到我的资源中关于Spring的pdf的文件进行查看

目录
  • Core Container(核心容器)
    • 简介
    • IoC/DI
      • 背景
      • 什么叫IoC(IOC是一种思想)
      • 什么又是IoC容器
      • 什么又是DI(DI是对IOC思想的实现的具体技术)
    • Spring容器使用流程(重要)
    • 开发步骤
      • 准备Maven项目及环境
      • 准备启动入口类
      • 准备spring配置文件
    • 初始化/注册Bean
      • 方式1:类注解
      • 方式2:@Bean(方法注解)
      • 方式3:@Configuration
    • 依赖注入(依赖装配)
      • 第一种方式:属性注入
      • 第二种方式:构造方法注入
      • 补充
        • 注入指定的Bean:@Qualifier
    • Bean的作用域(Bean的类型)(常见面试题)
      • singleton(单例模式)
        • 代码示例
      • prototype(原型模式)(每个请求创建一个新对象)
        • 代码示例
      • request
      • session
        • application(了解)
      • websocket(了解)
    • Bean的生命周期(面试常考)
      • 生命周期概览
        • 总结

Core Container(核心容器) 简介

本模块由 spring-core , spring-beans , spring-context , spring-context-support , and spring- expression (Spring expression Language) 4 个模块组成。
我们学习 spring-core 和 spring-beans 模块,这两个模块提供了整个Spring框架最基础的设施:IoC (Inversion of Control,控制反转) 和 DI (Dependency Injection,依赖注入)。这两个概念也是面试中最经常考的那一部分.
这部分功能相当于所有Spring 框架运行的基础,以前我们操作对象都需要手动的 new 对象,由对象的作用域决定对象的生命周期。使用Spring后,由框架提供了统一的容器来实例化、管理这些对象,并自 动组织对象与对象间的关系。这种容器称为IoC容器,有些地方也叫Spring Bean容器、Spring容器。
对象之间的复杂关系(体现在代码中就是对象中成员变量,引用了另一个对象),也交给了容器来进行设置。

IoC/DI 背景

大多数应用程序都是由两个或是更多的类通过彼此的合作来实现企业逻辑,这使得每个对象都需要获取 与其合作的对象(也就是它所依赖的对象)的引用。如果这个获取过程要靠自身实现,那么这将导致代 码高度耦合并且难以维护和调试。

什么叫IoC(IOC是一种思想)

IoC (Inversion of Control,控制反转) ,是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。只是因为该理论时间成熟相对较晚,并没有包含在GoF中。
系统中通过引入实现了IoC模式的IoC容器,即可由IoC容器来管理对象的生命周期、依赖关系等,从而使 得应用程序的配置和依赖性规范与实际的应用程序代码分离。
从使用上看,以前手动new对象,并设置对象中属性的方式,控制权是掌握在应用程序自身。现在则全 部转移到了容器,由容器来统一进行管理对象。因为控制权发生了扭转,所以叫控制反转。总的概括来说就是将对象创建和销毁的权利交给了IOC框架来进行管理.
关于IOC比较生动的一个例子:

可以看到我们Car类依赖于我们的frameWork类,然后frameWork类依赖于我们的Bottom类,Bottom类又依赖于我们的Tire类,此时可以看到Tire类中把我们车子的轮胎直接默认为了30的大小,但是我们并不想直接把车子的轮胎定义为30,那么我们此时就需要修改最后一个类,如上图所示,把Tire的无参构造方法转变为有参的构造方法,但是需要注意了,因为我们几个类之间是依赖的关系,所以当我们修改成有参的构造函数后,其他类都需要被修改,牵一发而动全身,这就是代码的耦合性,修改后的代码如下所示

此时为了降低耦合性,也就是我们所说的解耦,此时就需要我们将对象创建和销毁的权利交给了IOC框架来进行管理.也就是不再每个类都要修改一次,只需要修改我们的那一个公共类即可,此时我们来看修改过后的代码:


可以看到我们每个类传入的参数不再是自己new了,而是直接传入对象的引用,而把我们对象的创建交给了我们IOC容器,这样我们每次修改只需要去修改我们的Tire类即可,可以看到我们Tire类是以注入的方式到了我们的Bottom中,每个类之间的关系也都改为了注入的关系,并非依赖,这就是IOC思想

什么又是IoC容器

实现了IoC思想的容器就是IoC容器,比如:SpringFremework, Guice(Google开源的轻量级DI框架)
通过IOC容器可以实现对象生命周期管理,可以管理对象的依赖关系

什么又是DI(DI是对IOC思想的实现的具体技术)

DI (Dependency Injection,依赖注入) 是实现IoC的方法之一。所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。
所以,依赖注入(DI)和控制反转(IoC)是从不同的角度的描述的同一件事情,就是指通过引入 IoC 容器,利用依赖关系注入的方式,实现对象之间的解耦。
浅谈IOC–说清楚IOC是什么

Spring容器使用流程(重要)

Spring容器的API有 BeanFactory(现在基本已经不用) 和 ApplicationContext 两大类,他们都是顶级接口。其中ApplicationContext 是 BeanFactory 的子接口。对于两者的说明请参考面试题讲解Spring容器部分。我们主要使用 ApplicationContext 应用上下文接口。
下面是使用流程图,这个图要理解并会使用:

开发步骤 准备Maven项目及环境

首先创建一个Maven项目,名称为 springdemo1,以下是项目的maven配置文件 pom.xml :



    4.0.0

    org.example
    springdemo1
    1.0-SNAPSHOT

     
    
        1.8
        ${java.version}
        ${java.version}
        UTF-8
        5.2.10.RELEASE
    
       
    
        
            org.springframework
            spring-beans
            ${spring-framework.version}
        
        
            org.springframework
            spring-context
            ${spring-framework.version}
        
        
            org.projectlombok
            lombok
            1.18.16
        
    
    
        
            
            
                maven-clean-plugin
                3.1.0
            
            
                maven-compiler-plugin
                3.8.1
            
            
                maven-deploy-plugin
                2.8.2
            
            
                maven-install-plugin
                2.5.2
            
            
                maven-jar-plugin
                3.2.0
            
            
                maven-resources-plugin
                3.1.0
            
            
                maven-site-plugin
                3.3
            
            
                maven-surefire-plugin
                2.22.2
            
        
    

配置完成记得要刷新下maven面板哦

准备启动入口类

之后就可以使用Spring框架了,Spring提供了通过xml配置文件,来定义Bean,但是定义Bean的方式需 要通过包扫描的方式注册到容器中(其实还有其他方式,我们这里主要只掌握包扫描的方式)
写一个入口类:

package org.example;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class App {
    public static void main(String[] args) {
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("beans.xml");
       
        //一定要记得释放资源
        ((ClassPathXmlApplicationContext) applicationContext).close();
    }
}

准备spring配置文件

定义需要加载的Bean配置文件,在src/main/resource下,创建beans.xml文件




    


这个配置文件中最主要的话就是定义我们spring扫描bean的根路径,然后spring会去这个包下搜索需要注入的bean
定义这个路径的意义:如果我们不定义这个路径,spring就会去全局扫描整个java项目,这样做有两个缺点:1:性能比较低 2:有些场景不需要类托管.

初始化/注册Bean

既然上面已经指定路径了,下面我们就要把类拖官到spring容器当中了,方式呢有以下两种:

方式1:类注解

在类上使用注解 @Controller , @Service , @Repository , @Component 。需要保证该类会被Spring 扫描到
也就是说在方式一中,只有在类上加上这四个注解才能代表当前这个类是需要被spring容器托管的类

@Controller:也即是我们的前端业务交互层
@Service:业务处理层
@Repository:数据交互层
@Component:注解层

注意:只有之前在bean.xml中设置的包扫描路径上的类,且是使用了spring的这四个注解的类才可以被注册到容器中

  1. 以下模拟一个登录的类,其路径为org.example.controller.LoginController,@Conreoller注解会注册名为loginController的对象到容器中
package org.example.controller;

import org.springframework.stereotype.Controller;


//加上controller注解后,spring就能扫描到啦.

@Controller
public class LoginController {
    //注意LoginController类在spring中的id是类名开头字母小写,为loginController
}

定义好了Bean对象,注册到容器中以后,就可以获取Bean对象了,在入口类org.example.App中,
可以通过对象获取Bean,有两种方式获取:

  • 通过类型获取:这种获取方式要求该类型的Bean只能有一个
  • 通过名称获取:同一个类型的Bean可以有多个
    下面来看代码:
package org.example;

import org.example.controller.LoginController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class App {
    public static void main(String[] args) {
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("beans.xml");
        //之前我们都是new来获取对象,现在因为我们将LoginController注册到了spring容器当中
        //所以直接使用getBean方法,里面传入LoginController类在spring容器中的id便可以获取到对象

        //当然这是第一种获取对象的方式,但是不好在于假如我们的id写错了编译的时候并不会报错,但是运行后会报错
        LoginController loginController =
                (LoginController) applicationContext.getBean("loginController");

        //打印看是否获取成功,结果为org.example.controller.LoginController@543e710e
        System.out.println(loginController);


        //第二种方式是.class的方式,这种也是最常用的方式
        LoginController loginController1 =
                applicationContext.getBean(LoginController.class);


        //打印看是否获取成功,结果为org.example.controller.LoginController@543e710e
        System.out.println(loginController1);
        
        //一定要记得释放资源
        ((ClassPathXmlApplicationContext) applicationContext).close();
    }
}

  1. 此时我们再模拟一个URLController类,路径为org.example.controller.LoginController,但是这个类跟刚才有所不同的地方在于,@Conreoller注解会注册名为URLController的对象到容器中,并不是我们想象的第一个字母小写:例如uRLController这样的形式
package org.example.controller;

import org.springframework.stereotype.Controller;


@Controller
public class URLController {
}

定义好了Bean对象,注册到容器中以后,就可以获取Bean对象了,在入口类org.example.App中,同样的我们获取的方式还是两种:

package org.example;

import org.example.controller.URLController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class App {
    public static void main(String[] args) {
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("beans.xml");

        URLController urlController =
                (URLController) applicationContext.getBean("URLController");


        //结果为org.example.controller.URLController@543e710e
        System.out.println(urlController);
        
        URLController urlController1 =
                (URLController) applicationContext.getBean(URLController.class);

        //结果为org.example.controller.URLController@543e710e
        System.out.println(urlController1);

        //一定要记得释放资源
        ((ClassPathXmlApplicationContext) applicationContext).close();
    }
}

此时小伙伴们肯定会疑惑啦,我们spring到底是以怎样的一种方式来定义我们注册到spring容器中的id的呢?一会是开头首字母小写,一会又全是大写,那么到底是哪种定义方式呢?这个时候我们就需要去看我们的源码了:此时打开全局搜索后,搜索一个类叫做AnnotationBeanNameGenerator的类,这个类就是告诉我们spring到底是如何规范定义方式的
找到这个类中的一个方法名为generateBeanName

其返回值是一个buildDefaultBeanName方法,点到这个方法来看:

这个方法的返回值是buildDefaultBeanName这个方法
而这个方法最核心的语句就是下面这句话:程序把刚才的类名传到shortClassName参数这里生成id

return Introspector.decapitalize(shortClassName);

而decapitalize这个方法是属于jdk1.8的方法,点开进入到这个方法内部:

ok,我们此时在这个方法内部找到了端倪,也就是关于这个方法的注释告诉了我们命名的规则:大致意思如下:
假设第一个字母为大写,那么最后得到的第一个字母就是小写,假设是URL,最后得到的仍是URL
下面我们写一个测试类来测试一下:

package org.example.Test;

import java.beans.Introspector;


public class TestDemo {
    public static void main(String[] args) {
        String className = "URLController";
        //结果为URLController
        System.out.println(Introspector.decapitalize(className));

        String className1 = "LLoginController";
        //结果为LLoginController
        System.out.println(Introspector.decapitalize(className1));

        String className2 = "LoginController";
        //结果为loginController
        System.out.println(Introspector.decapitalize(className2));

        String className3 = "loginController";
        //结果为loginController
        System.out.println(Introspector.decapitalize(className2));
    }
}

总结:
1:如果第一个字母为大写,第二个字母为小写,那么最终就是第一个字母为小写的值
2:如果第一个字母为大写,第二个字母为大写,那么最终就是第一个字母为大写的值
3:如果第一个字母和第二个字母都是小写,最后还是原样不变.

方式2:@Bean(方法注解)

当前类被 Spring 扫描到时,可以在方法上使用@Bean注解,通过方法返回类型,也可以定义、注册Bean对象,默认使用方法名为Bean的名称

先定义一个用户类 User,其路径为org.example.model.User ,注意这里没有使用任何Spring的注解,User类不会被扫描到,但是使用了lombok注解,使用了此注解后,我们就可以省略自己写getter,setter方法以及toString方法

package org.example.model;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;


@Getter
@Setter
@ToString
public class User {
    private String name;
    private String password;
}

在此顺便我们来讲下lombok的原理,很多同学知道它,也会用,但是不清楚其中的原理:
lombok的实现原理:通过自定义的类注解,将自定义的类注解在程序编译的时候,转换成对应的JVM可以运行起来的代码,所以说我们的lombok只存在于编译时期,运行时期仍等价于下面的代码:小伙伴们可以build一下我们的项目,然后在target目录下的User.class文件查看,发现注解在编译时期就已经转换成了JVM可以运行起来的代码:

自定义的类注解:即就是lombok自己所提供的方法,例如@Getter,它就代表了两个含义:(1)代表类里面所有的属性都将被读取.(2)给每一个属性生成一个原生的Get方法

当然还有一个注解可以把刚才的@Getter,@Setter,@ToString这三个注解的作用全部给包括了,那就是@Data注解,这个注解也是非常的好用,写到实体类上也是可以的.
紧接着下一步,我们在已经注册到spring容器当中的LoginController中来完成剩余操作,此时我们在这个类中定义两个用户对象,并且这两个对象是在两个方法user1和user2内部的,来看代码:

package org.example.controller;

import org.example.model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;

//加上controller注解后,spring就能扫描到啦.

@Controller
public class LoginController {
//对方法加上Bean注解,就可以注册到spring容器当中去啦
    @Bean
    public User user1() {
        User user = new User();
        user.setName("abc");
        user.setPassword("123");
        return user;
    }

    @Bean
    public User user2() {
        User user = new User();
        user.setName("我不是汤神");
        user.setPassword("tang");
        return user;
    }
}

定义好了Bean对象,注册到容器中以后,就可以获取Bean对象了,在入口类org.example.App中,同样的我们获取的方式还是两种:来看代码

package org.example;

import org.example.model.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class App {
    public static void main(String[] args) {
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("beans.xml");


        User user1 = (User) applicationContext.getBean("user1");
        //结果为User(name=abc, password=123)
        System.out.println(user1);

        //第一种方式获取user2对象
        User user2 = (User) applicationContext.getBean("user2");
        //输出结果为User(name=我不是汤神, password=tang)
        System.out.println(user2);

        //第二种方式获取user2对象
        User user3 = applicationContext.getBean("user2", User.class);
        //输出结果为User(name=我不是汤神, password=tang)
        System.out.println(user3);

        //一定要记得释放资源
        ((ClassPathXmlApplicationContext) applicationContext).close();
    }
}

方式3:@Configuration

在类被Spring扫描到时,使用**@Configuration注解,可以注册一个配置类到容器中。配置类一般用来自定义配置某些资源。之后会在SpringMVC中用到。
下面还是创建一个类名为MyAppConfig,这个类的路径为org.example.config.MyAppConfig**,在这个类上我们加入@Configuration注解

package org.example.controller.config;

import org.springframework.context.annotation.Configuration;


@Configuration
public class MyAppConfig {

}

定义好了Bean对象,注册到容器中以后,就可以获取Bean对象了,在入口类org.example.App中,同样的我们获取的方式还是两种:来看代码:

package org.example;

import org.example.controller.config.MyAppConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class App {
    public static void main(String[] args) {
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("beans.xml");


        //第一种方式
        MyAppConfig myAppConfig =  (MyAppConfig)applicationContext.getBean("myAppConfig");
        //输出结果为org.example.controller.config.MyAppConfig$$EnhancerBySpringCGLIB$$b2652cee@1d76aeea
        System.out.println(myAppConfig);

        //第二种方式
        MyAppConfig myAppConfig1 = applicationContext.getBean(MyAppConfig.class);
        //输出结果为org.example.controller.config.MyAppConfig$$EnhancerBySpringCGLIB$$b2652cee@1d76aeea
        System.out.println(myAppConfig1);


        //一定要记得释放资源
        ((ClassPathXmlApplicationContext) applicationContext).close();
    }
}

依赖注入(依赖装配) 第一种方式:属性注入

说白了这个标题有点拗口,之前我们都是获取Bean对象就完事了,现在我们不但要从spring容器中去获取对象,同时还要将这个取出来的对象发到某个类里面,那么这个过程就叫做依赖注入,也称作依赖装配.
那么首先我们创建一个LoginService类,这个类的路径为org.example.service.LoginService:我们使用@Service注解将这个类注入到spring容器当中,代码如下所示:

package org.example.controller.service;

import org.springframework.stereotype.Service;

@Service
public class LoginService {
    public void sayHi(){
        System.out.println("LoginService say hi");
    }
}

然后此时我们想在LoginController类下面来取LoginService这个对象中的sayHi方法,就需要使用到@Autowired注解,代码如下所示:
首先是在对象上使用@Autowired注解(一般这种写法用的最多)

package org.example.controller;

import org.example.controller.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

//加上controller注解后,spring就能扫描到啦.

@Controller
public class LoginController {
    //第一种注入的方式,叫做属性注入,以下岩石的是属性注入的第一种写法:@Autowired
    //注意这种取名字的方式根Bean对象存到spring容器中类似,为loginService
    //取名方式前面有,我就不再做过多赘述了
    @Autowired
    private LoginService loginService;

    public void sayHi() {
        //取到后直接调用即可
        loginService.sayHi();
    }
    
}

同时我们还可以在setter方法上使用@Autowired注解

package org.example.controller;

import org.example.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

//加上controller注解后,spring就能扫描到啦.

@Controller
public class LoginController {
    //第一种注入的方式:属性注入的第二种写法setter注入+@Autowired

    private LoginService loginService;

//这样写的好处是传参的名字随便命名,之前还必须是loginService,现在写ls也可以
    @Autowired
    public void setLoginService(LoginService ls) {
        this.loginService = ls;
    }

    public void sayHi() {
        //取到后直接调用即可
        loginService.sayHi();
    }

}

然后我们在App这个类中进行下测试,看我们从spring容器中获取到LoginController这个对象后调用sayHi()方法,可不可以输出LoginService里面的sayHi方法的结果.

package org.example;

import org.example.controller.LoginController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class App {
    public static void main(String[] args) {
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("beans.xml");
        
        LoginController controller = applicationContext.getBean(LoginController.class);
        //结果为LoginService say hi
        controller.sayHi();


        //一定要记得释放资源
        ((ClassPathXmlApplicationContext) applicationContext).close();
    }
}

发现结果输出成功

第二种方式:构造方法注入

第二种注入方式为构造方法的写法,这种方法其实也非常简单:下面来看写法:
那么首先我们创建一个LoginService类,这个类的路径为org.example.service.LoginService:我们使用@Service注解将这个类注入到spring容器当中,代码如下所示:

package org.example.controller.service;

import org.springframework.stereotype.Service;

@Service
public class LoginService {
    public void sayHi(){
        System.out.println("LoginService say hi");
    }
}

然后此时我们想在LoginController类下面来取LoginService这个对象中的sayHi方法,之前我们使用的是属性注入的方法,现在我们使用的是构造方法注入的方法,来看代码:

package org.example.controller;

import org.example.service.LoginService;
import org.springframework.stereotype.Controller;

//加上controller注解后,spring就能扫描到啦.

@Controller
public class LoginController {
    //第二种注入的方式:构造方法的注入

    private LoginService loginService;
     //注意此处就不需要加入@Autowired注解
    public LoginController(LoginService loginService) {
        this.loginService = loginService;
    }

    public void sayHi() {
        //取到后直接调用即可
        loginService.sayHi();
    }

}

然后我们在App这个类中进行下测试,看我们从spring容器中获取到LoginController这个对象后调用sayHi()方法,可不可以输出LoginService里面的sayHi方法的结果.

package org.example;

import org.example.controller.LoginController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class App {
    public static void main(String[] args) {
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("beans.xml");

        LoginController controller = applicationContext.getBean(LoginController.class);
        //结果为LoginService say hi
        controller.sayHi();


        //一定要记得释放资源
        ((ClassPathXmlApplicationContext) applicationContext).close();
    }
}

补充 注入指定的Bean:@Qualifier

在spring容器中同类型的Bean有多个时,注入该类型Bean需要指定Bean的名称:注入有两种方法:

  • 属性名或方法参数名设置为Bean的名称
  • 属性名或方法参数设置 @Qualifier("名称")注解,注解内的字符串是Bean对象的名称
  • 以下LoginController类中定义了2个用户对象,且在方法参数中注入了Bean对象:
package org.example.controller;

import org.example.model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;

//加上controller注解后,spring就能扫描到啦.

@Controller
public class LoginController {

   

    //注意此时我们往spring容器中注入了两个Bean对象
    @Bean
    public User user1() {
        User user = new User();
        user.setName("abc");
        user.setPassword("123");
        return user;
    }

    @Bean
    public User user2() {
        User user = new User();
        user.setName("我不是汤神");
        user.setPassword("tang");
        return user;
    }

}

我们先来展示错误获取某个Bean对象的名字的代码写法:

package org.example.controller;

import org.example.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;

//加上controller注解后,spring就能扫描到啦.

@Controller
public class LoginController {

    //如果按照底下这样写我们会报错
    @Autowired
    private User user;

    //注意此时我们往spring容器中注入了两个Bean对象
    @Bean
    public User user1() {
        User user = new User();
        user.setName("abc");
        user.setPassword("123");
        return user;
    }

    @Bean
    public User user2() {
        User user = new User();
        user.setName("我不是汤神");
        user.setPassword("tang");
        return user;
    }

    public void sayHi() {
        //在这里会报错,原因是我们此时并不知道user这个引用到底指向user1方法返回的对象还是user2方法返回的对象
        System.out.println(user.getName());
    }

}

在App类内部进行输出打印:

package org.example;

import org.example.controller.LoginController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class App {
    public static void main(String[] args) {
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("beans.xml");

        LoginController controller = applicationContext.getBean(LoginController.class);
        //直接报错
        controller.sayHi();


        //一定要记得释放资源
        ((ClassPathXmlApplicationContext) applicationContext).close();
    }
}

以下是错误信息:

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type ‘org.example.model.User’ available: expected single matching bean but found 2: user1,user2
很明显系统并不知道获取哪个对象的name,所以报错了,此时我们提供两种解决方案

解决方案1:属性名或方法参数名设置为Bean的名称
LoginController类代码:

package org.example.controller;

import org.example.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;

//加上controller注解后,spring就能扫描到啦.

@Controller
public class LoginController {

    //属性名或方法参数名设置为Bean的名称 此时设置名称为方法名user1
    @Autowired
    private User user1;

    //注意此时我们往spring容器中注入了两个Bean对象
    @Bean
    public User user1() {
        User user = new User();
        user.setName("abc");
        user.setPassword("123");
        return user;
    }

    @Bean
    public User user2() {
        User user = new User();
        user.setName("我不是汤神");
        user.setPassword("tang");
        return user;
    }

    public void sayHi() {
        System.out.println(user1.getName());
    }

}

在App类内部打印结果,结果应为abc,我们来看结果是否正确:

package org.example;

import org.example.controller.LoginController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class App {
    public static void main(String[] args) {
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("beans.xml");

        LoginController controller = applicationContext.getBean(LoginController.class);
        //结果为abc,正确
        controller.sayHi();


        //一定要记得释放资源
        ((ClassPathXmlApplicationContext) applicationContext).close();
    }
}

解决方案2:属性名或方法参数设置 @Qualifier("名称")注解,注解内的字符串是Bean对象的名称
LoginController类代码:

package org.example.controller;

import org.example.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;

//加上controller注解后,spring就能扫描到啦.

@Controller
public class LoginController {

    //属性名或方法参数设置	`@Qualifier("名称")`注解,注解内的字符串是Bean对象的名称
    @Autowired
    @Qualifier("user1")
    private User user;

    //注意此时我们往spring容器中注入了两个Bean对象
    @Bean
    public User user1() {
        User user = new User();
        user.setName("abc");
        user.setPassword("123");
        return user;
    }

    @Bean
    public User user2() {
        User user = new User();
        user.setName("我不是汤神");
        user.setPassword("tang");
        return user;
    }

    public void sayHi() {
        System.out.println(user.getName());
    }

}

在App类内部打印结果,结果应为abc,我们来看结果是否正确:

package org.example;

import org.example.controller.LoginController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class App {
    public static void main(String[] args) {
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("beans.xml");

        LoginController controller = applicationContext.getBean(LoginController.class);
        //结果为abc,正确
        controller.sayHi();


        //一定要记得释放资源
        ((ClassPathXmlApplicationContext) applicationContext).close();
    }
}

Bean的作用域(Bean的类型)(常见面试题)

其实在这里作用域指的就是类型,也就是在spring中Bean都有哪些类型?常见的一共有六种

singleton(单例模式)

官方说明:(Default) Scopes a single bean definition to a single object instance for each Spring IoC container.

描述:该作用域下的Bean在IoC容器中只存在一个实例:获取Bean(即通过applicationContext.getBean等方法获取)及装配Bean(即通过@Autowired注入)都是同一个对 象。
场景:通常无状态的Bean使用该作用域。无状态表示Bean对象的属性状态不需要更新
备注:Spring默认选择该作用域
怎样验证其是同一个对象呢?来看代码示例:

代码示例

还是我们的LoginController类:

package org.example.controller;

import org.springframework.stereotype.Controller;

//加上controller注解后,spring就能扫描到啦.

@Controller
public class LoginController {

}

然后来到我们的APP类获取我们的Bean对象,假设遵循单例模式,那么最终获取到的Bean对象使用==判断时应该为true,因为都是同一对象,来看代码:

package org.example;

import org.example.controller.LoginController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class App {
    public static void main(String[] args) {
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("beans.xml");

        LoginController controller = applicationContext.getBean(LoginController.class);
        LoginController controller1 = applicationContext.getBean(LoginController.class);
        //答案为true
        System.out.println(controller == controller1);

        //一定要记得释放资源
        ((ClassPathXmlApplicationContext) applicationContext).close();
    }
}

prototype(原型模式)(每个请求创建一个新对象)

官方说明:Scopes a single bean definition to any number of object instances.
描述:每次对该作用域下的Bean的请求都会创建新的实例:获取Bean(即通过applicationContext.getBean等方法获取)及装配Bean(即通过@Autowired注入)都是新的对象 实例。
场景:通常有状态的Bean使用该作用域

代码示例

还是我们的LoginController类:只不过这次要在LoginController类上加上@Scope("prototype")

package org.example.controller;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;

//加上controller注解后,spring就能扫描到啦.

@Controller
@Scope("prototype")
public class LoginController {


}

然后来到我们的APP类获取我们的Bean对象,假设此时遵循prototype,那么最终获取到的Bean对象使用==判断时应该为false,因为创建了新的实例,来看代码:

package org.example;

import org.example.controller.LoginController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class App {
    public static void main(String[] args) {
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("beans.xml");

        LoginController controller = applicationContext.getBean(LoginController.class);
        LoginController controller1 = applicationContext.getBean(LoginController.class);
        //答案为false
        System.out.println(controller == controller1);

        //一定要记得释放资源
        ((ClassPathXmlApplicationContext) applicationContext).close();
    }
}

request

官方说明:Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean
definition. only valid in the context of a web-aware Spring ApplicationContext.
描述:每次http请求会创建新的Bean实例,类似于prototype
场景:一次http的请求和响应的共享Bean
备注:限定SpringMVC中使用

session

官方说明:Scopes a single bean definition to the lifecycle of an HTTP Session. only valid in the context of a web-aware Spring ApplicationContext.
描述:在一个http session中,定义一个Bean实例
场景:用户回话的共享Bean, 比如:记录一个用户的登陆信息
备注:限定SpringMVC中使用

application(了解)

官方说明:Scopes a single bean definition to the lifecycle of a ServletContext. only valid in the context of a web-aware Spring ApplicationContext.
描述:在一个http servlet Context中,定义一个Bean实例
场景:Web应用的上下文信息,比如:记录一个应用的共享信息
备注:限定SpringMVC中使用

websocket(了解)

官方说明:Scopes a single bean definition to the lifecycle of a WebSocket. only valid in the context of a web-aware Spring ApplicationContext.
描述:在一个HTTP WebSocket的生命周期中,定义一个Bean实例
场景:WebSocket的每次会话中,保存了一个Map结构的头信息,将用来包裹客户端消息头。第一 次初始化后,直到WebSocket结束都是同一个Bean。
备注:限定Spring WebSocket中使用

面试的时候记住前五种即可

Bean的生命周期(面试常考) 生命周期概览


乍一看卧槽这个图可真多,如果上面的图记不住的话,至少也要记住底下的这几步:

1:实例化:将二进制字节流转换成内存对象的过程,此时属性还未注入
2:属性设置:进行依赖注入
3:进行初始化:开始执行我的业务代码
4:销毁

如果能记下来,就记住下面的总结

总结

对于Bean的生命周期,主要步骤为:
1.实例化Bean:通过反射调用构造方法实例化对象。
2.依赖注入:装配Bean的属性
3.实现了Aware接口的Bean,执行接口方法:如顺序执行BeanNameAware、BeanFactoryAware、
ApplicationContextAware的接口方法。
4.Bean对象初始化前,循环调用实现了BeanPostProcessor接口的预初始化方法
(postProcessBeforeInitialization)
5.Bean对象初始化:顺序执行@PostConstruct注解方法、InitializingBean接口方法、init-method
方法
6.Bean对象初始化后,循环调用实现了BeanPostProcessor接口的后初始化方法
(postProcessAfterInitialization)
7.容器关闭时,执行Bean对象的销毁方法,顺序是:@PreDestroy注解方法、DisposableBean接口方法、destroy-method

补充说明:第一步的实例化是指new对象,Spring的语义中说初始化Bean包含Bean生命周期中的初始 化步骤。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/396683.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号