Spring系列(1-10)

烟雨 3年前 (2022-09-02) 阅读数 534 #Spring
文章标签 Spring

一、IOC、DI

1、IOC控制反转

一种设计理念,将对象创建和组装的主动控制权利交给了spring容器去做,控制的动作被反转了,降低了系统的耦合度,利于系统维护和扩展。
主要就是指需要使用的对象的组装控制权被反转了,之前是自己要做的,现在交给spring容器做

2、DI依赖注入

表示spring容器中创建对象时给其设置依赖对象的方式,通过某些注入方式可以让系统更灵活,比如自动注入等可以让系统变的很灵活。

二、Spring容器

1、IOC容器

IOC容器是具有依赖注入功能的容器负责对象的实例化、对象的初始化,对象和对象之间依赖关系配置、对象的销毁、对外提供对象的查找等操作,对象的整个生命周期都是由容器来控制

2、Bean概念

由Spring容器管理的对象统称为Bean对象。Bean就是普通的java对象,和我们自己new的对象其实是一样的,只是这些对象是由spring去创建和管理的。

3、Spring容器对象

3.1、BeanFactory接口

Spring容器中具有代表性的容器就是BeanFactory接口,这个是Spring容器的顶层接口,提供了容器最基本的功能。
//按bean的id或者别名查找容器中的bean
Object getBean(String name) throws BeansException

//这个是一个泛型方法,按照bean的id或者别名查找指定类型的bean,返回指定类型的bean对象
<T> T getBean(String name, Class<T> requiredType) throws BeansException;

//返回容器中指定类型的bean对象
<T> T getBean(Class<T> requiredType) throws BeansException;

//获取指定类型bean对象的获取器
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);

3.2、ApplicationContext接口

这个接口继承了BeanFactory接口,所以内部包含了BeanFactory所有的功能,并且在其上进行了扩展,增加了很多企业级功能,比如AOP、国际化、事件支持等等。

3.3、ClassPathXmlApplicationContext类

这个类实现了ApplicationContext接口,注意一下这个类名称包含了ClassPathXml,说明这个容器类可以从classpath中加载bean xml配置文件,然后创建xml中配置的bean对象。

3.4、AnnotationConfigApplicationContext类

这个类也实现了ApplicationContext接口,注意其类名包含了Annotation和config两个单词,上面我们有说过,bean的定义支持xml的方式和注解的方式,当我们使用注解的方式定义bean的时候,就需要用到这个容器来装载了,这个容器内部会解析注解来构建构建和管理需要的bean。

三、xml中bean定义

bean xml文件用于定义Spring容器需要管理的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-4.3.xsd">

    <import resource="引入其他bean xml配置文件" />
    <bean id="bean标识" class="完整类型名称"/>
    <alias name="bean标识" alias="别名" />
        
</beans>

1、bean元素

<bean id="bean唯一标识" name="bean名称" class="完整类型名称" factory-bean="工厂bean名称" factory-method="工厂方法"/>

bean名称别名定义规则

名称和别名可以通过bean元素中的id和name来定义,具体定义规则如下:
  1. 当id存在的时候,不管name有没有,取id为bean的名称。

  2. 当id不存在,此时需要看name,name的值可以通过","或者 空格 分割,最后会按照分隔符得到一个String数组,数组的第一个元素作为bean的名称,其他的作为bean的别名。

  3. 当id和name都存在的时候,id为bean名称,name用来定义多个别名。

  4. 当id和name都不指定的时候,bean名称自动生成,生成规则下面详细说明。

当id和name都未指定的时候,bean的名称和别名又是什么呢?此时由Spring自动生成,bean名称为:
bean的class的完整类名#编号
例如:beanName:com.javacode2018.lesson001.demo2.UserModel#0
别名:com.javacode2018.lesson001.demo2.UserModel

2、alias元素

alias元素也可以用来给某个bean定义别名,如下
<alias name="需要定义别名的bean" alias="别名" />

3、import元素

import元素引入其他bean配置文件。
<import resource="其他配置文件的位置" />

四、IOC容器创建bean实例的多种方式

1、通过反射调用构造方法创建bean对象

constructor-arg:用于指定构造方法参数的值。
  1. index:构造方法中参数的位置,从0开始,依次递增。

  2. value:指定参数的值。

  3. ref:当插入的值为容器内其他bean的时候,这个值为容器中对应bean的名称。

