Spring Bean、XML方式Bean配置、Bean实例化配置、Bean注入

Bean管理

一、SpringBoot Bean 初了解

先看一下SpringbootBean是怎么个情况,再看一下SpringBean的配置

1.1 了解

默认情况下,Spring项目启动时,会把Bean都创建好放在IOC容器中,如果主要获取这些Bean,可以通过如下方式:

  • 根据name获取bean: Object getBean(String name)
  • 根据类型获取bean: <T> T getBean(Class <T> requiredType)
  • 根据name获取bean(带类型转换): <T> T getBean(String name,Class <T> requiredType)
//  IOC容器对象
    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void testBean(){
//      TODO  根据bean名称获取   若没指定bean名称,默认类名首字母小写
        DeptController deptControllerBean1 =(DeptController) applicationContext.getBean("deptController");
        System.out.println(deptControllerBean1); //com.zhangjingqi.controller.DeptController@249b54af

//      TODO 根据bean的类型获取
        DeptController deptControllerBean2 = applicationContext.getBean(DeptController.class);
        System.out.println(deptControllerBean2);//com.zhangjingqi.controller.DeptController@249b54af

//      TODO 根据bean的名称 及 类型获取
        DeptController deptControllerBean3 = applicationContext.getBean("deptController",DeptController.class);
        System.out.println(deptControllerBean3);//com.zhangjingqi.controller.DeptController@249b54af

    }

上述所说的【Spring项目启动时,会把其中的bean创建好】还会受到作用域及延迟初始化影响,这里主要针对于默认的单例非延迟加载的bean而言。

1.2 Bean的作用域

Spring支持五中作用域,后三种在Web环境下才生效

作用域说明
singleton容器内同名称的bean只有一个实例(单例)(默认)
prototype每次使用该bean时会创建新的实例(非单例)
request每个请求范围内会创建新的实例(Web环境中,了解)
session每个会话范围内会创建新的实例(Web环境中,了解)
application每个应用范围内会创建新的实例(Web环境中,了解)

singleton模式下bean对象情况,并且在容器启动的时候创建好的

@Test
public void testScope(){
    for (int i=0;i<10;i++){
        DeptController deptControllerBean2 = applicationContext.getBean(DeptController.class);
        System.out.println(deptControllerBean2);
    }
}

com.zhangjingqi.controller.DeptController@586728e8
com.zhangjingqi.controller.DeptController@586728e8
com.zhangjingqi.controller.DeptController@586728e8
com.zhangjingqi.controller.DeptController@586728e8
com.zhangjingqi.controller.DeptController@586728e8
com.zhangjingqi.controller.DeptController@586728e8
com.zhangjingqi.controller.DeptController@586728e8
com.zhangjingqi.controller.DeptController@586728e8
com.zhangjingqi.controller.DeptController@586728e8
com.zhangjingqi.controller.DeptController@586728e8

我们也可以在第一次使用的时候实例化 @Lazy

​ 代表延迟初始化,直到第一次使用的时候

@Lazy //
@RestController
@Slf4j
public class DeptController {...}

通过@Scope注解来进行配置作用域

@Scope("prototype")
@RestController
@Slf4j
public class DeptController {}

com.zhangjingqi.controller.DeptController@7f6b57f2
com.zhangjingqi.controller.DeptController@144ee8a7
com.zhangjingqi.controller.DeptController@52b32b70
com.zhangjingqi.controller.DeptController@18c820d2
com.zhangjingqi.controller.DeptController@3d3930fe
com.zhangjingqi.controller.DeptController@5e51ec2e
com.zhangjingqi.controller.DeptController@15f2a43f
com.zhangjingqi.controller.DeptController@4c65d8e3
com.zhangjingqi.controller.DeptController@382faf51
com.zhangjingqi.controller.DeptController@69ce14e6

1.2.1 注意事项

  • 默认singleton的bean,在容器启动时被创建,可以使用@Lazy注解来延迟初始化(延迟到第一次使用时)
  • prototype,每一次使用该bean的时候都会创建一个新的实例
  • 实际开发当中,绝大部分的bean是单例的,也就是说绝大部分的bean不需要scope属性的

1.3 第三方Bean

​ 项目开发中,自己开发的类使用@Component以及其三个衍生注解@Controller、@Service、@Repository注入即可

但是还有一种情况是第三方提供的,比如依赖

比如:dom4j就是第三方组织提供的。 dom4j中的SAXReader类就是第三方编写的。

​ 如果我想将SAXReader对象存入到容器,需要在SAXReader类添加@Component注解,但是这是第三方bean,我们是无法修改的是不能添加注解的。

        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.3</version>
        </dependency>

如果要管理的bean对象来自于第三方(不是自定义的),是无法用@Component及衍生注解生命bean的,此时需要用到@Bean注解

​ 启动类也是配置类,我们完全可以在这里进行注入

//Filter是javaweb三大组件之一,不是Spring提供的,如果想要使用三大组件,需要添加这个注解
@ServletComponentScan
@SpringBootApplication
public class SpringbootWebApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootWebApplication.class, args);
    }
    //声明第三方bean
    @Bean //将当前方法的返回值对象交给IOC容器管理, 成为IOC容器bean
    public SAXReader saxReader(){
        return new SAXReader();
    }
}

测试是否可以

​ 创建xml文件,解析下面内容

<?xml version="1.0" encoding="UTF-8"?>
<emp>
    <name>Tom</name>
    <age>18</age>
</emp>

挺完美的

