Spring bean标签

目录

Spring bean标签

在创建IOC容器的时候,是如何把配置文件解析成我们的BeanDefinition。本文针对其<bean/>标签中的属性及其子标签进行说明。

1.了解Spring Xml配置文件

当我们需要去创建一个Spring配置文件的时候我们需要用到这个<beans/>跟根标签,只有在beans下面定义的信息才可以被Spring解析并保存到BeanDefinition中。

先了解一下大致结构。外层是根节点<beans/>,内层是一个一个的<bean/>标签。

xml 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

    <bean id="messageService" class="com.mfyuan.service.MessageServiceImpl"/>

</beans>

2.bean标签的Attrbute

源码中是通过这一个方法来解析的。

org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;

parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);

  • id:bean的唯一标识,不可重复。

  • name:bean的名称,不可重复。但是允许有多个可以通过,或者;来分割。

  • class: bean的类型,IOC容器会通过这个class来通过反射来创建这个对象。

  • singleton:标识Bean对象在IOC容器中是否是单例的。已弃用 。新版采用scope属性来替换。

  • scope:bean的作用域。

    • singleton(默认):在IOC容器中的是单例的。当bean被其他多个bean所依赖的时候。其他都是依赖的同一样。

      xml 复制代码
      <bean id="messageService" name="messageService" class="com.mfyuan.service.MessageServiceImpl"/>
      java 复制代码
      MessageService messageService = applicationContext.getBean("messageService", MessageService.class);
              System.out.println(messageService);
              messageService = applicationContext.getBean("messageService", MessageService.class);
              System.out.println(messageService);
      // 同时输出两个NotifyUtil的MessageServcie 根据内存地址判断为同一个并
      com.mfyuan.service.MessageServiceImpl@675d3402
      com.mfyuan.service.MessageServiceImpl@675d3402
    • prototype:原形的。表示当其他多个bean依赖该bean时,与单例不同的是他会创建一个新的实例去被依赖。

      xml 复制代码
      <!--scope改为prototype后-->
      <bean id="messageService" name="messageService" class="com.mfyuan.service.MessageServiceImpl" scope="prototype"/>
      java 复制代码
      // 两个对象不同
      com.mfyuan.service.MessageServiceImpl@675d3402
      com.mfyuan.service.MessageServiceImpl@51565ec2
    • request(web扩展的):每一个请求都会去创建不同bean,请求结束后销毁bean。

    • session(web扩展的):不同的session会创建不同bean,session结束后销毁bean。

    • application(web扩展的):不同的web Application会创建,不同bean,webApplication关闭后销毁bean。

  • abstract:抽象的bean,默认为false。为true时,表示该beanIOC容器启动 的时候不会去初始化它。只会当做一个配置模版,可以被其他beanparent属性使用。

  • lazy-init:懒加载。默认为false,实时加载。为true时,表示为延时加载的,只有当该beanIOC容器中被使用或者被依赖 的时候,才会去创建出这个bean

  • autowire:依赖注入的方式。通常有以下几种

    • no(默认):不采用autowire的机制。当我们需要依赖其他bean时,通过ref来引入其他bean

    • byName:通过属性的名称来自动装配,可以免去使用<property/>。它会自动根据属性名帮忙找到同样名称的bean帮我们注入到当前bean中。没有找到则不会注入

      xml 复制代码
      <bean id="notifyUtil"  class="com.mfyuan.util.NotifyUtil" autowire="byName"/>
    • byType:通过类型来自动装配。它会自动根据属性类型帮忙找到类型的bean帮我们注入到当前bean中。如果有多个同样类型的bean会报错。可以使用primary来指定那个是主要的。没有找到则不会注入

      xml 复制代码
      <bean id="notifyUtil"  class="com.mfyuan.util.NotifyUtil" autowire="byType"/>
    • constructor:通过构造器来自动装配。他会通过构造器的参数去帮我们找到同样的类型bean来创建当前bean。跟byType类似。但是如何没有找到合适bean来注入的话,则会报错。

    • default:采取父级标签(<beans />)的default-autowire

  • dependency-check:依赖检查。有以下几种:

    • none:即使不注入全部bean属性,也可以将bean创建成功。
    • simple:检查基本对象或者集合类<基本对象>没有注入则会报错。
    • objects:检查引用类型没有注入则会报错。
    • all:检查全部属性是否注入成功。
  • depends-on:设置当前bean依赖那些bean,可以是多个使用,;分割。依赖的这些bean在当前bean前初始化,在当前bean后销毁。

  • autowire-candidate:是否为候选的bean,默认为true。也就是是否可以被其他bean所依赖(只针对于byType,对于byName,不管这个值是true还是false都会注入成功)。

    java 复制代码
    @Data
    @ToString
    public class NotifyListUtil {
        private List<MessageService> messageServiceList;
    }
    xml 复制代码
    <!--创建了两个MessageService-->
    <bean id="messageService" name="messageService" class="com.mfyuan.service.MessageServiceImpl"/>
    
    <bean id="messageService1"  class="com.mfyuan.service.MessageServiceImpl"/>
    
    <bean id="notifyListUtil" class="com.mfyuan.util.NotifyListUtil" autowire="byType"/>
    java 复制代码
    NotifyListUtil notifyListUtil  = applicationContext.getBean("notifyListUtil", NotifyListUtil.class);
    System.out.println(notifyListUtil);
    // 输出 两个
    NotifyListUtil(messageServiceList=[com.mfyuan.service.MessageServiceImpl@e25b2fe, com.mfyuan.service.MessageServiceImpl@754ba872])
    xml 复制代码
    <!--将其中一个改为false-->
    <bean id="messageService1"  class="com.mfyuan.service.MessageServiceImpl" autowire-candidate="false"/>
    java 复制代码
    // 输出一个
    NotifyListUtil(messageServiceList=[com.mfyuan.service.MessageServiceImpl@e25b2fe])
    xml 复制代码
    <!--autowire改为byName-->
    <bean id="notifyListUtil" class="com.mfyuan.util.NotifyListUtil" autowire="byName"/>
    java 复制代码
    // 输出 0个 为什么呢?因为我这里的属性名是messageServiceList没有为该名称的bean所有就为0个。
    // 思考为什么byName会使这个属性失效了。我们只要知道的是beanName是不允许重复的就能明白了。
  • primary:是否为主要的。默认为false。当同时有多个同样类型的bean时,优先注入primarytrue的。

  • init-method:bean初始化的方法。

  • destroy-method:bean销毁的方法。

  • factory-method:分情况

    • 单独 使用factory-method的时候。指定class中的一个静态方法来创建这个bean。创建出来的bean,不再是class,而是指定静态方法返回值

      java 复制代码
      public class MyFactoryMethod {
      
          public static String getFactoryMethod(){
              return "myFactoryMethod";
          }
          
          public static String getFactoryMethod(String methodName){
              return methodName;
          }
      }
      xml 复制代码
      <bean id="myFactoryMethod" class="com.mfyuan.factory.MyFactoryMethod"/>
      <!--使用factory-method 来创建这个bean对象,而不是创建一个class的bean对象-->
      <bean id="myFactoryMethodStr" class="com.mfyuan.factory.MyFactoryMethod" factory-method="getFactoryMethod"/>
      
      <!--如果这个静态方法有参数的话,也可以通过constructor-arg 来指定参数-->
      <bean id="myFactoryMethodStr1" class="com.mfyuan.factory.MyFactoryMethod" factory-method="getFactoryMethod">
          <constructor-arg index="0" value="consumeMethod"/>
      </bean>
      java 复制代码
      Class<?> myFactoryMethod = applicationContext.getType("myFactoryMethod");
      System.out.println(myFactoryMethod);
      
      Class<?> myFactoryMethodStr = applicationContext.getType("myFactoryMethodStr");
      System.out.println(myFactoryMethodStr);
      
      Object myFactoryMethodStr1 = applicationContext.getBean("myFactoryMethodStr1");
      System.out.println(myFactoryMethodStr1);
      // 输出 一个为class指定的对象,一个则是String。
      class com.mfyuan.factory.MyFactoryMethod
      class java.lang.String
      consumeMethod
    • factory-bean一起使用的时候。指定factory-bean中的一个非静态方法class可以省去了。

      java 复制代码
      public class MyFactoryMethod {
          public String consumeMethod(){
              return "consumeMethod";
          }
      }
      xml 复制代码
      <bean id="myFactoryMethod" class="com.mfyuan.factory.MyFactoryMethod"/>
      <!- 通过指定factory-bean工厂bean,及指定一个factory-method来生成实际的bean对象 ->
      <bean id="myFactoryMethodStr2" factory-bean="myFactoryMethod" factory-method="consumeMethod"/>
      JAVA 复制代码
      Object myFactoryMethodStr1 = applicationContext.getBean("myFactoryMethodStr2");
      System.out.println(myFactoryMethodStr2);
      // 输出consumeMethod
  • factory-bean:通过指定factory-bean工厂bean,及指定一个factory-method来生成实际的bean对象。为什么使用factory-beanfactory-method必须是非静态的了?这个也很好理解,如果是静态的,直接使用class+factory-method即可。

