Spring的使用-Bean对象的储存和获取/Bea对象的作用域与生命周期

本文主要是介绍Spring的使用-Bean对象的储存和获取/Bea对象的作用域与生命周期,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一.存储Bean对象

1.1 <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"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="user" class="User"> </bean><bean id="people" class="demo.People"></bean>
</beans>

class路径是以java为当前路径来写的 

此时只是将类配置到了配置文件中,并没有真正的放到Spring中,只有在获取上下文对象时,才会将对象放进去。

获取bean对象:

    public static void main(String[] args) {//先得到 spring 上下文对象(路径名称要和spring创建的配置文件名称相等)ApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml");//依赖查找->IoC的一种实现方式//通过Bean名称来获取(这里的名称要和配置文件中的id)相等,此时得到的object对象,需要强转User user=(User)context.getBean("user");//通过类型来获取bean对象(如果Spring容器中有多个该类型对象,会报错)User user1=context.getBean(User.class);//通过Bean名称和类型来获取User user2=context.getBean("user",User.class);System.out.println(user.sayHello());
//        People people=context.getBean("people",People.class);
//        System.out.println(people.sayHi());}
//User Constructor
//hello worldpublic static void main(String[] args) {//XmlBeanFactory 有横线说明被官方弃用了,不建议使用BeanFactory context=new XmlBeanFactory(new ClassPathResource("spring-config.xml"));//通过Bean名称来获取(这里的名称要和配置文件中的id)相等,此时得到的object对象,需要强转User user=(User)context.getBean("user");//通过类型来获取bean对象User user1=context.getBean(User.class);//通过Bean名称和类型来获取User user2=context.getBean("user",User.class);System.out.println(user.sayHello());//People people=context.getBean("people",People.class);//System.out.println(people.sayHi());}
//User Constructor
//People constructor
//hello world

ApplicationContext VS BeanFactory 有什么区别?

相同点:都是容器管理对象,都可以获取Bean对象。

区别:

ApplicationContext 是 BeanFactory 的子类,ApplicationContext 拥有更多的功能(对国际化⽀持、资源访问⽀持、以及事件传播等⽅⾯的⽀持)

Bean 的加载机制不同:ApplicationContext 是一次加载并初始化所有的Bean对象,而BeanFactory 是需要哪个才去加载哪个(懒加载),因此更加轻量。

1.2  配置扫描路径

在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"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">
<!--       <bean id="user" class="User"> </bean>-->
<!--       <bean id="people" class="demo.People"></bean>-->
<!--       配置bean的扫描根路径:只有当前目录下的类才会扫描是否添加了注解,如果添加了注解--><content:component-scan base-package="demo"></content:component-scan>
</beans>

注意:想要将对象成功的存储到Spring 中,我们需要配置一下存储对象的扫描包路径,只有被配置的包下的所有类,添加了注解才能被正确的识别并保存到Spring中

二. 通过注解添加Bean对象

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

2.1 类注解:@Controller、@Service、@Repository、@Component、@Configuration

• @Controller【控制器】校验参数的合法性(安检系统),负责接口对接。

• @Service【业务】业务组装(客服中心):会告诉我使用哪些方法,调用哪些接口,但是不具体实行。

• @Repository【数据持久层】实际业务处理(实际办理的业务):即业务层调用的方法的具体实现,操作一些和数据库相关的信息。

• @Component【组件】 工具类层(基础的工具):例如对账户加密,进行的业务不涉及数据库

• @Configuration 【配置层】配置:例如一些端口号

这五个类注解都可以使用
//@Controller
//@Service
//@Repository
//@Configuration@Component
public class People {public People(){System.out.println("People constructor");}public void sayHi(){System.out.println("hello world");}
}public static void main(String[] args) {ApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml");People people=context.getBean("people",People.class);people.sayHi();}

Bean 生成名称源代码(此处是调用的jdk中的命名方法,并不是spring定义的,是Java定义的)

    public static String decapitalize(String name) {if (name == null || name.length() == 0) {return name;}//如果name长度大于1,并且前两个字符都是大写,返回name本身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);}

类注解Bean命名规则:

默认情况下,当类名的前两个字母都是大写时,名字和类名相同;当类名的前两个字母只有第一个是大写时,则名字是类名首字母小写。

若通过value设置,那么只能通过设置的类名来获取

五大类注解之间的关系

查看@Controller/@Service/@Repository/@Configuration 源码发现:

其他的四个类都是基于@Component实现的

2.2  方法注解:@Bean 

注意:@Bean 必须配合五大类注解一同使用,否则就会报错. (如果不使用五大类注解,spring就会扫描所有的类中的方法,会大大的降低效率).

public class ArticleInfo {private int id;private String title;private String Content;private LocalDateTime CreateTime;public int getId() {return id;}public void setId(int id) {this.id = id;}public String getTitle() {return title;}public void setTitle(String title) {this.title = title;}public String getContent() {return Content;}public void setContent(String content) {Content = content;}public LocalDateTime getCreateTime() {return CreateTime;}@Overridepublic String  toString() {return "ArticleInfo{" +"id=" + id +", title='" + title + '\'' +", Content='" + Content + '\'' +", CreateTime=" + CreateTime +'}';}public void setCreateTime(LocalDateTime createTime) {CreateTime = createTime;}
}@Controller
public class Articles {
//    可以通过以下三种方式来命名,可以命名任意个名字,若命名后,默认命名(方法名)不能再使用
//    @Bean({"arti","article"})
//    @Bean(name = {"arti","article"})@Bean(value = {"arti","article"})public ArticleInfo art(){//伪代码(真实的代码中是不允许我们去new的,因为我们IoC的思想就是控制反转)ArticleInfo articleInfo=new ArticleInfo();articleInfo.setId(1);articleInfo.setTitle("西游记");articleInfo.setContent("大闹天空");articleInfo.setCreateTime(LocalDateTime.now());return articleInfo;}
}public static void main(String[] args) {ApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml");
//      ArticleInfo articleInfo=context.getBean("article",ArticleInfo.class);
//      ArticleInfo articleInfo=context.getBean("arti",ArticleInfo.class);ArticleInfo articleInfo=context.getBean("art",ArticleInfo.class);System.out.println(articleInfo);}

• @Bean的默认获取方式:@Bean的默认命名=方法名

2.3 @Bean的加载顺序

2.3.1 名称相同

//此处的Order中的数字越大,权重越高,优先执行
@Order(100)
@Controller
public class Articles {
//    可以通过以下三种方式来命名,可以命名任意个名字,若命名后,默认命名不能再使用
//    @Bean({"arti","article"})
//    @Bean(name = {"arti","article"})
@Bean(value = {"arti","article"})
public ArticleInfo arti(){//伪代码(真实的代码中是不允许我们去new的,因为我们IoC的思想就是控制反转)ArticleInfo art=new ArticleInfo();art.setId(2);art.setTitle("三国演义");art.setContent("温酒斩华雄");art.setCreateTime(LocalDateTime.now());return art;
}@Bean(value = {"arti","article"})public ArticleInfo art(){//伪代码(真实的代码中是不允许我们去new的,因为我们IoC的思想就是控制反转)ArticleInfo art=new ArticleInfo();art.setId(1);art.setTitle("西游记");art.setContent("大闹天空");art.setCreateTime(LocalDateTime.now());return art;}
}@Order(18)
@Component
public class Articles2 {@Bean(value = {"arti","article"})public ArticleInfo arti(){//伪代码(真实的代码中是不允许我们去new的,因为我们IoC的思想就是控制反转)ArticleInfo art=new ArticleInfo();art.setId(3);art.setTitle("红楼梦");art.setContent("我也不知道");art.setCreateTime(LocalDateTime.now());return art;}
}

在同一个类中,返回同名的相同类型的方法 Bean 时, 按照前后顺序执行

如果一个类的多个Bean使用相同的名称,那么程序执行不会报错,但是第一个Bean之后的对象不会放到容器中,也就是只有第一次创建Bean的时候会将对称的Bean 名称关联起来,后续再有相同名称Bean,容器会自动忽略 

2.3.2 名称不同 

对于同种类型的Bean,而名称不同时,仍可注入到容器中

三. 获取Bean 对象

对于之前我们获取 Bean 对象的方法,是通过依赖查找的方法,先获取上下文对象(容器对象)context,然后在通过getBean来获取对象的实例.对此,似乎还没有直接new来的快.因此我们还可以通过对象(依赖)注入的方式来获取对象的实例.

3.1 属性注入

@Repository
public class UserResposity {private int num;public int add(){System.out.println("Do UserResposity method");return num;}
}@Service
public class UserService {@AutowiredUserResposity userResposity;public int add(){System.out.println("Do UserService method.");return userResposity.add();}
}//使用测试验证对象是否被注入
class UserServiceTest {@Testpublic void test1(){//此处只是为了验证UserService中的UserResposity是否被注入了对象ApplicationContext context= new ClassPathXmlApplicationContext("spring-config.xml");UserService userService=context.getBean("userService",UserService.class);userService.add();}
}

上述代码该类型的对象在spring容器中只有一个,那若有多个呢?

public class User {private String name;public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Users{" +"name='" + name + '\'' +'}';}
}@Component
public class Users {@Bean("user1")public User user1(){User user=new User();user.setName("张三");return user;}@Bean("user2")public User user2(){User user=new User();user.setName("李四");return user;}}@Service
public class UserService2 {@AutowiredUser user;public void sayHi(){System.out.println(user.toString());}
}class UserService2Test {@Testpublic void test(){ApplicationContext context= new ClassPathXmlApplicationContext("spring-config.xml");UserService2 userService2=context.getBean("userService2",UserService2.class);userService2.sayHi();}
}//available: expected single matching bean but found 2: user1,user2

依赖注入 vs 依赖查找

依赖查找依赖Bean的名称

@Autowried 依赖注入流程:首先先根据 getType (从spring容器)获取对象,如果只获取一个,那么直接将此对象 注入到当前属性中;如果获取到多个对象,才会使用getName(根据名称)进行匹配。

 此时我们发现会报错,因为找到了两个该类型的对象。此时我们可以通过以下几种方法解决:

将属性名成改为Bean 储存在spring容器中对应的名称

    @AutowiredUser user2;User user1;

使用注解  @Qualifier

    @Autowired@Qualifier("user2")User user;

优点:使用简单

缺点:

无法注入final 修饰的属性 

只适用于IoC容器

3.2 Setter 注入

@Service
public class SetterUserService {private UserResposity userResposity;@Autowiredpublic void setRepository(UserResposity repository){this.userResposity=repository;}public void sayHi(){userResposity.add();}
}@Testpublic void test() {ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");SetterUserService service = context.getBean("setterUserService", SetterUserService.class);service.sayHi();}

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

缺点:

无法注入一个final 修饰的变量

setter 注入的对象可以被修改,setter 本来就是一个方法,既然是一个方法,就有可能被多次调用和修改。

3.3 构造方法注入

@Service
public class ConstructorService {//可以注入final修饰的属性private final UserResposity userResposity;
//    private UserResposity userResposity;//如果当前类中只存在一个构造方法时,@Autowired可以省略@Autowired//若容器中有多个UserResposity时,可以通过参数列表中的变量名改为容器中对应的名字public ConstructorService(UserResposity userResposity) {this.userResposity = userResposity;}public void sayHi(){userResposity.add();}}@Testpublic void test() {ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");ConstructorService service = context.getBean("constructorService", ConstructorService.class);service.sayHi();}

优点:

可以注入一个final 修饰的变量

注入的对象不会被改变(构造方法只调用一次)

构造方法可以保证注入对象完全初始化

通用性更好(不依赖IoC)

四. @Resource

@Service
public class UserService2 {@Resource(name = "user2")User user;public void sayHi(){System.out.println(user.toString());}
}class UserService2Test {@Testpublic void test(){ApplicationContext context= new ClassPathXmlApplicationContext("spring-config.xml");UserService2 userService2=context.getBean("userService2",UserService2.class);userService2.sayHi();}
}

@Autowried与@Resource 的区别:

出身不同:@Resource 来自于JKD;@Autowired来自于Spring

支持参数不同:@Resource 支持的参数有很多,@Autowired只支持一个参数

使用上不同:@Resource 不支持构造方法注入;而@Autowired 支持

idea 兼容性支持不同:使用@Atowired 在idea 专业版下可能误报(required 设置为false表示该属性可不被注入,可以通过设置为false来解决)

五. Bean 作用域

案例:

@Service
public class ScopeService1 {@Resource(name = "user1")private User user;@Resource(name = "user1")private User user2;public void print(){user.setId(222);user2.setId(333);System.out.println("user1:"+user.toString());System.out.println("user2:"+user2.toString());}
}@Testpublic void Test(){ApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml");ScopeService1 service1=context.getBean("scopeService1",ScopeService1.class);service1.print();}

我们想要两个user属性有不同的id,但是设置完后发现两个user的id相同。

原因分析:操作以上问题的原因是因为Bean 默认情况下是单例状态,也就是所有人使用的都是同一个对象。单例模式可以很大程度上提高性能,所以在Spring中Bean的作用域默认也是 singleton单例模式

解决方案:在bean对象放入Spring 容器中时,指定作用域

    @Bean("user1")@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)public User user1(){User user=new User();user.setId(1);user.setName("张三");return user;}

5.1 作用域定义 

Bean 的作用域是指 Bean 在 Spring 整个框架中的某种行为模式,比如 singleton 单例作用域,就表示 Bean 在整个 Spring 中只有一份,它是全局共享的,那么当其他人修改了这个值之后,那么另一个人读取到的就是被修改的值。

5.2 Bean 的6 中作用域

sigleton

• 描述:该作用域下的Bean在IoC 中只存在一个实例:获取Bean及装配Bean都是同一个对象

• 场景:通常无状态的Bean使用该作用域。无状态表示Bean对象的属性不需要更新

• 备注:Spring 默认选择该作用域

prototype

• 描述:每次对该作用域下的Bean 的请求都会创建新的实例:获取Bean及装配Bean都是新的对象

• 场景:通常有状态的Bean使用该作用域

request

描述:每次 http 请求都会创建新的Bean实例,类似于prototype

场景:一次 http 请求和响应的共享Bean

备注:限定 SpringMVC 中使用

session

描述:在一个 http session 中定义一个Bean实例

场景:用户会话的共享Bean,比如:记录一个用户的登录信息

备注:限定SpringMVC中使用

application

描述:在一个 http servlet Context 中,定义一个Bean实例

场景:Web 应用的上下文信息,比如:记录一个应用的共享信息

备注:限定在SpringMVC中使用

websocket

描述:在一个http websocket 的生命周期中,定义一个Bean实例

场景:WebSocket 的每次会话中,保存了一个Map 结构的头信息,将用来包裹客户端消息头。第一次初始化后,直到 WebSocket 结束都是同一个Bean

备注:限定Spring WebSocket 中使用

六. Spring 执行流程和 Bean 的生命周期

6.1 Spring 的执行流程

Spring 生命周期:

• 启动容器

• 读取配置进行 Bean 初始化

• 将 Bean 加入到容器中

• 装配 Bean 属性(给当前类的属性ID进行注入)

6.2 Bean 的生命周期

1. 实例化(内存空间分配)

2. 设置Bean 属性 (进行依赖注入,将依赖的Bean 赋值到当前类的属性上)

3. Bean的初始化

• 实现了各种 Aware 通知的方法,如 BeanNameAware、BeanFactoryAware、ApplicationContextAware 的接口方法;

• 执行 BeanPostProcessor初始化的前置方法

• 执行 @PostConsturct 初始化方法,依赖注入操作之后被执行

• 执行 BeanPostProcessor 初始化后置方法

@Component
public class BeanLifeComponent implements BeanNameAware {@Overridepublic void setBeanName(String name) {System.out.println("执行了 setBeanName 方法:"+name);}
//  初始化方法@PostConstructpublic void postConsturct(){System.out.println("执行了 postConsturct 方法");}//这个方法是和初始化方法一样的,只是这里需要在xml文件中声明public void myInit(){System.out.println("执行了 myInit 方法");}public void use(){System.out.println("使用Bean");}@PreDestroypublic void preDestroy(){System.out.println("执行了 preDestroy 方法");}
}public static void main(String[] args) {//这里不要使用ApplicationContext,因为它没有close方法ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml");BeanLifeComponent beanLife=context.getBean("myBean",BeanLifeComponent.class);//使用BeanbeanLife.use();//此时的上下文对象若不关闭,是不会执行 preDestroy 方法的context.close();
}

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"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"><!--这里的init-method 的名字和我们代码中初始化方法名字要一致--><bean id="myBean" class="BeanLifeComponent" init-method="myInit"></bean>
</beans>

4. 使用Bean

5. 销毁Bean

销毁容器的各种方法,如@PreDestroy、DisposableBean 接口方法、destroy-method。

思考:为什么要先设置属性在进行初始化呢

 因为我的初始化方法在执行的过程中可能会用到我这个类中的某个属性,如果我不先进行属性的赋值,那么在执行到使用属性的这行代码时一定会报错

这篇关于Spring的使用-Bean对象的储存和获取/Bea对象的作用域与生命周期的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/541907

相关文章

Java图片压缩三种高效压缩方案详细解析

《Java图片压缩三种高效压缩方案详细解析》图片压缩通常涉及减少图片的尺寸缩放、调整图片的质量(针对JPEG、PNG等)、使用特定的算法来减少图片的数据量等,:本文主要介绍Java图片压缩三种高效... 目录一、基于OpenCV的智能尺寸压缩技术亮点:适用场景:二、JPEG质量参数压缩关键技术:压缩效果对比

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

springboot+dubbo实现时间轮算法

《springboot+dubbo实现时间轮算法》时间轮是一种高效利用线程资源进行批量化调度的算法,本文主要介绍了springboot+dubbo实现时间轮算法,文中通过示例代码介绍的非常详细,对大家... 目录前言一、参数说明二、具体实现1、HashedwheelTimer2、createWheel3、n

使用Python实现一键隐藏屏幕并锁定输入

《使用Python实现一键隐藏屏幕并锁定输入》本文主要介绍了使用Python编写一个一键隐藏屏幕并锁定输入的黑科技程序,能够在指定热键触发后立即遮挡屏幕,并禁止一切键盘鼠标输入,这样就再也不用担心自己... 目录1. 概述2. 功能亮点3.代码实现4.使用方法5. 展示效果6. 代码优化与拓展7. 总结1.

使用Python开发一个简单的本地图片服务器

《使用Python开发一个简单的本地图片服务器》本文介绍了如何结合wxPython构建的图形用户界面GUI和Python内建的Web服务器功能,在本地网络中搭建一个私人的,即开即用的网页相册,文中的示... 目录项目目标核心技术栈代码深度解析完整代码工作流程主要功能与优势潜在改进与思考运行结果总结你是否曾经

Java利用docx4j+Freemarker生成word文档

《Java利用docx4j+Freemarker生成word文档》这篇文章主要为大家详细介绍了Java如何利用docx4j+Freemarker生成word文档,文中的示例代码讲解详细,感兴趣的小伙伴... 目录技术方案maven依赖创建模板文件实现代码技术方案Java 1.8 + docx4j + Fr

SpringBoot首笔交易慢问题排查与优化方案

《SpringBoot首笔交易慢问题排查与优化方案》在我们的微服务项目中,遇到这样的问题:应用启动后,第一笔交易响应耗时高达4、5秒,而后续请求均能在毫秒级完成,这不仅触发监控告警,也极大影响了用户体... 目录问题背景排查步骤1. 日志分析2. 性能工具定位优化方案:提前预热各种资源1. Flowable

Linux中的计划任务(crontab)使用方式

《Linux中的计划任务(crontab)使用方式》:本文主要介绍Linux中的计划任务(crontab)使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、前言1、linux的起源与发展2、什么是计划任务(crontab)二、crontab基础1、cro

kotlin中const 和val的区别及使用场景分析

《kotlin中const和val的区别及使用场景分析》在Kotlin中,const和val都是用来声明常量的,但它们的使用场景和功能有所不同,下面给大家介绍kotlin中const和val的区别,... 目录kotlin中const 和val的区别1. val:2. const:二 代码示例1 Java

C++变换迭代器使用方法小结

《C++变换迭代器使用方法小结》本文主要介绍了C++变换迭代器使用方法小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1、源码2、代码解析代码解析:transform_iterator1. transform_iterat