@Autowired
private SAXReader saxReader;
@Test
public void testBean2() throws DocumentException {
    Document document = saxReader.read(this.getClass().getClassLoader().getResource("1.xml")
            );

    Element rootElement = document.getRootElement();
    String name = rootElement.element("name").getText();
    String age = rootElement.element("age").getText();
    System.out.println(name + " : " + age); // Tom : 18
}

​ 但是在Spring项目中,我们一般会保证启动类的纯粹性,让启动类仅仅是启动类,我们把其他的配置单独列出来

@Configuration //配置类 (在配置类当中对第三方bean进行集中的配置管理)
public class CommonConfig {
    //声明第三方bean
    @Bean //将当前方法的返回值对象交给IOC容器管理, 成为IOC容器bean
//通过@Bean注解的name/value属性指定bean名称, 如果未指定, 默认是方法名
    public SAXReader reader(DeptService deptService) {
        System.out.println(deptService);
        return new SAXReader();
    }
}




二、 基于XML方式Bean的配置

2.1 SpringBean配置概览

XML配置方式功能描述
<bean id=“” class=“”>Bean的id和全限定名配置
<bean name=“”>通过name设置Bean的别名,通过别名也能直接获取到Bean实例
<bean scope=“”>Bean的作用范围,BeanFactory作为容器时取值singleton和prototype
<bean lazy-init=“”>Bean的实例化时机,是否延迟加载。BeanFactory作为容器时无效
<bean init-method=“”>Bean实例化后自动执行的初始化方法,method指定方法名
<bean destroy-method=“”>Bean实例销毁前的方法,method指定方法名
<bean autowire=“byType”>设置自动注入模式,常用的有按照类型byType,按照名字byName
<bean factory-bean=“” factory-method=“”/>指定哪个工厂Bean的哪个方法完成Bean的创建

2.2 bean id class 配置

配置UserServiceImpl由Spring容器负责管理

    <!--class 可以明确Bean在哪-->
    <!--id 为Bean做一个唯一标识-->
    <bean id="userService" class="com.zhangjingqi.service.impl.UserServiceImpl">
    </bean>
  • id

    唯一标识,不能与其他bean重复

    我们最终要getBean,getBean的参数其实是beanName,并不是id,但是当bean对象进入到容器后,会将id转化成beanName进行存储

applicationContext.getBean("userService");

假如说我们不配置id,会出现什么情况?

这个Bean依然有BeanName,如下所示

  <bean class="com.zhangjingqi.service.impl.UserServiceImpl">
    </bean>

如果不配置id,则Spring会把当前Bean实例的全限定名作为beanName

applicationContext.getBean("com.zhangjingqi.service.impl.UserServiceImpl");

2.3 bean name 别名配置

我们一般都是配置id,别名几乎不适用

可以为当前Bean指定多个别名,根据别名也可以获得Bean对象

<bean id="userService" name="aaa,bbb" class="com.zhangjingqi.service.impl.UserServiceImpl"/>

通过下面的方式都可以获取上面的Bean

applicationContext.getBean("userService");
applicationContext.getBean("aaa");
applicationContext.getBean("bbb");

说明:BeanName就是BeanName,别名就是别名,不在一块进行存储

下面是BeanName,依然是userService,并没有出现aaa/bbb

image-20230603162503340

那为什么还能通过aaa,bbb获取到呢?

如下所示

image-20230603162747223

假如我们没有配置id,但是配置了别名,会出现什么情况?

<bean  name="aaa,bbb" class="com.zhangjingqi.service.impl.UserServiceImpl">
</property>
</bean>

如下所示,第一个别名充当BeanName

image-20230603163102292

image-20230603163008789

如果id,name都没有呢?

根据class全类名获取

2.4 bean scope 作用范围

Spring支持五中作用域,后三种在Web环境下才生效

作用域说明
singleton容器内同名称的bean只有一个实例(单例)(默认)
prototype每次使用该bean时会创建新的实例(非单例)
request每个请求范围内会创建新的实例(Web环境中,了解)
session每个会话范围内会创建新的实例(Web环境中,了解)
application每个应用范围内会创建新的实例(Web环境中,了解)
  • 默认singleton的bean,在容器启动时被创建,可以使用@Lazy注解来延迟初始化(延迟到第一次使用时)

  • prototype,每一次使用该bean的时候都会创建一个新的实例

​ 用完之后就找不到这个引用了,最终就释放了,被垃圾回收器给回收了

  • 实际开发当中,绝大部分的bean是单例的,也就是说绝大部分的bean不需要scope属性的
<!--class 可以明确Bean在哪-->
<!--id 为Bean做一个唯一标识-->
<bean id="userService" scope="prototype" class="com.zhangjingqi.service.impl.UserServiceImpl">

</bean>
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

        Object userService1 = applicationContext.getBean("userService");
        System.out.println(userService1);//com.zhangjingqi.service.impl.UserServiceImpl@43bc63a3
        Object userService2 = applicationContext.getBean("userService");
        System.out.println(userService2);//com.zhangjingqi.service.impl.UserServiceImpl@702657cc
        Object userService3 = applicationContext.getBean("userService");
        System.out.println(userService3);//com.zhangjingqi.service.impl.UserServiceImpl@6a6cb05c

2.5 bean 延迟加载

当lazy-init设置为true时为延迟加载,也就是当Spring容器创建的时候,不会立即创建Bean实例,等待用到时再创建Bean实例并存储到单例池中去,后续在使用该Bean直接从单例池获取即可,本质上该Bean还是单例的

    <bean id="userService"  class="com.zhangjingqi.service.impl.UserServiceImpl" lazy-init="true">
    </bean>

lazy-init对BeanFactory是无效的

​ 比如说lazy-init=“false”,此时BeanFactory可以直接帮我们创建嘛?

​ 这是直接不可能的!

