Spring IoC 与容器的初始化

Spring IoC 与容器的初始化

IoC 解决了到底解决了什么?

控制翻转 Inversion of Control

控制-对象的创建者

翻转-角色的转变

凡是在高级的编程语言中面对程序的设计总会提到的一点就是,低耦合、高内聚。那么对于低耦合与高内聚应该怎么去理解这两点呢?

低耦合强调的是模块应该尽可能的单一,模块内部细节对内私有封装,对外只暴露对应的接口,利用可配置替换的模块更换其“硬编码”的操作

image-20230213093903851

image-20230213094308984

image-20230213094337277

image-20230213094416327

public interface UserService {
    void run();
}
/**
 * @author peggy
 * @data 2023/2/13 10:09
 */
public class UserServiceImpl implements UserService {
    public void run() {
        System.out.println("小孩跑起来了");
    }
}
<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
​
    <!-- services -->
    <bean id="userService" class="com.peggy.service.impl.UserServiceImpl"/>
    <!-- more bean definitions for services go here -->
​
</beans>

Spring 的容器创建的时机

关于 scope 属性的配置

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
​
    <!--多例模式-->
    <bean id="userServicePrototype" scope="prototype" class="com.peggy.service.impl.UserServiceImpl"/>
    
    <!--默认单例模式-->
    <!--创建的所有的对象都是同一个对象-->
    <bean id="userServiceSingleton"  class="com.peggy.service.impl.UserServiceImpl"/>
</beans>
​
public class App {
    public static void main( String[] args ) {//        加载配置文件
        ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml");//        获取加载资源
        UserService userServiceSingleton1 = (UserService) applicationContext.getBean("userServiceSingleton");
        UserService userServiceSingleton2 = (UserService) applicationContext.getBean("userServiceSingleton");
        UserService userServiceSingleton3 = (UserService) applicationContext.getBean("userServiceSingleton");System.out.println("userServiceSingleton1 对象地址:"+userServiceSingleton1);
        System.out.println("userServiceSingleton2 对象地址:"+userServiceSingleton2);
        System.out.println("userServiceSingleton3 对象地址:"+userServiceSingleton3);UserService userServicePrototype1 = (UserService) applicationContext.getBean("userServicePrototype");
        UserService userServicePrototype2 = (UserService) applicationContext.getBean("userServicePrototype");
        UserService userServicePrototype3 = (UserService) applicationContext.getBean("userServicePrototype");System.out.println("userServicePrototype1 对象地址:"+userServicePrototype1);
        System.out.println("userServicePrototype2 对象地址:"+userServicePrototype2);
        System.out.println("userServicePrototype3 对象地址:"+userServicePrototype3);}
}

image-20230213104109801

我们可以发现当我们在 Spring 中我们采用默认的配置,通过 ApplicationContext 对象获取 id 指定的 Bean 对象都是同一个对象(对象的地址相同)

如果我们将 scope 属性设置为 prototype 后,再次通过 id 指定获取的 Bean 对象不在是同一个对象

那么对于这两种方式在初始化的时候,在 Spring 中是什么时候创建的对象呢?我们对 UserService 接口的实现类的构造方法进行改造测试

/**
 * @author peggy
 * @data 2023/2/13 10:09
 */
public class UserServiceImpl implements UserService {
​
    public UserServiceImpl() {
        System.out.println("UserServiceImpl 对象被创建");
    }
​
    public void run() {
        System.out.println("小孩跑起来了");
    }
}
    public static void main( String[] args ) {
​
//        加载配置文件
        ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml");
​
        System.out.println("采用Spring单例模式加载。。。。。。。");
//        获取加载资源
        UserService userServiceSingleton1 = (UserService) applicationContext.getBean("userServiceSingleton");
        System.out.println("userServiceSingleton1 对象地址:"+userServiceSingleton1);
​
        System.out.println("采用Spring多例模式加载。。。。。。。");
        UserService userServicePrototype1 = (UserService) applicationContext.getBean("userServicePrototype");
        System.out.println("userServicePrototype1 对象地址:"+userServicePrototype1);
​
    }

image-20230213105654593

public class App {
    public static void main( String[] args ) {
​
//        加载配置文件
        ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml");
        
        System.out.println("采用Spring多例模式加载。。。。。。。");
        UserService userServicePrototype1 = (UserService) applicationContext.getBean("userServicePrototype");
        System.out.println("userServicePrototype1 对象地址:"+userServicePrototype1);
​
    }
}

image-20230213110809425

最终的输出结果如图所示

可以发现我们的 Spring 在第一次加载我们的 applicationContext.xml 配置文件的时候,已经初始化好了一个 UserServiceImpl 的 Bean 对象。

而当我们指定默认配置的 id 的时候,会直接从初始化好的 Bean 容器中获取对象。

当我们指定多例模式的配置的 id 获取 UserServiceImpl 对象的时候,就会创建一个新的对象(该对象已经不是由 Spring 进行管理)向我们的引用,而不是直接从 Bean 容器中获取当前之前已经创建好的对象。

