Spring进阶学习 02、Bean的定义与Spring容器

简介: Spring进阶学习 02、Bean的定义与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:默认类名及首字母小写或自定义。

实现FactoryBean接口,通过其中的getObject()方法来返回bean对象。

name:默认类名及手写字母小写或自定义。

直接通过容器进行注册指定类,并提供一个函数式接口Suppiler,通过其中的get()方法返回bean对象。

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"));
}


相关文章
|
2月前
|
搜索推荐 JavaScript Java
基于springboot的儿童家长教育能力提升学习系统
本系统聚焦儿童家长教育能力提升,针对家庭教育中理念混乱、时间不足、个性化服务缺失等问题,构建科学、系统、个性化的在线学习平台。融合Spring Boot、Vue等先进技术,整合优质教育资源,提供高效便捷的学习路径,助力家长掌握科学育儿方法,促进儿童全面健康发展,推动家庭和谐与社会进步。
|
2月前
|
NoSQL 算法 Redis
【Docker】(3)学习Docker中 镜像与容器数据卷、映射关系!手把手带你安装 MySql主从同步 和 Redis三主三从集群!并且进行主从切换与扩容操作,还有分析 哈希分区 等知识点!
Union文件系统(UnionFS)是一种**分层、轻量级并且高性能的文件系统**,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem) Union 文件系统是 Docker 镜像的基础。 镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
504 5
|
9月前
|
监控 Java 应用服务中间件
微服务——SpringBoot使用归纳——为什么学习Spring Boot
本文主要探讨为什么学习Spring Boot。从Spring官方定位来看,Spring Boot旨在快速启动和运行项目,简化配置与编码。其优点包括:1) 良好的基因,继承了Spring框架的优点;2) 简化编码,通过starter依赖减少手动配置;3) 简化配置,采用Java Config方式替代繁琐的XML配置;4) 简化部署,内嵌Tomcat支持一键式启动;5) 简化监控,提供运行期性能参数获取功能。此外,从未来发展趋势看,微服务架构逐渐成为主流,而Spring Boot作为官方推荐技术,与Spring Cloud配合使用,将成为未来发展的重要方向。
364 0
微服务——SpringBoot使用归纳——为什么学习Spring Boot
|
6月前
|
安全 Java 数据库
Spring Boot 框架深入学习示例教程详解
本教程深入讲解Spring Boot框架,先介绍其基础概念与优势,如自动配置、独立运行等。通过搭建项目、配置数据库等步骤展示技术方案,并结合RESTful API开发实例帮助学习。内容涵盖环境搭建、核心组件应用(Spring MVC、Spring Data JPA、Spring Security)及示例项目——在线书店系统,助你掌握Spring Boot开发全流程。代码资源可从[链接](https://pan.quark.cn/s/14fcf913bae6)获取。
1046 2
|
8月前
|
Java Spring
Spring框架的学习与应用
总的来说,Spring框架是Java开发中的一把强大的工具。通过理解其核心概念,通过实践来学习和掌握,你可以充分利用Spring框架的强大功能,提高你的开发效率和代码质量。
204 20
|
XML Java 数据格式
Spring容器Bean之XML配置方式
通过对以上内容的掌握,开发人员可以灵活地使用Spring的XML配置方式来管理应用程序的Bean,提高代码的模块化和可维护性。
384 6
|
安全 Java 开发者
Spring容器中的bean是线程安全的吗?
Spring容器中的bean默认为单例模式,多线程环境下若操作共享成员变量,易引发线程安全问题。Spring未对单例bean做线程安全处理,需开发者自行解决。通常,Spring bean(如Controller、Service、Dao)无状态变化,故多为线程安全。若涉及线程安全问题,可通过编码或设置bean作用域为prototype解决。
297 1
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
317 9
|
前端开发 Java Docker
使用Docker容器化部署Spring Boot应用程序
使用Docker容器化部署Spring Boot应用程序
|
Java Kotlin 索引
学习Spring框架特性及jiar包下载
Spring 5作为最新版本,更新了JDK基线至8,修订了核心框架,增强了反射和接口功能,支持响应式编程及Kotlin语言,引入了函数式Web框架,并提升了测试功能。Spring框架可在其官网下载,包括文档、jar包和XML Schema文档,适用于Java SE和Java EE项目。
299 0