​ 对ApplicationContext有效

2.6 bean 初始化与销毁方法配置

Bean在被实例化后,可以执行指定的初始化方法完成一些初始化的操作,Bean在销毁之前也可以执行指定的销毁方法完成一些操作

也就是说能指定Bean操作的初始化方法和销毁方法的配置

public class UserServiceImpl implements UserService {

    public void init() {
        System.out.println("Bean实例初始化方法执行");
    }
    public void destroy() {
        System.out.println("Bean实例销毁方法执行");
    }
}    

init方法先执行还是构造方法先执行?

构造方法先执行,因为构造方法的执行代表对象的创建

<bean id="userService"  init-method="init" destroy-method="destroy"
      class="com.zhangjingqi.service.impl.UserServiceImpl" </property>
</bean>

测试

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Object userService1 = applicationContext.getBean("userService");
System.out.println(userService1);

控制台只输出了“Bean实例初始化方法执行”

运行测试方法后,为什么只输出了“Bean实例初始化方法执行”,而没有输出“Bean实例销毁方法执行”?

​ 因为只有容器ApplicationContext显示关闭的时候才会销毁Bean并执行销毁方法

​ 我们程序停掉后类似于停点,容器ApplicationContext并不知道要关闭,它根本就没有来得及去执行Bean的销毁方法

要想执行销毁方法如下所示,执行applicationContext.close();语句

ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

Object userService1 = applicationContext.getBean("userService");
System.out.println(userService1);
applicationContext.close();

强调一点

Bean的销毁与Bean的销毁方法的调用不是一回事

比如说容器ApplicationContext没有了,在内存中已经挂掉了,那Bean肯定也是没有了(因为没有办法维护Bean了,所以Bean也没有了)

那Bean销毁后为什么销毁方法没被调用呢?

因为Spring还没有执行到那一步,容器就已经挂掉了,已经没办法再调用销毁方法了,所以我们要显示的关闭容器,让Spring容器执行销毁方法

applicationContext.close();

2.7 bean实现InitializingBean 接口

我们还可以通过实现 InitializingBean 接口,完成一些Bean的初始化操作

InitializingBean 接口中有一个afterPropertiesSet方法,当类实现了这个接口之后,Spring容器会自动帮你去调用此方法

afterPropertiesSet 字面意思就是在属性设置之后执行,我们测试一下试试

UserServiceImpl中方法的执行顺序我已经按照顺序拍好了,下面有测试进行验证

public class UserServiceImpl implements UserService , InitializingBean {
    public UserServiceImpl(){
        System.out.println("实例对象创建了");
    }
    
    // BeanFactory去调用该方法,从容器中获得userDao设置到此处
    public void setUserDao(UserDao userDao) {
        System.out.println("userDao实例:"+userDao);
    }
    
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean...");
    }
    
    public void init() {
        System.out.println("Bean实例初始化方法执行");
    }
    
    public void destroy() {
        System.out.println("Bean实例销毁方法执行");
    }
    
}

配置类

<!--class 可以明确Bean在哪-->
<!--id 为Bean做一个唯一标识-->
<bean id="userService"  init-method="init" destroy-method="destroy"
      class="com.zhangjingqi.service.impl.UserServiceImpl" >
    <!--name属性就是set方法的名称,并且把set去掉,首字母小写-->
    <!--ref是引用,从容器中找具体需要哪个对象-->
    <property name="userDao" ref="userDao"></property>
</bean>
<!--配置UserDao-->
<bean id="userDao" class="com.zhangjingqi.dao.impl.UserDaoImpl">

</bean>

测试类

        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

        Object userService1 = applicationContext.getBean("userService");

        applicationContext.close();

执行结果

image-20230603191447430

所以我们初始化Bean的操作有两种了,一种是在bean配置是设置一个init-method属性,并指定对应的方法,另一种方式是类实现InitializingBean类,并重写afterPropertiesSet方法

如果两种操作我都编写了,那谁会先执行?

​ afterPropertiesSet执行后再init-method属性对应的方法执行,详细的讲解会在bean的生命周期中说明

三、Bean实例化配置

Spring的实例化方式主要有下面两种

  • 构造方式实例化:底层通过构造方法对Bean进行实例化
  • 工厂方式实例化:底层通过调用自定义的工厂方法对Bean进行实例化

说明

​ 我们现在使用Spring容器是通过ApplicationContext,它帮我们调用的BeanFactory底层的对应方法,BeanFactory本身就是工厂,但是工厂内部帮我们造Bean的时候又分为两种方式,一是通过构造方式实例化,另一种是通过工厂方式实例化

​ 说白了就是工厂内部套工厂,第一层工厂就是我们的BeanFactory,第二层工厂就是需要我们进行提供了(第二种方式工厂方式实例化),第二层工厂可以产生一个Bean,最终由Spring容器帮我们管理

3.1 构造方式实例化Bean

构造方式实例化Bean又分为无参构造方法实例化和有参构造方法实例化,Spring中配置的<bean>几乎都是无参构造该方式,此处不在赘述。下面讲解有参构造方法实例化Bean

至于是有参构造还是无参构造实例化,取决于在配置文件中是怎么配置的,默认是无参构造实例化

那这么说来,默认情况下,我们设置了一个有参构造函数,没有设置无参构造函数,那是会报错的,因为默认是无参构造实例化,有了有参构造函数后就会把无参覆盖掉,此时我们需要手动添加无参构造函数

配置文件