<bean id="bean名称" name="bean名称或者别名" class="bean的完整类型名称">
    <constructor-arg index="0" value="bean的值" ref="引用的bean名称" />
    <constructor-arg index="1" value="bean的值" ref="引用的bean名称" />
    ....
    <constructor-arg index="n" value="bean的值" ref="引用的bean名称" />
</bean>

2、通过静态工厂方法创建bean对象

class:指定静态工厂完整的类名
factory-method:静态工厂中的静态方法,返回需要的对象。
constructor-arg:用于指定静态方法参数的值,用法和上面介绍的构造方法一样。
<bean id="bean名称" name="" class="静态工厂完整类名" factory-method="静态工厂的方法">
    <constructor-arg index="0" value="bean的值" ref="引用的bean名称" />
    <constructor-arg index="1" value="bean的值" ref="引用的bean名称" />
    ....
    <constructor-arg index="n" value="bean的值" ref="引用的bean名称" />
</bean>

3、通过实例工厂方法创建bean对象

<bean id="bean名称" factory-bean="需要调用的实例对象bean名称" factory-method="bean对象中的方法">
    <constructor-arg index="0" value="bean的值" ref="引用的bean名称" />
    <constructor-arg index="1" value="bean的值" ref="引用的bean名称" />
    ....
    <constructor-arg index="n" value="bean的值" ref="引用的bean名称" />
</bean>

4、通过FactoryBean来创建bean对象

<bean id="bean名称" class="FactoryBean接口实现类" />
public class UserFactoryBean implements FactoryBean<UserModel> {
    int count = 1;
    @Nullable
    @Override
    public UserModel getObject() throws Exception {
        // 返回了一个创建好的UserModel对象
    	UserModel userModel = new UserModel();
    	userModel.setName("我是通过FactoryBean创建的第"+count+++ "对象");//@4
    	return userModel;
    }
    @Nullable
    @Override
    public Class<?> getObjectType() {
    	return UserModel.class;
    }
    @Override
    public boolean isSingleton() {
        // 是否是单例
    	return true;
    }
}

五、Bean的作用域scope

通过scope属性指定bean的作用域,如:
<bean id="" class="" scope="作用域" />
  • singleton:单例

  • prototype:多例

Spring web环境中的作用域

  • request:在一次http请求中。

  • session:一次会话中。

  • application:一个web环境中。

1、自定义Scope

第1步:实现Scope接口,我们来看一下这个接口定义
public interface Scope {
    /**
    * 返回当前作用域中name对应的bean对象
    * name:需要检索的bean的名称
    * objectFactory:如果name对应的bean在当前作用域中没有找到,那么可以调用这个ObjectFactory来创建这个对象
    **/
    Object get(String name, ObjectFactory<?> objectFactory);
    
    /**
    * 将name对应的bean从当前作用域中移除
    **/
    @Nullable
    Object remove(String name);
    /**
    * 用于注册销毁回调,如果想要销毁相应的对象,则由Spring容器注册相应的销毁回调,而由自定义作用域选择是不是要销毁相应的对象
    */
    void registerDestructionCallback(String name, Runnable callback);
    
    /**
    * 用于解析相应的上下文数据,比如request作用域将返回request中的属性。
    */
    @Nullable
    Object resolveContextualObject(String key);
    
    /**
    * 作用域的会话标识,比如session作用域将是sessionId
    */
    @Nullable
    String getConversationId();
}

定义了作用域的名称为一个常量thread,可以在定义bean的时候给scope使用

/**
* 自定义本地线程级别的bean作用域,不同的线程中对应的bean实例是不同的,同一个线程中同名的bean是同一个实例
*/
public class ThreadScope implements Scope {
	public static final String THREAD_SCOPE = "thread";
	private ThreadLocal<Map<String, Object>> beanMap = new ThreadLocal() {
		@Override
		protected Object initialValue() {
			return new HashMap<>();
		}
	};

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
    	Object bean = beanMap.get().get(name);
    	if (Objects.isNull(bean)) {
    		bean = objectFactory.getObject();
    		beanMap.get().put(name, bean);
    	}
    	return bean;
    }
    
    @Override
    public Object remove(String name) {
    	return this.beanMap.get().remove(name);
    }
    
    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
    	//bean作用域范围结束的时候调用的方法,用于bean清理
   		System.out.println(name);
    }

    @Override
    public Object resolveContextualObject(String key) {
    	return null;
    }

    @Override
    public String getConversationId() {
    	return Thread.currentThread().getName();
    }
}