3.bean的子标签

子标签,也就是xmlbean这个dom元素的子节点;

例如

xml 复制代码
<bean>
    <description>hello description</description>
	<meta key="info" value="我是一个bean"/>
<bean/>
  • description:bean的一个描述。

  • meta:bean的元信息(可以存在多个)。

    • 源码中由此parseMetaElements(ele, bd);方法进行解析;
  • lookup-method:不常用,但是说一下。

    可以重写bean中的一个方法,把方法的返回值替换成bean指定的对象。要求是返回值与指定的对象类型相同。

    源码中由parseLookupOverrideSubElements(ele, bd.getMethodOverrides());处理

    使用场景。在一个单例对象中,我需要他的一个属性原形的。

    实体类

    java 复制代码
    @Getter
    @Setter
    public  class MyLockUpMethod {
        private User refreshUser;
    }

    配置文件

    xml 复制代码
    <!--我们定义了一个User,他的scope是prototype 也就是在依赖注入的时候每次都注入的一个新的对象-->
    <bean id="mfyuan" class="com.mfyuan.model.User" scope="prototype">
        <property name="name" value="mfYuan" />
    </bean>
    <!-- 将mfyuan这个bean注入到myLockUpMethod中-->
    <bean id="myLockUpMethod" class="com.mfyuan.component.MyLockUpMethod" >
        <property name="refreshUser" ref="mfyuan"></property>
    </bean>

    操作类

    java 复制代码
    MyLockUpMethod myLockUpMethod = applicationContext.getBean("myLockUpMethod",MyLockUpMethod.class);
    System.out.println(myLockUpMethod+"-"+myLockUpMethod.getRefreshUser());
    myLockUpMethod = applicationContext.getBean("myLockUpMethod",MyLockUpMethod.class);
    System.out.println(myLockUpMethod+"-"+myLockUpMethod.getRefreshUser());
    // 输出 这两个对象的refreshUser是相同的,
    com.mfyuan.component.MyLockUpMethod@39c0f4a-com.mfyuan.model.User@39c0f4a
    com.mfyuan.component.MyLockUpMethod@39c0f4a-com.mfyuan.model.User@39c0f4a

    这个结果也很好能理解,因为我们的MyLockUpMethod是单例的。所以每次从IOC容器中取的是一个对象。其属性也自然想同。

    修改配置文件

    xml 复制代码
    <!-- 将myLockUpMethod的scope也改原形的-->
    <bean id="myLockUpMethod" class="com.mfyuan.component.MyLockUpMethod" scope="prototype">
        <property name="refreshUser" ref="mfyuan"></property>
    </bean>6
    // 输出不同,但是这样无法保证myLockUpMethod是同一个对象。
    com.mfyuan.component.MyLockUpMethod@39c0f4a-com.mfyuan.model.User@1794d431
    com.mfyuan.component.MyLockUpMethod@42e26948-com.mfyuan.model.User@57baeedf

    修改配置文件

    xml 复制代码
    <bean id="myLockUpMethod" class="com.mfyuan.component.MyLockUpMethod">
    	<lookup-method  name="getRefreshUser" bean="mfyuan"/>
    </bean>
    // 满足要求MyLockUpMethod相同Uesr不同。
    com.mfyuan.component.MyLockUpMethod$$EnhancerBySpringCGLIB$$af152e52@e320068-com.mfyuan.model.User@1f57539
    com.mfyuan.component.MyLockUpMethod$$EnhancerBySpringCGLIB$$af152e52@e320068-com.mfyuan.model.User@76f2b07d

    可以看见他这里是使用的CGLIB代理来创建的myLockUpMethod,通过去代理并重写getRefreshUser,来实现。

  • replaced-method:方法替换,相比lockup-method,更灵活一点。就相当于重写方法。

    源码中由parseReplacedMethodSubElements(ele, bd.getMethodOverrides())来解析。

    xml 复制代码
    <bean id="xxx" class="xxx">
        <replaced-method name="xxx" replacer="xxx">
        	<arg-type match=""></arg-type>
        </replaced-method>
    </bean>

    <bean/>内允许有多个<replaced-method/>,其name属性为要重写的方法名称,replacer为实现了MethodReplacer的实现类,具体替换逻辑在reimplement中编写。如果需要重写的方法有重载的情况的话可以通过<arg-type/>指定类型,从而找到目标方法。

    java 复制代码
    public interface MethodReplacer {
    	/**
    	 * 重新实现给定的方法。
    	 * @param obj 被替换方法的对象
    	 * @param method 要替换的方法
    	 * @param args 方法的参数
    	 * @return 方法的返回值
    	 */
    	Object reimplement(Object obj, Method method, Object[] args) throws Throwable;
    }

    实体类

    java 复制代码
    public class Execute {
        public void execute(){
            System.out.println("execute...");
        }
    }

    配置文件

    xml 复制代码
    <bean id="execute" class="com.mfyuan.model.Execute">

    操作类

    java 复制代码
    Execute execute = applicationContext.getBean(Execute.class);
    System.out.println(execute);
    execute.execute();
    // 输出
    com.mfyuan.model.Execute@7a1ebcd8
    execute...

    实现MethodReplacer

    java 复制代码
    public class ExecuteReplacer implements MethodReplacer {
    
        @Override
        public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
            switch (method.getName()) {
                case "execute":
                    System.out.println("execute replacer...");
            }
            return null;
        }
    }

    修改配置文件

    xml 复制代码
    <!-- 将ExecuteReplacer注入IOC容器。 -->
    <bean id="executeReplacer" class="com.mfyuan.component.ExecuteReplacer"/>
    
    <bean id="execute" class="com.mfyuan.model.Execute">
        <!-- 替换execute方法,由executeReplacer来实现。 -->
        <replaced-method name="execute" replacer="executeReplacer"/>
    </bean>

    操作类

    java 复制代码
    // 输出
    com.mfyuan.model.Execute$$EnhancerBySpringCGLIB$$1accf27a@5faeada1
    execute replacer...

    方法替换成功,发现这个类也是被CGLIB进行了代理。

    java 复制代码
    public interface MethodReplacer {
    	/**
    	 * 重新实现给定的方法。
    	 * @param obj 被替换方法的对象
    	 * @param method 要替换的方法
    	 * @param args 方法的参数
    	 * @return 方法的返回值
    	 */
    	Object reimplement(Object obj, Method method, Object[] args) throws Throwable;
    }
  • constructor-arg:用于构造函数注入,或者是factory-bean有参数的时候等。可以通过该标签指定参数。(可以多个)。源码中由parseConstructorArgElements(ele, bd);解析。

  • property(最常用的 ):指定bean对象的属性值。源码中由parsePropertyElements(ele, bd);解析。

  • qualifier:如果当前beanautowire,并且,当对应的属性找到的多个bean时,可以通过qualifier来指定使用哪一个。