<!--class 可以明确Bean在哪-->
<!--id 为Bean做一个唯一标识-->
<bean id="userService" class="com.zhangjingqi.service.impl.UserServiceImpl">
    
    <!--name: 指定构造方法的参数名 value:指定参数值-->
    <constructor-arg name="name" value="我叫zhangjingqi"></constructor-arg>
    <!--name属性就是set方法的名称,并且把set去掉,首字母小写-->
    <!--ref是引用,从容器中找具体需要哪个对象-->
    <property name="userDao" ref="userDao"></property>

</bean>

<!--配置UserDao-->
<bean id="userDao" class="com.zhangjingqi.dao.impl.UserDaoImpl"></bean>

UserServiceImpl类

public class UserServiceImpl implements UserService  {

    private UserDao userDao;
    // BeanFactory去调用该方法,从容器中获得userDao设置到此处
    public void setUserDao(UserDao userDao) {
        this.userDao =userDao;
    }

    public UserServiceImpl(){
        System.out.println("UserServiceImpl实例化 - 无参构造注入");
    }

    public UserServiceImpl(String name){
        System.out.println("UserServiceImpl实例化 - 有参构造注入,name="+name);
    }

}

测试程序


        //参数是一个xml配置文件
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        Object userService1 = applicationContext.getBean("userService");
//        System.out.println(userService1);
        applicationContext.close();

测试结果

image-20230603194816081

如果我们构造方法是两个参数呢?,会执行下面的哪个构造方法?

public UserServiceImpl(){
    System.out.println("UserServiceImpl实例化 - 无参构造注入");
}

public UserServiceImpl(String name){
    System.out.println("UserServiceImpl实例化 - 有参构造注入,name="+name);
}
public UserServiceImpl(String name,int age){
    System.out.println("UserServiceImpl实例化 - 有参构造注入,name="+name+",age="+age);
}
<bean id="userService" class="com.zhangjingqi.service.impl.UserServiceImpl">
    <!--name: 指定构造方法的参数名 value:指定参数值-->
    <constructor-arg name="name" value="我叫zhangjingqi"></constructor-arg>
    <constructor-arg name="age" value="22"></constructor-arg>

    <!--name属性就是set方法的名称,并且把set去掉,首字母小写-->
    <!--ref是引用,从容器中找具体需要哪个对象-->
    <property name="userDao" ref="userDao"></property>

</bean>

答案: 执行public UserServiceImpl(String name,int age)构造方法

说明

​ constructor-arg标签不仅仅可以用于构造函数的参数,只要是Bean创建时需要的参数,都可以用这个标签进行传输

​ 下面在Bean工厂时会出现另外一种使用方式

3.2 工厂方式实例化Bean

工厂方式实例化Bean,又分为三种

  • 静态工厂方法实例化Bean
    自定义一个工厂,在工厂内部自定义一个静态方法,在静态方法的内部产生一个Bean,最终这个Bean会交给Spring容器管理

  • 实例工厂方法实例化Bean

    自定义一个工厂,在工厂内部自定义一个非静态方法,在非静态方法的内部产生一个Bean,最终这个Bean会交给Spring容器管理

  • 实现FactoryBean规范延迟实例化Bean

    老师说这个一句两句说不明白,不在这里说了。

​ **静态工厂方法实例化与实例工厂方法实例化最重要的区别? **

​ 静态工厂方法实例化与实例工厂方法实例化最重要的区别就是方法是静态还是非静态

​ 静态工厂方法实例化产生Bean的时候是不需要创建对象的,直接类名调用即可

​ 实例工厂方法实例化必须需要实例化对象去调用

3.2.1 静态工厂方法实例化Bean

  • 原先是怎么产生Bean的?

    Spring容器通过全包名反射创建好对象放到容器当中

  • 现在是怎么创建Bean?

    Spring容器帮我们去调用MyBeanFactory1的静态方法userDao,最终将返回的对象存入到Spring容器之中

静态工厂

这样使用静态工厂有什么好处呢?

  • 比如我们想在创建UserDaoImpl对象之前执行一些其他的操作,我们就可以在return new UserDaoImpl();语句之前进行编写

  • 由此方法将第三方的对象注入Spring容器

public class MyBeanFactory1 {
    public static UserDao userDao(){
        return new UserDaoImpl();
    }
}

配置文件进行配置

**正常情况下**,在启动时Spring容器会按照全限定名com.zhangjingqi.factory.MyBeanFactory1从无参构造创建MyBeanFactory1对象,创建完对象放入到容器当中id为userDao1,BeanName也是id为userDao1

**但是此时有 factory-method指定方法为userDao**,此时Spring在解析时就明白了,我们不是把com.zhangjingqi.factory.MyBeanFactory1创建为对象,而是把MyBeanFactory1类中userDao的返回值作为对象,再以我们指定的id作为BeanName存储到容器当中

也就是说userDao1指定是userDao方法返回值Bean的名字/id,而不是MyBeanFactory1对象的名字/id

只需要这一个配置就行,不需要在配置中配置UserDao的Bean信息

<!--创建与之前一个样子-->
<bean id="userDao1" factory-method="userDao"
      class="com.zhangjingqi.factory.MyBeanFactory1">
</bean>

进行测试

//参数是一个xml配置文件
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Object userDao1 = applicationContext.getBean("userDao1");
System.out.println("userDao1 = "+userDao1);//userDao1 = com.zhangjingqi.dao.impl.UserDaoImpl@37883b97

经过我们的debug,发现userDao1确实是UserDaoImpl类的实例对象,并不是MyBeanFactory1类的

image-20230603210709794

3.2.2 实例工厂方法实例化Bean

与静态工厂方法实例化Bean的区别就是不是静态的而已。

工厂

这样使用实例工厂有什么好处呢?

  • 在创建实例化对象之前与之后可以进行一些其他的操作

  • 由此方法将第三方的对象注入Spring容器