第2步:使用自定义的作用域

<!-- 自定义scope的bean -->
<bean id="threadBean" class="com.javacode2018.lesson001.demo4.BeanScopeModel" scope="thread">
	<constructor-arg index="0" value="thread"/>
</bean>

第3步:将自定义的scope注册到容器
需要调用org.springframework.beans.factory.config.ConfigurableBeanFactory#registerScope的方法,看一下这个方法的声明
/**
* 向容器中注册自定义的Scope
*scopeName:作用域名称
* scope:作用域对象
**/
void registerScope(String scopeName, Scope scope);
public static void main(String[] args) throws InterruptedException {
        String beanXml = "classpath:/com/javacode2018/lesson001/demo4/beans/thread.xml";
        //手动创建容器
        ClassPathXmlApplicationContext context = new
                ClassPathXmlApplicationContext(){
                    @Override
                    protected void
                    postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
                        //向容器中注册自定义的scope
                        beanFactory.registerScope(ThreadScope.THREAD_SCOPE, new
                                ThreadScope());
                        super.postProcessBeanFactory(beanFactory);
                    }
                };
        //设置配置文件位置
        context.setConfigLocation(beanXml);
        //启动容器
        context.refresh();
        //使用容器获取bean
        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread() + "," +
                        context.getBean("threadBean"));
                System.out.println(Thread.currentThread() + "," +
                        context.getBean("threadBean"));
            }).start();
            TimeUnit.SECONDS.sleep(1);
        }
    }

六、依赖注入之手动注入

通常有2种方式:构造函数的方式和set属性的方式,Spring中也是通过这两种方式实现注入的,下面详解2种方式。

1、通过构造器注入

1.1、根据构造器参数索引注入

<bean id="diByConstructorParamIndex" class="com.javacode2018.lesson001.demo5.UserModel">
    <constructor-arg index="0" value="路人甲Java"/>
    <constructor-arg index="1" value="上海市"/>
</bean>

1.2、根据构造器参数类型注入

<bean id="diByConstructorParamType" class="com.javacode2018.lesson001.demo5.UserModel">
    <constructor-arg type="参数类型" value="参数值"/>
    <constructor-arg type="参数类型" value="参数值"/>
</bean>

1.3、根据构造器参数名称注入

<bean id="diByConstructorParamName" class="com.javacode2018.lesson001.demo5.UserModel">
    <constructor-arg name="参数类型" value="参数值"/>
    <constructor-arg name="参数类型" value="参数值"/>
</bean>

java通过反射的方式可以获取到方法的参数名称,不过源码中的参数通过编译之后会变成class对象,通常情况下源码变成class文件之后,参数的真实名称会丢失,参数的名称会变成arg0,arg1,arg2这样的,和实际参数名称不一样了,如果需要将源码中的参数名称保留在编译之后的class文件中,编译的时候需要用下面的命令:

javac -parameters java源码

编译的时候不可能手动编译,Spring提供了解决方案,通过ConstructorProperties注解来定义参数的名称,将这个注解加在构造方法上面,如下:

@ConstructorProperties({"第一个参数名称", "第二个参数的名称",..."第n个参数的名称"})
public 类名(String p1, String p2...,参数n)

2、setter注入

<bean id="" class="">
	<property name="属性名称" value="属性值" />
    ...
    <property name="属性名称" value="属性值" />
</bean>

3、注入容器中的bean

注入容器中的bean有两种写法:

3.1、ref属性方式

构造器方式,将value替换为ref:
<constructor-arg ref="需要注入的bean的名称"/>

setter方式,将value替换为ref:

<property name="属性名称" ref="需要注入的bean的名称" />

3.2、内置bean的方式

构造器的方式:
<constructor-arg>
    <bean class=""/>
</constructor-arg

setter方式:

<property name="属性名称">
	<bean class=""/>
</property>

4、其他类型注入