扩展

FactoryBean

又称工厂Bean。那BeanFactory呢?这两者完全不是一种东西。BeanFactory可以把他理解为就是一个IOC容器,而FactroyBean的话则是一种特殊Bean。类似<bean/>里的factory-bean,与factory-method。而FactoryBean的话则不需要去指定这个,直接通过class使用即可。

FactoryBean源码

java 复制代码
public interface FactoryBean<T> {

	/**
	 * 工厂Bean返回的对象实例
	 * @return an instance of the bean (can be {@code null})
	 * @throws Exception in case of creation errors
	 * @see FactoryBeanNotInitializedException
	 */
	T getObject() throws Exception;

	/**
	 * 返回对象实例的类型
	 * @return the type of object that this FactoryBean creates,
	 * or {@code null} if not known at the time of the call
	 * @see ListableBeanFactory#getBeansOfType
	 */
	Class<?> getObjectType();

	/**
	 * 返回的实例是否单例的,
	 * @return whether the exposed object is a singleton
	 * @see #getObject()
	 * @see SmartFactoryBean#isPrototype()
	 */
	boolean isSingleton();
}

实现一个FactoryBean

java 复制代码
@Data
public class UserFactoryBean implements FactoryBean<User> {

    private String userName;

    public UserFactoryBean(String userName) {
        this.userName = userName;
    }