public class MyBeanFactory2 {
    public UserDao userDao(){
        return new UserDaoImpl();
    }
}

配置

我们应该先创建工厂MyBeanFactory2,让Spring容器帮我们创建工厂对象,再让工厂对象调用userDao方法产生UserDaoImpl对象

<!--MyBeanFactory2对象-->
<bean id="myBeanFactory2" class="com.zhangjingqi.factory.MyBeanFactory2">
</bean>

<bean id="userDao2" factory-bean="myBeanFactory2" factory-method="userDao" >
</bean>

测试

成功获取Bean

//参数是一个xml配置文件
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Object userDao2 = applicationContext.getBean("userDao2");
System.out.println("userDao2 = "+userDao2); //userDao2 = com.zhangjingqi.dao.impl.UserDaoImpl@6913c1fb

相当的完美

image-20230603212845790

3.2.3 有参数的静态工厂和实例工厂方法

  • 静态工厂

工厂类

public class MyBeanFactory1 {

    public static UserDao userDao(String name,int age){
        System.out.println("name:"+name+",age:"+age);
        return new UserDaoImpl();
    }

}

配置文件

<bean id="userDao1" factory-method="userDao"
      class="com.zhangjingqi.factory.MyBeanFactory1">
    <constructor-arg name="name" value="我叫zhangjingqi"></constructor-arg>
    <constructor-arg name="age" value="22"></constructor-arg>
</bean>

测试

ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Object userDao1 = applicationContext.getBean("userDao1");
System.out.println("userDao1 = "+userDao1);

image-20230603213929635

  • 实例工厂

工厂类

public class MyBeanFactory2 {

    public UserDao userDao(String name,int age){
        System.out.println("name:"+name+",age:"+age);
        return new UserDaoImpl();
    }

}

配置文件

<bean id="myBeanFactory2" class="com.zhangjingqi.factory.MyBeanFactory2">
</bean>

<bean id="userDao2" factory-bean="myBeanFactory2" factory-method="userDao" >
    <constructor-arg name="name" value="我叫zhangjingqi"></constructor-arg>
    <constructor-arg name="age" value="22"></constructor-arg>
</bean>

测试类

//参数是一个xml配置文件
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Object userDao2 = applicationContext.getBean("userDao2");
System.out.println("userDao2 = "+userDao2);//userDao2 = com.zhangjingqi.dao.impl.UserDaoImpl@609cd4d8

3.2.4 实现FactoryBean规范延迟实例化Bean

相对复杂一点,这种方式并不是马上创建Bean,而是在用的时候才创建Bean

此种方式Spring底层用的稍微多一点,我们在开发中不经常使用,但是有时候面试会问

FactoryBean是一个接口,主要的作用在于定义工厂Bean实现规范的

比如说我们的一个类(这个类也能是工厂)实现了FactoryBean接口,那定义的这个类也会变成一个工厂Bean

工厂Bean的作用是造对象的,调用方法getObject()

public interface FactoryBean<T> {
    String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

    @Nullable
    T getObject() throws Exception;

    @Nullable
    Class<?> getObjectType();

    default boolean isSingleton() {
        return true;
    }
}

实现FactoryBean接口,并重写getObject方法

public class MyBeanFactory3 implements FactoryBean<UserDao> {

//  返回的Bean是谁
    public UserDao getObject() throws Exception {
        return new UserDaoImpl();
    }

//  返回Bean的类型是什么
    public Class<?> getObjectType() {
        return UserDao.class;
    }
}

配置文件

<bean id="userDao3" class="com.zhangjingqi.factory.MyBeanFactory3">
</bean>

测试程序

//参数是一个xml配置文件
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Object userDao3 = applicationContext.getBean("userDao3");
System.out.println("userDao3 = "+userDao3); //userDao3 = com.zhangjingqi.dao.impl.UserDaoImpl@305b7c14

单例对象结果图

image-20230604140334397

但是现在有一个疑问,userDao3对应的value是MyBeanFactory3,而不是UserDaoImpl,但是我们控制台却输出的是“userDao3 = com.zhangjingqi.dao.impl.UserDaoImpl@38102d01”,显然又是UserDaoImpl,这是怎么回事?

​ 我们userDao3被缓存到了factoryBeanObjectCache,也就是说从下图的位置返回的

image-20230604140907921

为什么说是延迟呢?

我们在执行“applicationContext.getBean(“userDao3”);”之前,factoryBeanObjectCache中是没有UserDao3对象的,但是我们执行完这条语句后,factoryBeanObjectCache中就存在了

​ 这也就是说new ClassPathXmlApplicationContext(“beans.xml”)加载配置文件生成Spring容器的时候FactoryBean接口的getObject方法并没有执行/调用,但是在执行getBean时,才去调用的getObject方法生成userDaoImpl对象,并缓存到factoryBeanObjectCache

四、Bean 注入方式与数据类型

4.1 Bean 注入方式

ref 是 reference 的缩写形式,翻译为:涉及,参考的意思,用于引用其他Bean的id

value 用于注入普通属性值

  • 通过Bean的set方法注入

    要有set方法

<property name="userDao" ref="userDao"/>

<property name="userDao" value="haohao"/>
  • 通过构造Bean的方法进行注入
<constructor-arg name="name" ref="userDao"/>

<constructor-arg name="name" value="haohao"/>

4.2 Bean 注入数据类型

注入的数据类型有哪些?

  • 普通数据类型,例如:String、int、boolean等,通过value属性指定
  • 引用数据类型,例如:UserDaoImpl、DataSource等,通过ref属性指定
  • 集合数据类型,例如:List、Map、Properties等。