public class DiOtherTypeModel {
    private List<String> list1;
    private Set<UserModel> set1;
    private Map<String, Integer> map1;
    private int[] array1;
    private Properties properties1;
    public List<String> getList1() {
        return list1;
    }
    public void setList1(List<String> list1) {
        this.list1 = list1;
    }
    public Set<UserModel> getSet1() {
        return set1;
    }
    public void setSet1(Set<UserModel> set1) {
        this.set1 = set1;
    }
    public Map<String, Integer> getMap1() {
        return map1;
    }
    public void setMap1(Map<String, Integer> map1) {
        this.map1 = map1;
    }
    public int[] getArray1() {
        return array1;
    }
    public void setArray1(int[] array1) {
        this.array1 = array1;
    }
    public Properties getProperties1() {
        return properties1;
    }
    public void setProperties1(Properties properties1) {
        this.properties1 = properties1;
    }
    @Override
    public String toString() {
        return "DiOtherTypeModel{" +
            "list1=" + list1 +
            ", set1=" + set1 +
            ", map1=" + map1 +
            ", array1=" + Arrays.toString(array1) +
            ", properties1=" + properties1 +
            '}';
    }
}
<?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-4.3.xsd">
    <bean id="user1" class="com.javacode2018.lesson001.demo5.UserModel"/>
    <bean id="user2" class="com.javacode2018.lesson001.demo5.UserModel"/>
    <bean id="diOtherType"
          class="com.javacode2018.lesson001.demo5.DiOtherTypeModel">
        <!-- 注入java.util.List对象 -->
        <property name="list1">
            <list>
                <value>Spring</value>
                <value>SpringBoot</value>
            </list>
        </property>
        <!-- 注入java.util.Set对象 -->
        <property name="set1">
            <set>
                <ref bean="user1"/>
                <ref bean="user2"/>
                <ref bean="user1"/>
            </set>
        </property>
        <!-- 注入java.util.Map对象 -->
        <property name="map1">
            <map>
                <entry key="路人甲Java" value="30"/>
                <entry key="路人" value="28"/>
            </map>
        </property>
        <!-- 注入数组对象 -->
        <property name="array1">
            <array>
                <value>10</value>
                <value>9</value>
                <value>8</value>
            </array>
        </property>
        <!-- 注入java.util.Properties对象 -->
        <property name="properties1">
            <props>
                <prop key="key1">java高并发系列</prop>
                <prop key="key2">mybatis系列</prop>
                <prop key="key3">mysql系列</prop>
            </props>
        </property>
    </bean>
</beans>

七、依赖注入之自动注入(autowire)

xml中可以在bean元素中通过autowire属性来设置自动注入的方式:
<bean id="" class="" autowire="byType|byName|constructor|default" />
  1. byteName:按照名称进行注入

  2. byType:按类型进行注入

  3. constructor:按照构造方法进行注入

  4. default:默认注入方式

八、通过depend-on干预bean创建和销毁顺序

depend-on使用方式:
<bean id="bean1" class="" depend-on="bean2,bean3,bean4" />
depend-on:设置当前bean依赖的bean名称,可以指定多个,多个之间可以用”,“进行分割,上面不管bean2,bean2,bean4在任何地方定义,都可以确保在bean1创建之前,会先将bean2,bean3,bean4创建好,表示bean1依赖于这3个bean,可能bean1需要用到bean2、bean3、bean4中生成的一些资源或者其他的功能等,但是又没有强制去在bean1类中通过属性定义强依赖的方式去依赖于bean2、bean3、bean4;当然销毁的时候也会先销毁当前bean,再去销毁被依赖的bean,即先销毁bean1,再去销毁depend-on指定的bean。

九、primary=true优先注入

可以通过这个属性来指定当前bean为主要候选者,当容器查询一个bean的时候,如果容器中有多个候选者匹配的时候,此时spring会返回主要的候选者。
<bean id="bean1" class="" primary="true" />

十、autowire-candidate是否作为候选bean

autowire-candidate:设置当前bean在被其他对象作为自动注入对象的时候,是否作为候选bean,默认值是true。false表示不作为候选bean
<bean id="bean1" class="" autowire-candidate="false"/>


参考:路人甲-Spring系列上。

版权声明

非特殊说明,本文由Zender原创或收集发布,欢迎转载。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

作者文章
热门