二.Spring反转控制(IOC)

    技术2026-03-16  8

    二.IOC

    1.简介

    Inversion of Control

    反转控制

    凡是你所需要的对象并不是由自己所准备的

    而是由其他组件控制你所获取的对象的时候

    简单来讲:我们所需要进行的操作是收到其他组件控制的

    这种情况就称之为反转控制

    注重的是结果

    2.DI

    Dependency injection

    依赖注入

    凡是你所需要使用的对象的属性的值不是由自己所准备的

    而是由其他组件控制你的属性的值的时候

    简单来讲:我们所需要操作的过程是收到其他组件所控制的

    这种情况称之为依赖注入

    主要的是过程

    3.IDEA中使用Spring

    初始情况下,IDEA中无法自动创建Spring的配置文件

    但是当IDEA中对应的工程导入了Spring的基本依赖的时候

    此时IDEA将会提供自动创建Spring配置文件的方式

    导入Spring基本依赖

    <properties> <spring.version>5.2.5.RELEASE</spring.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> </dependencies>

    4.Spring配置文件

    <bean id="userDao" class="dao.impl.UserDaoImpl"></bean> <!-- id属性:当前bean的key class属性:当前bean所对应的Java类的包名.类名 标签体:配置当前bean的属性的值 --> <bean id="userService" class="service.impl.UserServiceImpl"> <!-- property标签:配置某一个属性,一个property标签对应一个属性 name属性:当前的属性名是谁 value属性:为当前的属性赋予一个简单值 ref属性:其他bean的引用 表示当前的属性的值是另一个由Spring所管理的bean 此时ref属性的值对应的是所管理的bean的id属性值 --> <property name="userDao" ref="userDao"/> </bean>

    5.加载Spring容器

    BeanFactory Spring容器核心用于解析Spring容器读取容器中的内容 ApplicationContext BeanFactory的子接口简化了解析的操作使用ClassPathXmlApplicationContext实现类 其参数可以是一个字符串也可以是一个字符串数组也可以是一个可变长字符串还支持通配符 public static void main(String[] args) { Resource resource = new ClassPathResource("applicationContext.xml"); // BeanFactory bf = new XmlBeanFactory(resource); // // UserService userService = (UserService) bf.getBean("userService"); // userService.login(); // // ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); // ApplicationContext ac = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml","applicationContext2.xml"}); // ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml","applicationContext2.xml"); ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext*.xml"); UserService userService = (UserService) ac.getBean("userService"); userService.login(); }

    6.装配

    6-1 简单值装配

    简单值:基本数据类型、String、包装类型、Class、Resource

    <!-- 简单值装配方式一 --> <bean id="someBean" class="ioc01.SomeBean"> <!-- value属性:为当前指定的属性赋予一个简单值 --> <property name="id" value="1"/> <property name="name" value="admin"/> <property name="salary" value="1000.0"/> <property name="c" value="java.lang.String"/> </bean> <!-- 简单值装配方式二 --> <bean id="someBean2" class="ioc01.SomeBean"> <!-- 可以使用value子标签进行简单值装配,效果与value属性一致 --> <property name="id"> <value>2</value> </property> <property name="name"> <value>alice</value> </property> <property name="salary"> <value>2000.0</value> </property> <property name="c"> <value>java.lang.Exception</value> </property> </bean>

    6-2 其他bean的引用

    当当前的bean中的属性的值是另一个已经存在的bean的实例的时候

    可以使用ref属性或者ref标签进行其他bean的引用

    <!-- 其他bean的引用 --> <bean id="otherBean" class="ioc02.OtherBean"> <property name="id" value="1"/> <property name="name" value="admin"/> </bean> <!-- 方式一 --> <bean id="someBean" class="ioc02.SomeBean"> <!-- ref属性:指向当前容器中已经存在的另一个bean的id属性值 --> <property name="otherBean" ref="otherBean"/> </bean> <!-- 方式二 --> <bean id="someBean2" class="ioc02.SomeBean"> <!-- ref子标签:指定当前属性所引用的bean bean属性:指向当前容器中已经存在的另一个bean的id属性值 --> <property name="otherBean"> <ref bean="otherBean"></ref> </property> </bean>

    6-3 集合类型的装配

    集合类型:List、Set、Map、Properties、Array

    这些集合属性又分为两种类型

    List、Set、Array相当于链表结构

    Map、Properties相当于键值对结构

    同一种结构的类型可以混用

    链表结构的集合类型当元素的值有且仅有一个的时候,可以使用简单值的方式进行装配

    <!-- 集合类型装配 --> <bean id="someBean" class="ioc03.SomeBean"> <!-- 使用list标签对List类型的属性进行装配 该标签存在无数个value子标签 每一个value子标签装配一个元素的值 --> <property name="list"> <list> <value>l1</value> <value>l2</value> <value>l3</value> </list> </property> <property name="set"> <set> <value>s1</value> <value>s2</value> <value>s3</value> </set> </property> <property name="arr"> <array> <value>s1</value> <value>s2</value> <value>s3</value> </array> </property> <property name="map"> <map> <entry key="k1" value="m1"></entry> <entry key="k2" value="m2"></entry> <entry key="k3" value="m3"></entry> </map> </property> <property name="properties"> <props> <prop key="kp1">p1</prop> <prop key="kp2">p2</prop> <prop key="kp3">p3</prop> </props> </property> </bean> <!-- 同一种结构的集合属性可以混用 --> <bean id="someBean2" class="ioc03.SomeBean"> <!-- 使用list标签对List类型的属性进行装配 该标签存在无数个value子标签 每一个value子标签装配一个元素的值 --> <property name="list"> <list> <value>l1</value> <value>l2</value> <value>l3</value> </list> </property> <property name="set"> <list> <value>s1</value> <value>s2</value> <value>s3</value> </list> </property> <property name="arr"> <list> <value>s1</value> <value>s2</value> <value>s3</value> </list> </property> <property name="map"> <map> <entry key="k1" value="m1"></entry> <entry key="k2" value="m2"></entry> <entry key="k3" value="m3"></entry> </map> </property> <property name="properties"> <map> <entry key="kp1" value="p1"></entry> <entry key="kp2" value="p2"></entry> <entry key="kp3" value="p3"></entry> </map> </property> </bean>

    7.实例化

    实例化–>DI–>初始化–>使用–>销毁

    7-1 实例化的途径

    无参构造函数实例化对象

    <bean id="someBean" class="ioc04.SomeBean"></bean>

    有参构造函数实例化对象

    通过constructor-arg标签来实现有参构造函数的配置一个标签对应构造函数中的一个参数index属性:指定当前参数的索引位置 索引从0开始如果不设置该属性,默认按照参数顺序进行执行 type属性:指定当前参数的类型 该属性一般可以不配置,Spring自动检测参数的类型 <bean id="someBean2" class="ioc04.SomeBean"> <constructor-arg index="1" type="java.lang.String"> <value>admin</value> </constructor-arg> <constructor-arg index="0" type="java.lang.Integer"> <value>1</value> </constructor-arg> </bean>

    静态工厂实例化对象

    静态工厂创建对象的时候,class指向的是当前对应的工厂类的class但是如果仅仅只是通过class指向工厂,根本无法获取到我们想要的bean该bean是根据工厂中对应的方法获取的因此需要通过factory-method进行指定所调用的静态方法名 <bean id="userService" class="ioc05.ObjectFactory" factory-method="getObject"></bean> <bean id="map" class="java.lang.System" factory-method="getenv"></bean> <bean id="javaHome" class="java.lang.System" factory-method="getenv"> <constructor-arg> <value>JAVA_HOME</value> </constructor-arg> </bean>

    实例工厂实例化对象

    该bean是通过工厂bean中的方法获取的因此该bean需要调用指定的工厂bean中的方法在配置中需要指定所使用的工厂bean与工厂方法factory-bean 表示当前所调用的工厂bean是谁其值是当前容器所管理的工厂bean的id值 factory-method 生产当前bean的工厂bean所使用的方法名此处为实例方法

    7-2 初始化销毁方法

    init-method 将bean中的指定的方法设置为初始化方法当方法被设置为初始化方法治好,其执行时可以获取到DI的值该方法将会在DI之后,使用之间进行自动执行 destroy-method 将bean中指定的方法设置为销毁方法将方法设置为销毁方法之后,Spring可以主动进行销毁此时的销毁是强制执行的,该方法是由ClassPathXmlApplicationContext实现的在ApplicationContext接口中没有提供销毁方法因此,必须直接定义实现类ClassPathXmlApplicationContext提供了两种销毁方法 destroy() 调用方法的时候立刻销毁不关心当前的bean是否已经使用完毕因此,调用方法之后,如果仍然使用容器中的bean会报错 registerShutdownHook() 调用方法的时候不会立刻销毁当虚拟机彻底停止之后才会进行销毁因此,调用方法之后,仍然可以使用容器中的bean <bean id="someBean" class="ioc07.SomeBean" init-method="a" destroy-method="b"> <property name="name" value="admin"/> </bean> public class Test { public static void main(String[] args) { // ApplicationContext ac = new ClassPathXmlApplicationContext("ioc07/spring.xml"); // SomeBean someBean = (SomeBean) ac.getBean("someBean"); // System.out.println(someBean); // someBean.a(); // 执行销毁方法 // 在java中,我们只能对销毁方法做建议 // 无法强制其执行 // Runtime.getRuntime().gc(); ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("ioc07/spring.xml"); // 销毁方式一 ac.destroy(); // 销毁方式二 // ac.registerShutdownHook(); // System.out.println("Test.main"); // SomeBean someBean = (SomeBean) ac.getBean("someBean"); // System.out.println(someBean); while (true){ } } }

    7-3 实例化时机

    BeanFactory 延迟实例化解析容器的时候并不会对容器中配置的bean进行实例化只有当使用到对应的bean的时候,才会进行实例化 ApplicationContext 预先实例化解析容器的时候就已经对容器中配置的所有的bean进行了实例化而不需要等到使用时才去实例化可以更改其实例化的时机通过lazy-init属性可以更改实例化时机 其值有三种default:默认值,相当于falsetrue:延迟实例化false:预先实例化 <bean id="someBean" class="ioc08.SomeBean" lazy-init="true"></bean>

    7-4 组件作用域

    默认情况下,IOC容器中所有的bean都是单例的

    有些时候,我们需要多个不同的实例

    可以通过scope属性更改组件作用域

    使得bean不再是单例的

    scope属性:组件作用域,值有两种 prototype:每一次调用都会生成一个新的实例singleton:默认值,单例 <bean id="someBean" class="ioc09.SomeBean" scope="prototype"></bean> <bean id="someBean2" class="ioc09.SomeBean"></bean>

    8.在bean中获取当前容器

    将当前的bean实现ApplicationContextAware接口

    重写其中的setApplicationContext方法

    将其作为一个注入操作

    其容器的实例是由Spring帮我们传递的

    传递到对应的setApplicationContext方法的参数中

    将该实例传递到当前的全局变量中,则当前bean中可以获取到当前容器的实例

    public class SomeBean implements ApplicationContextAware { private ApplicationContext ac; @Override public void setApplicationContext(ApplicationContext ac) throws BeansException { this.ac = ac; } public void doSome(){ // ApplicationContext ac = new ClassPathXmlApplicationContext("ioc10/spring.xml"); System.out.println("doSome-ac:"+ac); } }

    9.继承配置

    继承配置存在两种情况

    多个bean,拥有相同的属性,且这些属性具有相同的属性值 此时在容器中可以使用一个抽象的父类对这些属性进行注入这个父类是一个虚拟的、抽象的、并不是真实存在的使用abstract="true"配置当前父类 表示当前的bean并不是真实存在的 在父类中可以对属性进行注入当父类被继承之后,在父类中可以配置子类的所有属性的注入子类通过parent="父类id"配置继承指定的父类 当子类继承父类之后会自动继承父类中一切的配置如果子类中存在了与父类相同的配置则优先使用子类中的配置,相当于重写操作 <bean id="fatherBean" abstract="true"> <property name="id" value="1"></property> <property name="name" value="admin"></property> </bean> <bean id="someBean" class="ioc11.SomeBean" parent="fatherBean"> <property name="password" value="123456"></property> </bean> <bean id="otherBean" class="ioc11.OtherBean" parent="fatherBean"> <property name="salary" value="8000.0"></property> </bean> 同一个bean,在容器中存在多个该bean的实例 在这些bean中,有些属性值相同,有些属性值不相同此时可以定义一个抽象的父类将这些bean中相同的配置均在父类中进行配置所有的bean继承该父类,将会继承父类中的一切配置 <bean id="fatherBean" class="ioc11.SomeBean" abstract="true"> <property name="name" value="admin"/> <property name="password" value="123456"/> </bean> <bean id="someBean" parent="fatherBean"> <property name="id" value="1"/> </bean> <bean id="someBean2" parent="fatherBean"> <property name="id" value="2"/> </bean> <bean id="someBean3" class="ioc11.SomeBean" parent="fatherBean"> <property name="id" value="3"/> </bean>

    10.自动装配

    Autowire

    只有在其他bean的引用的时候进行使用

    通过autowire属性进行自动装配的使用

    其值常用的有两种

    byType 根据bean的类型进行自动装配在对当前bean的属性进行注入的时候会根据当前属性的类型在当前的容器查找与该属性类型一致的bean当找到了类型一致的bean的时候,会将该bean注入到该属性中该方式存在一种bug 当在容器中找到了多个与属性类型一致的bean的时候此时会报错 byName 根据bean的id进行自动装配在对当前bena的属性进行注入的时候会根据当前属性的名字在当前的容器中查找所有bean的id找到与属性名一致的id的值此时会将该id所在bean注入到对应的属性中由于id属性值具有唯一性,因此不可能找到多个bean <bean id="otherBean" class="ioc12.OtherBean"> <property name="id" value="1"/> <property name="name" value="admin"/> </bean> <bean id="otherBean2" class="ioc12.OtherBean"> <property name="id" value="2"/> <property name="name" value="tom"/> </bean> <!--<bean id="someBean" class="ioc12.SomeBean">--> <!--<property name="otherBean" ref="otherBean"/>--> <!--</bean>--> <!--<bean id="someBean" class="ioc12.SomeBean" autowire="byType"></bean>--> <bean id="someBean" class="ioc12.SomeBean" autowire="byName"></bean>

    11.Resource

    Resource属于简单值装配

    该类型一般是操作文件的

    在为Resource类型注入值的时候,其值一般指向的是一个文件的路径

    值的格式:classpath:路径名/文件名

    //File f = new File("ioc13/a.txt"); //Resource resource = new ClassPathResource("ioc13/a.txt"); //File file = resource.getFile(); //System.out.println(file.getName()); //System.out.println(file.length()); // //InputStream in = resource.getInputStream(); //BufferedReader br = new BufferedReader(new InputStreamReader(in)); //System.out.println(br.readLine()); ApplicationContext ac = new ClassPathXmlApplicationContext("ioc13/spring.xml"); //Resource resource = ac.getResource("ioc13/a.txt"); //Resource resource = ac.getResource("classpath:ioc13/a.txt"); SomeBean someBean = (SomeBean) ac.getBean("someBean"); Resource resource = someBean.getResource(); InputStream in = resource.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(in)); System.out.println(br.readLine()); <bean id="someBean" class="ioc13.SomeBean"> <property name="resource" value="classpath:ioc13/a.txt"></property> </bean>

    12.FactoryBean

    12-1 简介

    该bean本质上是一个工厂

    该bean并不是用于创建自己的

    而是为了创建另一个bean

    在某些时候,某些bean的实例化过程比较麻烦

    但是对于使用者而言,只关心最终生成的bean

    需要的仅仅只是最终的bean

    其过程使用者并不关心

    12-2 开发步骤

    创建一个Java类 该类属于FactoryBean,用于生产一个对应的bean该类要求实现FactoryBean接口重写其中的三个方法 getObject():生产bean的过程getObjectType():生产的bean的类型isSingleton():生产的bean是否是单例的 public class DateFactoryBean implements FactoryBean { @Override public Object getObject() throws Exception { Calendar c = Calendar.getInstance(); Date date = c.getTime(); return date; } @Override public Class<?> getObjectType() { return Date.class; } @Override public boolean isSingleton() { return false; } } 配置FactoryBean 在IOC容器中配置你所需要的的FactoryBean该FactoryBean是用于产生某个具体的bean的因此,配置的FactroyBean就相当于最终所需要的的bean只是其class指向的是当前的FactoryBean <bean id="date" class="ioc14.DateFactoryBean"></bean>

    13.后处理bean

    13-1 简介

    对当前容器中的所有的bean做一个后处理

    在初始化之前或者之后做一些额外的处理

    实例化–>DI–>postProcessBeforeInitialization–>初始化

    –>postProcessAfterInitialization–>使用–>销毁

    后处理bean并不是针对某一个bean进行处理

    而是针对整个IOC容器

    后处理bean是对整个IOC容器中所有的bean都生效

    13-2 开发步骤

    创建一个Java类 该类实现BeanPostProcessor接口根据Spring版本不同,处理也不同 Spring5之前,必须重写接口中的两个方法Spring5之后,可以选择性的重写某个方法 public class SomeBeanPostProcessor implements BeanPostProcessor { /** * 在初始化之前做后处理 * @param bean 当前需要处理的bean * @param id 当前需要处理的bean的id * @return * @throws BeansException */ @Override public Object postProcessBeforeInitialization(Object bean, String id) throws BeansException { // 判断当前正在处理的bean是谁 // 对SomeBean的name做大写处理 if(bean instanceof SomeBean){ SomeBean someBean = (SomeBean) bean; someBean.setName(someBean.getName().toUpperCase()); // return someBean; } // 对OtherBean的name做小写处理 if("otherBean".equals(id)){ OtherBean otherBean = (OtherBean) bean; otherBean.setName(otherBean.getName().toLowerCase()); // return otherBean; } return bean; } /** * 在初始化之后做后处理 * @param bean * @param id * @return * @throws BeansException */ @Override public Object postProcessAfterInitialization(Object bean, String id) throws BeansException { return bean; } } 配置后处理bean 由于后处理bean并不是针对某一个bean,而是为所有的bean服务因此配置的后处理bean不需要id属性只需要指定谁是后处理bean即可 <bean id="someBean" class="ioc15.SomeBean"> <property name="id" value="1"/> <property name="name" value="admin"/> </bean> <bean id="otherBean" class="ioc15.OtherBean"> <property name="name" value="Jack"/> </bean> <!-- 配置后处理bean --> <bean class="ioc15.SomeBeanPostProcessor"/>

    14.属性编辑

    将一个对象使用简单值装配的方式进行值的注入

    可以通过编写转换规则将对象与简单值进行转换

    14-1 编写转换规则

    编写转换规则的时候,对于需要的转换的简单值

    其格式必须有限制

    创建一个Java类 该类继承PropertyEditorSupport选择重写你所需要的的方法 void setAsText(String text) 将一个简单值转换成对象 String getAsText() 将一个对象转换成简单值 在操作的时候,提供的方法中只能操作简单值的数据如果想要设置/获取对象的数据,需要使用内置的方法 setValue(Object obj) 设置转换后的对象将简单值转换成对象之后,通过该方法返回对应的对象 getValue() 获取需要转换的对象将对象转换成简单值的时候,获取需要转换的对象是谁 public class AddressEditorSupport extends PropertyEditorSupport { /** * 将简单值text转换成Address对象 * 对于简单值的格式存在要求,其格式必须为:province-city * @param text * @throws IllegalArgumentException */ @Override public void setAsText(String text) throws IllegalArgumentException { String[] arr = text.split("-"); Address address = new Address(); address.setProvince(arr[0]); address.setCity(arr[1]); setValue(address); } }

    14-2 注册转换规则

    将转换规则注册到Spring容器中

    在容器中对转换规则进行配置

    <bean id="someBean" class="ioc16.SomeBean"> <property name="id" value="1"/> <property name="name" value="admin"/> <property name="address" value="江苏-南京"/> <property name="date" value="2020-05-29"/> </bean> <!-- 注册转换规则 --> <!-- 使用Spring提供的后处理bean对转换规则进行注册 --> <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <!-- 通过customEditors属性注册所用到的转换规则 可以注册无数个转换规则,其值是一个map类型 key:指向的是所使用到的对象的类型 value:指向的是所使用的转换规则是谁 --> <property name="customEditors"> <map> <entry key="ioc16.Address" value="ioc16.AddressEditorSupport"/> <entry key="java.util.Date" value="ioc16.DateEditorSupport"/> </map> </property> </bean>

    15.访问properties文件

    driver=com.mysql.jdbc.Driver url=jdbc:mysql://127.0.0.1:3306/ums?useUnicode=true&characterEncoding=utf-8 username=root password=root public class SomeBean { private String driver; private String url; private String username; private String password; <!-- 访问properties文件 --> <!-- 方式一:使用Spring提供的后处理bean进行访问 该方式已过时 --> <!--<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">--> <!--<property name="location" value="classpath:ioc17/dataSource.properties"/>--> <!--</bean>--> <!-- 方式二:使用context命名空间提供的方法 --> <context:property-placeholder location="classpath:ioc17/dataSource.properties"/> <bean id="someBean" class="ioc17.SomeBean"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </bean>
    Processed: 0.014, SQL: 9