4.2.1 注入List

  • 向List中注入普通参数

配置文件

<bean id="userService" class="com.zhangjingqi.service.impl.UserServiceImpl">
    <!--不靠ref了,因为是一个集合,要靠子标签-->
    <property name="stringList">
            <list>
                <value>aaaa</value>
                <value>bbb</value>
                <value>ccc</value>
            </list>
    </property>
</bean>

UserServiceImpl

public class UserServiceImpl implements UserService  {

//   注入List
    private List<String> stringList;

    public void setStringList(List<String> stringList) {
        this.stringList = stringList;
    }

测试程序

ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

UserServiceImpl userService = (UserServiceImpl) applicationContext.getBean("userService");

System.out.println("userService = "+userService.getStringList());//userService = [aaaa, bbb, ccc]
  • 向List中注入引用参数

    UserServiceImpl

private List<UserDao> userDaoList;

public void setUserDaoList(List<UserDao> userDaoList) {
    this.userDaoList = userDaoList;
}

配置文件

<bean id="userService" class="com.zhangjingqi.service.impl.UserServiceImpl">

    <property name="userDaoList">
        <list>
            <!--可以配置多个,每一个都代表一个userDao对象-->
            <bean class="com.zhangjingqi.dao.impl.UserDaoImpl"></bean>
            <bean class="com.zhangjingqi.dao.impl.UserDaoImpl"></bean>
            <!--或者可以引用-->
            <ref bean="userDao1"></ref>
            <ref bean="userDao2"></ref>
            <ref bean="userDao3"></ref>
        </list>
    </property>
    
</bean>

<bean id="userDao1" class="com.zhangjingqi.dao.impl.UserDaoImpl"></bean>
<bean id="userDao2" class="com.zhangjingqi.dao.impl.UserDaoImpl"></bean>
<bean id="userDao3" class="com.zhangjingqi.dao.impl.UserDaoImpl"></bean>

4.2.2 注入Set集合

  • 普通属性
private Set<String> setSet;

public void setSetSet(Set<String> setSet) {
    this.setSet = setSet;
}

配置文件

<bean id="userService" class="com.zhangjingqi.service.impl.UserServiceImpl">

    <property name="strSet">
        <set>
            <value>aaaaa</value>
            <value>bbbbb</value>
            <value>ccccc</value>
        </set>
    </property>
    
</bean>
  • 引用类型
private Set<UserDao> userDaoSet;

public void setUserDaoSet(Set<UserDao> userDaoSet) {
    this.userDaoSet = userDaoSet;
}

配置文件

    <property name="userDaoSet">
        <set>
            <!-- 第一种方式-->
            <bean class="com.zhangjingqi.dao.impl.UserDaoImpl"></bean>
            <!--  第二种方式-->
            <ref bean="userDao1"></ref>
            <ref bean="userDao2"></ref>
            <ref bean="userDao3"></ref>
        </set>
    </property>

</bean>
<bean id="userDao1" class="com.zhangjingqi.dao.impl.UserDaoImpl"></bean>
<bean id="userDao2" class="com.zhangjingqi.dao.impl.UserDaoImpl"></bean>
<bean id="userDao3" class="com.zhangjingqi.dao.impl.UserDaoImpl"></bean>

4.2.3 注入Map集合

UserServiceImpl

private Map<String, UserDao> map;

public void setMap(Map<String, UserDao> map) {
    this.map = map;
}

配置信息

<bean id="userService" class="com.zhangjingqi.service.impl.UserServiceImpl">
    <property name="map">
        <map>
            <!--第一种方式-->
            <!--<entry key="1" value=""></entry>-->
            <!--第二种方式-->
            <!--<entry key-ref="" value-ref=""></entry>-->
            <entry key="1" value-ref="userDao1"></entry>
            <entry key="2" value-ref="userDao2"></entry>
            <entry key="3" value-ref="userDao3"></entry>
        </map>
    </property>
</bean>

    <bean id="userDao1" class="com.zhangjingqi.dao.impl.UserDaoImpl"></bean>
    <bean id="userDao2" class="com.zhangjingqi.dao.impl.UserDaoImpl"></bean>
    <bean id="userDao3" class="com.zhangjingqi.dao.impl.UserDaoImpl"></bean>

4.2.4 注入 Properties 键值对

UserServiceImpl

private Properties properties;

public void setProperties(Properties properties) {
    this.properties = properties;
}

配置文件

<bean id="userService" class="com.zhangjingqi.service.impl.UserServiceImpl">
   <property name="properties">
       <props>
           <prop key="p1">zhangjingqi</prop>
           <prop key="p2">123</prop>
           <prop key="p3">456</prop>
       </props>
   </property>
</bean>    

测试

//参数是一个xml配置文件
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

UserServiceImpl userService = (UserServiceImpl) applicationContext.getBean("userService");

System.out.println(userService.getProperties());//{p3=456, p2=123, p1=zhangjingqi}

System.out.println(userService.getProperties().getProperty("p1"));//zhangjingqi

4.3 扩展 - Bean的自动装配

我们之间接触的Bean都是人为手动的将其注入进去的,是手动装配

自动装配方式如果被注入的属性类型是Bean引用的话,那么可以在<bean> 标签中使用 autowire 属性去配置自动注入方式,属性值有两个

​ 不需要人为的指定properties标签去给他配置,它会自动根据你指定的某一个要求然后进行自动装配

  • byName:通过属性名自动装配,即去匹配 setXxx 与 id=“xxx”(name=“xxx”)是否一致

    比如说我们要往UserServiceImpl类中注入一个UserDaoImpl类对象

    怎么完成自动装配的呢?

