Spring(二)

简介: Spring(二)

依赖注入(依赖装配)

第一种方式:属性注入

说白了这个标题有点拗口,之前我们都是获取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();
    }
}

2.png

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的生命周期(面试常考)

生命周期概览

2.png

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


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生命周期中的初始 化步骤。

相关文章
|
7月前
|
网络协议 安全 网络安全
雷池WAF+emby+ddnsgo搭建个人影音库,实现远程安全访问流媒体
雷池WAF+emby+ddnsgo搭建个人影音库,实现远程安全访问流媒体
645 5
|
10月前
|
存储 人工智能 安全
阿里云 Confidential Al 最佳实践
本次分享的主题是阿里云 Confidential AI 最佳实践 ,由阿里云乾越分享。 1. 需求背景介绍 2. 大规模场景下面临的系统及安全风险 3. 计算栈的共享职责模型与用户信任边界的冲突 4. 传统计算、存储和网络安全技术中存在用户信任成本较高的问题 5. Confidential AI 方案实施模式 6. 基于CAI技术的阿里云Confidential Cloud Computing架构 7. Confidential AI on EGS DEMO 8. 阿里云全面应用Confidential AI 9. 完备的机密计算远程证明过程 10.发布《机密计算保障人工智能系统安全研究报告
447 1
|
9月前
|
监控 Cloud Native Java
基于阿里云容器服务(ACK)的微服务架构设计与实践
本文介绍如何利用阿里云容器服务Kubernetes版(ACK)构建高可用、可扩展的微服务架构。通过电商平台案例,展示基于Java(Spring Boot)、Docker、Nacos等技术的开发、容器化、部署流程,涵盖服务注册、API网关、监控日志及性能优化实践,帮助企业实现云原生转型。
|
机器学习/深度学习 存储 算法
数据结构与算法——BFS(广度优先搜索)
数据结构与算法——BFS(广度优先搜索)
|
JavaScript IDE 开发工具
【VsCode+LeetCode】优雅玩法
【VsCode+LeetCode】优雅玩法
429 0
|
消息中间件 Java Spring
Spring Boot与JMS消息中间件的集成
Spring Boot与JMS消息中间件的集成
|
弹性计算 关系型数据库 MySQL
|
新零售 Cloud Native 安全
光云科技快麦ERP正式入选阿里云原生合作伙伴计划,赋能企业“新基建”
阿里云在云原生领域的投入广泛而深入,在容器、服务网格和 Serverless 等领域均有丰富的技术和产品体系,目前阿里云已经拥有国内最丰富的云原生产品家族、最全面的云原生开源贡献、最大规模的云原生应用实践、最大的云原生客户群体。
1412 89
光云科技快麦ERP正式入选阿里云原生合作伙伴计划,赋能企业“新基建”
|
供应链 安全 物联网
区块链技术的应用场景和优势
区块链技术的应用场景和优势
1387 0
UE5 Motion Warping功能学习
UE5 Motion Warping功能学习
534 0
UE5 Motion Warping功能学习