Spring进阶学习 02、Bean的定义与Spring容器
@[toc]
前言
本篇博客是在学习 图灵学院-SPRING源码教程 笔记,若文章中出现相关问题,请指出!
所有博客文件目录索引:博客目录索引(持续更新)
一、认识对象、JavaBean以及SpringBean的区别
基础概念
普通对象
:属性公开化public。
-
public class User { public String name; //属性为公开,获取与设置属性可以直接通过属性进行 } new User(); //使用new创建对象
javabean
:属性是私有的,提供了set/get方法。访问与获取某个属性只能通过方法来进行。这种方式肯定比普通的对象兼容性会更好。不允许调用者直接访问属性。
-
public class User { private String name; //属性为私有 //获取与设置属性都需要通过方法来进行,兼容性更好,不允许调用者直接访问属性 public String getName() { return name; } public void setName(String name) { this.name = name; } } new User(); //使用new创建对象
SpringBean
:首先需要引入Spring的依赖包,通过Spring框架来帮我们生成的对象!其中包含了一整套的Bean生命周期。
-
//1、使用注解(@Bean、@Component...)、xml形式来定义Bean,通过BeanDefinition来进行定义Bean的结构 new ClassPathXmlApplicationContext("spring.xml"); //加载spring的xml文件来让spring容器进行Bean的实例化 或 new AnnotationConfigApplicationContext(SpringConfig.class) //加载spring的注解配置类来进行Bean的实例化
总结:
- 普通对象与JavaBean都是通过我们自己手动new来创建的对象。
- Spring Bean本质是通过反射来创建对象,此时并不是我们来进行创建,而是交由Spring框架来帮我们创建。
二、Bean的定义方式
介绍声明式与编程式定义方式
四种方式
①声明式定义:包含xml配置(<bean/>
)、@Component、@Bean方式。
- 其中
@Bean
方式更加的灵活的进行一些配置操作,相较于xml配置更快捷(xml配置会有个读取配置文件操作)。
②编程式定义:自定义BeanDefinition。
③利用FactoryBean方式:实现FactoryBean接口,其中的getObjet()来创建bean实例。
④使用Suppiler方式:通过其函数式接口的getObject()方法创建bean实例。
总结:其中声明式定义的底层实现都是基于通过BeanDefinition来进行定义的。
①声明式创建Bean
@Bean注解
单独使用:
public class Config { //被ApplicationContext加载时也创建Bean,name为config
//定义了@Bean注解,此时new User()的Bean对象的name为user1。(根据方法名)
@Bean
public User user1(){
return new User();
}
}
测试类:
public class Main{
public static void main(String[] args) throws IOException {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);
//遍历查看容器中的Bean的name
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
System.out.println("获取的实例如下:");
Object bean = applicationContext.getBean("config");
System.out.println(bean);
Object bean1 = applicationContext.getBean("user1");
System.out.println(bean1);
}
说明:Config类被指定ApplicationContext加载,实际上创建了两个Bean实例。
②编程式创建Bean(利用BeanDefinition)
首先定义一个User类:
public class User {
}
编程式方式来创建Bean对象,通过BeanDefinition来创建:
public class Main{
public static void main(String[] args) throws IOException {
//AnnotationConfigApplicationContext由于实现了GenericApplicationContext才有的注册BeanDefinition方法
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();//Spring容器
//1、获取并设置BeanDefinition对象
//获取一个BeanDefinition(通过一个工具类来获取,实际上内部就是创建了一个AbstractBeanDefinition),仅仅比直接创建一个AbstractBeanDefinition多了一个验证操作。
//但由于AbstractBeanDefinition的构造器是protected,所以无法在不同包非子类中进行手动new创建!
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(User.class);
//beanDefinition.setBeanClassName("user1");//设置bean的name
//2、使用容器对象来进行注册与初始化bean对象
//进行注册
applicationContext.registerBeanDefinition("user1",beanDefinition);
applicationContext.refresh(); //真正进行初始化过程,也就是初始化到容器中
//测验:测试容器中是否有对应的bean对象!!!!
Object bean = applicationContext.getBean("user1");
System.out.println(bean);
}
}
额外说明:在这个AbstractBeanDefinition
类中你可以定义一系列的对于要创建Bean
的规范,如是否进行懒加载?单例or多实例?初始化方法?销毁方法?等等,其中你在xml或者注解或者编程式方式中配置的信息对应着BeanDefinition
中的属性。
③通过实现FactoryBean来创建Bean
自动配置类
@Configuration
@ComponentScan("xyz.changlu") //自动扫描xyz.changlu包目录下
public class SpringConfig {
}
User
类:下面自定义FactoryBean实际创建得到的一个bean
public class User {
}
自定义FactoryBean类:
@Component("clfb") //这里设置name为clfb。(若是没有自定义参数,就会将类名及其首字母小写作为bean的name名称 )
public class ChangluFactoryBean implements FactoryBean { //实现FactoryBean接口,并重写两个方法
//返回的实际bean对象,其name为clfb
@Override
public Object getObject() throws Exception {
return new User();
}
//返回bean对象的类型
@Override
public Class<?> getObjectType() {
return User.class;
}
}
测试类:
public class Main{
public static void main(String[] args) throws IOException {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
//FactoryBean实现类会创建两个Bean对象
//获取到ChangluFactoryBean创建的bean对象
Object bean = applicationContext.getBean("clfb");
System.out.println(bean);
//获取到ChangluFactoryBean这个bean对象
Object clfb = applicationContext.getBean("&clfb");
System.out.println(clfb);
}
}
运行测试类得到以下结果:
总结:对于上面的FactoryBean
实际上对应了两个Bean
对象
beanName
为"clfb",bean
对象为getObject
方法所返回的User
对象。(底层并不是Spring容器来创建的对象,而是通过调用getObject()方法来创建的Bean对象)beanName
为"&clfb",bean
对象为ChangluFactoryBean
类的实例对象。
④通过提供Suppiler接口方式来创建Bean
思路:通过容器直接进行注册,所需两个条件①指定类的Class。②Suppiler函数式接口,返回要创建Bean的对象。
待创建Bean对象User类:
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试类:通过容器来进行注册
public class Main{
public static void main(String[] args) throws IOException {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//1、容器注册与实例化Bean
//通过容器来进行注册Bean,并传入一个函数式接口(bean实例通过get()方法获取)
context.registerBean(User.class, new Supplier<User>() {
@Override
public User get() { //本质是调用get()方法获取到实例
User user = new User();
user.setName("changlu"); //进行初始化方法
return user;
}
});
context.refresh();//进行实例化
//2、根据name(user)获取到指定Bean对象
User bean = context.getBean("user",User.class);
System.out.println(bean.getName());
}
}
总结
Spring中有如下几种定义bean对象的方式:
- 声明式创建:即使用xml配置(
<bean/>
)或注解形式如@Component
、@Bean
。@Component
:创建bean的name为类名及首字母小写。也可以进行自定义name。@Bean
:其bean的name为方法名。
- 编程式创建:手动创建
BeanDefinition
,并手动在容器中进行注册与初始化。(声明式创建底层实际就是使用BeanDefinition)- name:默认
类名及首字母小写
或自定义
。
- name:默认
- 实现
FactoryBean
接口,通过其中的getObject()方法来返回bean对象。- name:默认
类名及手写字母小写
或自定义
。
- name:默认
- 直接通过容器进行注册指定类,并提供一个函数式接口Suppiler,通过其中的get()方法返回bean对象。
- name:默认
类名及手写字母小写
或自定义
。
- name:默认
说明:我们对于业务最多的就是使用前两种。
三、Spring容器
Spring容器中存的就是Bean。
3.1、Spring容器中并不是仅仅只是一个单例池(介绍单例与原型)
介绍单例Bean、单例池、单例模式
单例Bean
:需要设置scope="singleton"
,即通过指定name获取的bean对象永远是单例的,其存储在一个单例池中,对应使用了单例模式。
- 若是设置
scope="prototype"
,那么每次通过name获取到的bean对象不是同一个对象,每次获取都会创建一个新的bean实例。
单例池:底层使用的是ConcurrentHashMap,使用的变量名为singleObject,存储的键值对为beanName与object(即单例对象),默认情况下是非懒加载单例Bean(IOC容器已启动就创建Bean实例)。
测试scope="singleton"与scope="prototype"的区别
①创建一个User对象
public class User {
}
②编写xml配置文件,配置两个User的Bean实例
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<!-- 默认是scope="singleton",表示单例 -->
<bean id="user" class="xyz.changlu.User"/>
<!-- 设置是scope="prototype",表示原型,每个通过user1获取就会创建一个实例 -->
<bean id="user1" class="xyz.changlu.User" scope="prototype"/>
</beans>
③进行测试,得出结论:容器中并不仅仅存在一个单例池
public class Main{
public static void main(String[] args) throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
//获取单例Bean(name为user的就是单例Bean)
System.out.println(context.getBean("user", User.class));
System.out.println(context.getBean("user", User.class));
//获取到的是原型Bean(每次获取name为user1的bean都是不同实例)
System.out.println(context.getBean("user1", User.class));
System.out.println(context.getBean("user1", User.class));
}
}
3.2、认识BeanFactory
BeanFactory
:是一个Bean工厂,能够进行创建与获取Bean。主要能够创建对象以及BeanDefinition。
实操
接下来我们手动使用BeanFactory
来进行创建与获取Bean
对象:
①准备一个Bean类
public class User {
}
②BeanFactory能够以两种形式来定义Bean对象
public class Main{
public static void main(String[] args) throws IOException {
//创建一个BeanFactory,其拥有创建Bean与获取Bean的功能
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
//方式一:注册对象(默认是单例的,存储到单例池中)
//beanFactory.registerSingleton("user",new User());
//方式二:注册指定的BeanDefinition
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(User.class);//设置指定创建的Class
//注册指定的BeanDefinition,其name为user(默认也是单例的,存储到单例池中)
beanFactory.registerBeanDefinition("user",beanDefinition);
//测试
User user = beanFactory.getBean("user", User.class);
User user1 = beanFactory.getBean("user", User.class);
System.out.println(user);
System.out.println(user1);
}
}
第一种方式是直接注册指定的单例对象,这个对象可以直接进行new的方式传入。
第二种方式是创建一个BeanDefinition,接着使用BeanFactory来进行注册该BeanDefinition。
总结:这两种方式来让BeanFactory创建bean对象默认都是单例形式的。
源码:查看BeanFactory中的单例池
首先看一下DefaultListableBeanFactory
的继承关系图:
单例池的对象定义在该类继承的DefaultSingletonBeanRegistry
类中
3.3、ApplicationContext
3.3.1、ApplicationContext与BeanFactory关系
ApplicationContext与BeanFactory两个接口关系
可以看到实际上ApplicationContext也是一个BeanFactory,并且其除了能够生产bean之外还具有其他额外的功能如事件发布、资源加载、国际化资源以及获取环境资源等。
3.3.2、AnnotationConfigApplicationContext使用
这里拿AnnotationConfigApplicationContext来进行示例,展示一下其具有的功能,相对于ApplicationContext其扩展了其他功能如上面黄色框中提供的方法,下面是拿一些功能方法来测试,感受一下其具有的其他方法`:
public static void main(String[] args) throws IOException {
//实例化一个ApplicationContext
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(User.class);
//注册BeanDefinition实例:继承DefaultListableBeanFactory实现BeanDefinitionRegistry得到的方法
context.registerBeanDefinition("user",beanDefinition);
context.refresh();
//拿到操作系统级别的环境变量(也就是windows设置的环境变量)
System.out.println("=====系统环境变量=====");
context.getEnvironment().getSystemEnvironment().forEach((key,value)->{
System.out.println(key+":"+value);
});
System.out.println("\n\n=====JVM环境变量=====");
context.getEnvironment().getSystemProperties().forEach((key,value)->{
System.out.println(key+":"+value);
});
//发布事件
//context.publishEvent();
//获取资源:更容易的拿到资源
System.out.println("\n\n=====获取resource目录下的资源并打印=====");
Resource resource = context.getResource("classpath:spring.xml");
InputStream is = resource.getInputStream();
byte[] data = new byte[1024];
int len;
while((len = is.read(data))!=-1){
System.out.print(new String(data,0,len));
}
is.close();
System.out.println(resource);
//国际化
// context.getMessage()
}
运行结果:
3.4、ClassPathXmlApplicationContext与FileSystem…理解
这两个ApplicationContext都可以用来加载spring.xml文件,只不过他们对应的定位路径不相同。
FileSystemXmlApplicationContext
其可以通过相对路径或绝对路径来找到spring.xml文件,需要注意的点是只有FileSystemXmlApplicationContext可以使用绝对路径的,其他不支持使用会报错!
相对路径:
public static void main(String[] args) throws IOException {
//使用相对路径时,默认路径位置在当前项目下,所以可以通过src/main/resources/spring.xml来指定文件位置
FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("src/main/resources/spring.xml");
System.out.println(context.getBean("user"));
}
绝对路径:
//绝对定位
FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("F:\\03、框架源码调试demo\\02、spring-demo\\src\\main\\resources\\spring.xml");
ClassPathXmlApplicationContext
其定位在编译项目之后的target目录下:
//直接写配置文件名称就能够进行定位到
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
System.out.println(applicationContext.getBean("user"));
3.5、refresh()介绍(刷新)
介绍可刷新与不可刷新
对于ClassPathXmlApplicationContext、FileSystemXmlApplicationContext是可以进行调用refresh()方法进行刷新的,而对于AnnotationConfigApplicationContext
使用该方法时就会出现报错异常:
原因:
对于spring-mvc依赖包中的AnnotationConfigWebApplicationContext
是可以支持刷新的,并且其还可以通过xml文件配置的方式来配置bean:
refresh()
用途:有相当于重启的动作。会去重新解析对应的spring.xml
配置文件。此时之前创建的bean实例对象就会全部进行销毁再重新创建生成了一份。
示例即证明:
public static void main(String[] args) throws IOException {
FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("src/main/resources/spring.xml");
System.out.println(context.getBean("user"));
context.refresh();//刷新spring容器,重新加载spring.xml文件,销毁之前所有的bean实例,重新生成新的bean实例
System.out.println(context.getBean("user"));
}
- 点赞
- 收藏
- 关注作者
评论(0)