    ​ UserServiceImpl类内部有一个setUserDao方法,如果把此方法名的set字符去掉,剩下的第一个单词首字母小写,也就是userDao与容器中的某个Bean的BeanName能对应上就行

    ​ 如果匹配不上,所对应的对象也就是userDao对象为NULL(两种情况,容器中根本不存在该对象,或者是BeanName对应错了)

    配置文件

<bean id="userService" autowire="byName"
      class="com.zhangjingqi.service.impl.UserServiceImpl">
</bean>

<bean id="userDao" class="com.zhangjingqi.dao.impl.UserDaoImpl"></bean>

UserServiceImpl

public class UserServiceImpl implements UserService  {

    private UserDao userDao;
    public void setUserDao(UserDao userDao) {
        this.userDao =userDao;
    }
}    

测试

ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
UserServiceImpl userService = (UserServiceImpl) applicationContext.getBean("userService");
System.out.println(userService.getUserDao());//com.zhangjingqi.dao.impl.UserDaoImpl@1573f9fc
  • byType:通过Bean的类型从容器中匹配,匹配出多个相同Bean类型时,报错

    类型可能会重复,比如UserDao接口可能会有很多实现类都注册了Bean,那此时使用byType就会报错

虽然是根据类型装配,但是也需要有setUserDao方法

public class UserServiceImpl implements UserService  {

    private UserDao userDao;
    public void setUserDao(UserDao userDao) {
        this.userDao =userDao;
    }
}

配置文件

此时set方法与BeanName名字不对应也是没有问题的

<bean id="userService" autowire="byType"
      class="com.zhangjingqi.service.impl.UserServiceImpl">
</bean>

<bean id="userDao88888" class="com.zhangjingqi.dao.impl.UserDaoImpl"></bean>

五、SpringBean 其他配置标签

Spring 的 xml 标签大体上分为两类,一种是默认标签,一种是自定义标签

5.1 默认标签

  • 默认标签:就是不用额外导入其他命名空间约束的标签,例如 <bean> 标签

下面这段代码中xmlns代表了xml name space的简写,其值就是一个地址,这个就是默认的命名空间,并且xmlns内部维护的标签,都是默认标签

<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">
标签作用
<beans>一般作为 xml 配置根标签,其他标签都是该标签的子标签
<bean>Bean的配置标签,上面已经详解了,此处不再阐述
<import>外部资源导入标签
<alias>指定Bean的别名标签,使用较少

beans标签下面还能再套beans

但是用的比较少

多套beans的话,我们可以指定用哪一套,下面会讲

image-20230604185420011

5.1.1 beans 标签 profile属性 切换环境

<beans>标签,除了经常用的做为根标签外,还可以嵌套在根标签内,使用profile属性切换开发环境

怎么指定被激活的环境?

  • 使用命令行动态参数,虚拟机参数位置加载

     -Dspring.profiles.active=test
    
  • **使用代码的方式设置环境变量 **

    System.setProperty("spring.profiles.active","test")
    

配置文件

现在我们有三个环境,默认环境、dev环境、test环境

其中userService、userDao不在任何环境下

<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.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd">
    <bean id="userService"  class="com.zhangjingqi.service.impl.UserServiceImpl"/>

    <bean id="userDao" class="com.zhangjingqi.dao.impl.UserDaoImpl"></bean>

    <beans profile="dev">
        <bean id="userServiceDev" class="com.zhangjingqi.service.impl.UserServiceImpl"></bean>
    </beans>

    <beans profile="test">
        <bean id="userDaoTest" class="com.zhangjingqi.dao.impl.UserDaoImpl"></bean>
    </beans>
</beans>

测试

  • 默认环境

    没有任何设置,就是默认环境,此时只有默认环境下的配置生效,其他环境下的配置不生效

ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

UserServiceImpl userService = (UserServiceImpl) applicationContext.getBean("userService");

UserDaoImpl userDao = (UserDaoImpl) applicationContext.getBean("userDao");

System.out.println(userService);//com.zhangjingqi.service.impl.UserServiceImpl@6ae5aa72
System.out.println(userDao);//com.zhangjingqi.dao.impl.UserDaoImpl@222545dc
  • dev环境

    除此之外,默认环境的Bean也生效

//      指定运行环境
        System.setProperty("spring.profiles.active", "dev");

        //参数是一个xml配置文件
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

        UserServiceImpl userService = (UserServiceImpl) applicationContext.getBean("userServiceDev");
  • test环境

    除此之外,默认环境的Bean也生效

//      指定运行环境
        System.setProperty("spring.profiles.active", "test");

        //参数是一个xml配置文件
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

        UserDaoImpl userDao = (UserDaoImpl) applicationContext.getBean("userDaoTest");

5.1.2 Import标签

导入其他配置文件,项目变大后,就会导致一个配置文件内容过多,可以将一个配置文件根据业务某块进行拆分,拆分后,最终通过<import>标签导入到一个主配置文件中,项目加载主配置文件就连同<import> 导入的文件一并加载了

<!--导入用户模块配置文件-->
<import resource="classpath:UserModuleApplicationContext.xml"/>

<!--导入商品模块配置文件-->
<import resource="classpath:ProductModuleApplicationContext.xml"/>

5.1.3 alias标签

为某个Bean添加别名,与在<bean> 标签上使用name属性添加别名的方式一样,我们为UserServiceImpl指定四个别名:aaa、bbb、xxx、yyy

<!--配置UserService-->
<bean id="userService" name="aaa,bbb" class="com.itheima.service.impl.UserServiceImpl">
      <property name="userDao" ref="userDao"/>
</bean>
<!--指定别名-->
<alias name="userService" alias="xxx"/>
<alias name="userService" alias="yyy"/>

5.2 自定义标签