    @Override
    public User getObject() throws Exception {
        return new User(userName);
    }

    @Override
    public Class<?> getObjectType() {
        return User.class;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}

配置文件

xml 复制代码
<bean id="userFactoryBean" class="com.mfyuan.factory.UserFactoryBean" >
    <constructor-arg index="0" value="xiaoChen" />
</bean>

输出

java 复制代码
Object userFactoryBean = applicationContext.getBean("userFactoryBean");
System.out.println(userFactoryBean);
// User(name=xiaoChen)

// 通过&可以拿到UserFactoryBean本身的这个对象。
userFactoryBean = applicationContext.getBean("&userFactoryBean");
System.out.println(userFactoryBean);
// UserFactoryBean(userName=xiaoChen)

让我们在IOC容器中去注入一个FactoryBean对象的时候,其实是为我们注入了两个对象,其一是getObject()返回值,其二是FactroyBean本身这个对象。通过&+beanName可以取到FactoryBean本身的这个对象。

相关推荐
等一场春雨6 分钟前
Java设计模式 八 适配器模式 (Adapter Pattern)
java·设计模式·适配器模式
一弓虽28 分钟前
java基础学习——jdbc基础知识详细介绍
java·学习·jdbc·连接池
王磊鑫28 分钟前
Java入门笔记(1)
java·开发语言·笔记
马剑威(威哥爱编程)1 小时前
2025春招 SpringCloud 面试题汇总
后端·spring·spring cloud
硬件人某某某1 小时前
Java基于SSM框架的社区团购系统小程序设计与实现(附源码,文档,部署)
java·开发语言·社区团购小程序·团购小程序·java社区团购小程序
程序员徐师兄1 小时前
Java 基于 SpringBoot 的校园外卖点餐平台微信小程序(附源码,部署,文档)
java·spring boot·微信小程序·校园外卖点餐·外卖点餐小程序·校园外卖点餐小程序
chengpei1471 小时前
chrome游览器JSON Formatter插件无效问题排查,FastJsonHttpMessageConverter导致Content-Type返回不正确
java·前端·chrome·spring boot·json
Quantum&Coder1 小时前
Objective-C语言的计算机基础
开发语言·后端·golang
五味香1 小时前
Java学习,List 元素替换
android·java·开发语言·python·学习·golang·kotlin
Joeysoda1 小时前
Java数据结构 (从0构建链表(LinkedList))
java·linux·开发语言·数据结构·windows·链表·1024程序员节