关于Spring Bean 的生命周期的控制管理

<!-- 格式 -->
<bean init-method="init" destroy-method="destroy"/>
  • init-method 属性指定 Bean 对象在初始化创建的时候优先调用

  • destroy-method 属性指定 Bean 对象在结束销毁的时候进行调用

案例

在单例的情况下 init-method 与 destroy-method

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
​
    <!--单例模式创建 Bean 对象-->
    <bean id="userService" init-method="init" destroy-method="destroy" class="com.peggy.service.impl.UserServiceImpl"/>
</beans>
public class App {
    public static void main( String[] args ) {
​
        ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml");
​
        UserService userService = (UserService) applicationContext.getBean("userService");
        System.out.println("userService对象地址:"+userService);
    }
}
​

image-20230213112348507

可以发现只有 init 方法执行了,而我们的 destroy 方法好像没有执行,这是为什么呢?

这是因为,我们的 Java 虚拟机的结束时我们的 Spring 中的 Bean 容器还没有来的及释放我们的资源,所以看不到 destroy 的执行结果。

那么我们就提前让我们的 Spring 对象结束,资源提前释放就可以看到 destroy 的执行结果。

由于 ApplicationContext 对象中没有提供关闭 Spring 对象资源的方法,所以我们需要调用,配置文件加载的对象 ClassPathXmlApplicationContext 中的 close 关闭资源。

public class App {
    public static void main( String[] args ) {
​
        ClassPathXmlApplicationContext  applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml");
​
        UserService userService = (UserService) applicationContext.getBean("userService");
        System.out.println("userService对象地址:"+userService);
​
        applicationContext.close();
    }
}

image-20230213113023860

此时的 destroy 的方法在虚拟机结束之前,Spring 容器销毁之后执行

如果我们将 Spring 的 Bean 对象修改成多例模式,那么在创建 UserServiceImpl 的时候是怎么样的呢?

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
​
    <!--单例模式创建 Bean 对象-->
    <bean id="userService" scope="prototype" init-method="init" destroy-method="destroy" class="com.peggy.service.impl.UserServiceImpl"/>
</beans>
public class App {
    public static void main( String[] args ) {
​
        ClassPathXmlApplicationContext  applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml");
​
        UserService userService1 = (UserService) applicationContext.getBean("userService");
        System.out.println("userService1对象地址:"+userService1);
​
        UserService userService2 = (UserService) applicationContext.getBean("userService");
        System.out.println("userService2对象地址:"+userService2);
​
        UserService userService3 = (UserService) applicationContext.getBean("userService");
        System.out.println("userService3对象地址:"+userService3);
​
        applicationContext.close();
    }
}

image-20230213123349321

我们发现只有对象创建的时候调用了一次 init 初始化的对象,而没有在 Spring 容器销毁的时候执行 destroy 方法。

其实这是因为,当我将 Spring 的 scope 修改为 prototype 时,创建的 UserServiceImpl 已经不再受 Spring 管理,只是一个普通的对象。

关于 BeanFactory 与 ApplicationContext 接口

image-20230213150211053

通过继承关系可以发现,BeanFactory接口是最早的 Spring 容器的原型,而后续在 BeanFactory 的基础上延续出了 ApplicaiotnContext 接口。

image-20230213150541702

在 BeanFactory 中已经存在了 getBean 的方法,那么在 BeanFactory接口中的 getBean 与 ApplicationCpntext接口中的 getBean 有什么区别呢?

我们使用与原先在探究 Spring 容器单例与多例的代码进行再次测试

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="userService" class="com.peggy.service.impl.UserServiceImpl"/>
</beans>
/**
 * @author peggy
 * @data 2023/2/13 10:09
 */
public class UserServiceImpl implements UserService {
​
    public UserServiceImpl() {
        System.out.println("UserServiceImpl 对象被创建");
    }
​
    public void run() {
        System.out.println("小孩跑起来了");
    }
}
  • 使用 ApplicationContext 获取 Bean 对象
public class App {
    public static void main( String[] args ) {
​
        ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml");
        System.out.println("开始执行 getBean 方法");
        applicationContext.getBean("userService");
​
    }
}

image-20230213151813935

  • 使用 BeanFactory 获取 Bean 对象
public class App {
    public static void main( String[] args ) {
        Resource resource = new ClassPathResource("applicationContext.xml");
        BeanFactory beanFactory = new XmlBeanFactory(resource);
        System.out.println("开始执行 getBean 方法");
        beanFactory.getBean("userService");
    }
}

image-20230213152418171

通过上面的执行结果其实我们就可以得出 BeanFactory 与 ApplicationContext 之间的差别

BeanFactory 只要在调用 getBean 方法的时候才会初始化我们的 Bean 对象

ApplicationContext 在我们首次读取加载 Spring 的配置文件的时候就会初始化单例的 Bean 对象

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值