  • 自定义标签:就是需要额外引入其他命名空间约束,并通过前缀引用的标签,例如 <context:property-placeholder/> 标签

并且xmlns:context="http://www.springframework.org/schema/context"内部维护的标签(此内部的约束在对应的jar中存在,所以一定要导入坐标),我们都叫做自定义标签

Spring的自定义标签需要引入外部的命名空间,并为外部的命名空间指定前缀,使用 <前缀:标签> 形式的标签,称之为自定义标签

如下所示,xmlns:context便是我们自己定义的一个新的命名空间

<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.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd">

如果我们编写了下面的标签,一看就知道是context命名空间中的标签

<context:property-placeholder></context:property-placeholder>

六、Spring 的get方法

方法定义返回值和参数
Object getBean (String beanName)根据beanName从容器中获取Bean实例,要求容器中Bean唯一,返回值为Object,需要强转
T getBean (Class type)根据Class类型从容器中获取Bean实例,要求容器中Bean类型唯一,返回值为Class类型实例,无需强转
T getBean (String beanName,Class type)根据beanName从容器中获得Bean实例,返回值为Class类型实例,无需强转
//根据beanName获取容器中的Bean实例,需要手动强转
UserService userService = (UserService) applicationContext.getBean("userService");

//根据Bean类型去容器中匹配对应的Bean实例,如存在多个匹配Bean则报错
UserService userService2 = applicationContext.getBean(UserService.class);

//根据beanName获取容器中的Bean实例,指定Bean的Type类型
UserService userService3 = applicationContext.getBean("userService", UserService.class);

七、Spring定义非自定义Bean

在 xml 中配置的Bean都是自己定义的,例如:UserDaoImpl,UserServiceImpl。

​ 在实际开发中有些功能类并不是我们自己定义的,而是使用的第三方jar包中的,那么,这些Bean要想让Spring进行管理,也需要对其进行配置

7.1 配置 Druid 数据源交由Spring管理

  • 导入Druid坐标
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.49</version>
</dependency>
<!-- druid数据源 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.23</version>
</dependency>
  • 不交给Spring的时候
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(....);
druidDataSource.setUrl(....);
druidDataSource.setUsername(....);
druidDataSource.setPassword(....);
  • 交给Spring

配置文件

property是set方法注入,name属性值就是原本的set方法名称去除掉set字符并且第一个单词的首字母小写,一定要对应,否则注入失败

<!--配置数据源信息-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <!--配置必要属性-->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc://localhost:3306/mybatis"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</bean>
  • 测试程序
//参数是一个xml配置文件
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Object dataSource = applicationContext.getBean("dataSource");
System.out.println(dataSource);

image-20230604202618223

7.2 配置Connection交由Spring管理

开发中没有这么干的,就是应用一下,巩固一下知识

Connection 的产生是通过DriverManager的静态方法getConnection获取的,所以我们要用静态工厂方式配置。

配置文件

<!--相当于Class.forName("com.mysql.jdbc.Driver")-->
<bean class="java.lang.Class" factory-method="forName">
    <!--forName方法需要参数-->
    <constructor-arg name="className" value="com.mysql.cj.jdbc.Driver"/>
</bean>

<bean id="connection" class="java.sql.DriverManager" factory-method="getConnection" scope="prototype">
    <constructor-arg name="url" value="jdbc:mysql:localhost:3306/mybatis"/>
    <constructor-arg name="user" value="root"/>
    <constructor-arg name="password" value="root"/>
</bean>

7.3 配置日期对象交由Spring管理

之前编写形式

​ 我们可以把SimpleDateFormat看成工厂,而parse就是产生 对象的工厂实例方法,Date类型就是工厂实例方法的返回值

String currentTimeStr = "2023-08-27 07:20:00";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = simpleDateFormat.parse(currentTimeStr);

怎么交给Spring容器管理?

<!--配置工厂对象-->
<bean id="simpleDateFormat" class="java.text.SimpleDateFormat" >
    <constructor-arg name="pattern" value="yyyy-MM-dd HH:mm:ss"></constructor-arg>
</bean>

<bean id="date" class="java.util.Date" factory-bean="simpleDateFormat" factory-method="parse">
    <constructor-arg name="source" value="2023-08-27 07:20:00"></constructor-arg>
</bean>

测试

ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Object date = applicationContext.getBean("date");
System.out.println(date);

7.4 配置MyBatis的SqlSessionFactory交由Spring管理

原始方式

//加载mybatis核心配置文件,使用Spring静态工厂方式
InputStream in = Resources.getResourceAsStream(“mybatis-conifg.xml”);

//创建SqlSessionFactoryBuilder对象,使用Spring无参构造方式
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

//调用SqlSessionFactoryBuilder的build方法,使用Spring实例工厂方式
SqlSessionFactory sqlSessionFactory = builder.build(in);

导入对应坐标

<!--Mybatis-->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.5</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.19</version>
</dependency>

配置文件

<!--静态工厂方式产生Bean实例-->
<bean id="inputStream" class="org.apache.ibatis.io.Resources" factory-method="getResourceAsStream">
    <constructor-arg name="resource" value="mybatis-config.xml"/>
</bean>

<!--无参构造方式产生Bean实例-->
<bean id="sqlSessionFactoryBuilder" class="org.apache.ibatis.session.SqlSessionFactoryBuilder"/>

<!--实例工厂方式产生Bean实例-->
<bean id="sqlSessionFactory" factory-bean="sqlSessionFactoryBuilder" factory-method="build">
    <constructor-arg name="inputStream" ref="inputStream"/>
</bean>
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我爱布朗熊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值