经典框架 Spring Framework 学习笔记之 IoC( Inversion of Control 反转控制)

本文详细介绍了Spring框架的核心特性,包括IoC容器的作用、AOP编程、声明式事务、简化JavaEEAPI使用、生态支持、与其他框架集成以及测试友好性。同时涵盖了Spring的启动方式、配置方法、Bean作用域、FactoryBean和条件注解的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

写在前面,Spring 简介
Spring 是以 IoC 和 AOP 为核心的开源框架,目前使用最多的 Java EE 企业应用开源框架。

为什么在项目中使用Spring?

简单的回答好用啊!
1.方便解耦,简化开发:通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免了硬编码所造成的过度程序耦合。由于IoC解决对象之间的耦合问题,使得开发者更容易从单例模式类、属性文件解析等 底层编码的困扰中解脱出来,从而将更多的精力用于思考业务逻辑层面的编码设计。AOP在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复。
2.强大的AOP编程 与 声明式事务支持:可以轻松实现许多不容易用传统OOP(面向对象编程)实现的功能,如安全、事务、日志等。声明式的事务管理方式相比于硬编码方式,大大提高了框架的灵活性和易用性,使得开发效率和质量能得到显著提升。
3.降低Java EE API 的使用难度:Spring对Java EE API(如JDBC、JavaMail等)进行了封装,使得这些API的使用难度大大降低。难度降低,意味着更容易获得的开发成本
4.完整的生态支持:Spring几乎能够满足所有的业务场景,而且 Spring全家桶里面还提供了非常多的这种工具,比如Spring Security 处理安全问题、Spring Data 简化数据访问、Spring Cloud 提供微服务的支持等等。
5.方便集成各种优秀框架(其中支持主流数据访问):Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如Struts、Hibernate、MyBatis、Quartz等)的直接支持,降低了框架使用难度。像原来经典的 SSH、SSM 等框架组合的流行,就是很好证明。
6.方便测试:Spring提供了对JUnit4等测试框架的支持,可以通过注解 @Test 方便地测试 Spring 程序,使得测试工作变得更加简单和高效。
7.源码是经典的学习范例:Spring的源代码设计精妙,结构清晰,体现了Java设计模式的灵活运用和Java技术的高深造诣,是学习Java技术的最佳实践典范。源码几乎面试必问
8.社区活跃:如果开发过程中遇到问题,网上搜一下,很容易找到解决方案。

做完了铺垫,开始记录Spring 的学习过程,下面先从 IoC开始。

Spring IOC 基础

1. 启动 IoC 容器的方式

  1. Java环境下启动IoC容器
  • ClassPathXmlApplicationContext:从类的根路径下加载配置文件(推荐使用)
  • FileSystemXmlApplicationContext:从磁盘路径上加载配置文件
  • AnnotationConfigApplicationContext:纯注解模式下启动Spring容器
    主要用于教学,需要讲授 Spring 原理,实际几乎用不到;
    在某些简单测试场景下,需要本地开发测试而不是 线上 Linux 环境下 Web 容器启动,此时可以考虑以上三种 启动方式。
  1. Web环境下启动IoC容器
  • 从xml启动容器,使用监听器启动Spring的IOC容器
  • 配置类启动容器

2. 基本用法

2.1 XML 配置,即标签
2.1.1 最简单的注入用法

<bean class=“org.boyjava.demo.model.User” id=“user”/>

<?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.xsd">

    <!--可以直接在这个地方向 Spring 容器注册 Bean ,此时Bean 的实例化实际上是默认的反射调用该Bean 的无参构造方法 -->
    <bean class="org.boyjava.demo.model.User" id="user"/>
</beans>

一旦注册成功,User 就已经被初始化了。

public class Demo01 {
    public static void main(String[] args) {
        // Spring 容器构建过程中,Bean 就已经被创建了
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    }
}
2.1.2 指定构造器注入
<bean class="org.boyjava.demo.model.User" id="user2">
    <!--通过构造器参数的下标去指定参数-->
    <constructor-arg index="0" value="1"/>
    <!--通过构造器参数的名字去指定参数-->
    <constructor-arg name="address" value="北京"/>
    <constructor-arg name="username" value="zhangsan"/>
</bean>
2.1.3 set方法注入
<bean class="org.boyjava.demo.model.User" id="user">
    <!--这个本质上实际上就是调用set方法-->
    <property name="id" value="99"/>
    <property name="username" value="lisi"/>
    <property name="address" value="上海"/>
</bean>
2.1.4 数组、集合等特殊属性注入
<bean class="org.boyjava.demo.model.Book" id="book">
    <property name="authors">
        <!--数组赋值-->
        <array>
            <value>zhangsan</value>
            <value>lisi</value>
        </array>
    </property>
    <property name="publishers">
        <list>
            <value>电子工业出版社</value>
            <value>机械工业出版社</value>
        </list>
    </property>
    <property name="info">
        <map>
            <entry key="price" value="20"/>
            <entry key="num" value="300"/>
        </map>
    </property>
</bean>
2.1.5 获取Bean 方式总结
User user2 = ctx.getBean("user2",User.class);

getBean() 这个方法就是跟Sping 容器去要一个 Bean,将来无论是通过何种方式去跟容器要一个 Bean 回来,底层实现都是 getBean () 方法

  • ctx.getBean(“根据名字获取 Bean,返回 Object,返回之后需要强转”) ctx.getBean(根据类型获取
  • Bean,这个要求该 Bean 在容器中只存在一个实例)
  • 无参呢?根据类型去获取,首先是根据类型查找 beanName,找到名字之后,再调用重载的 getBean(“beanName”)去获取bean 。
2.1.6 Bean 生命周期 的 钩子函数(初始化 和 销毁)

可自定义钩子函数,也可实现接口 InitializingBean, DisposableBean 。

2.2 Java配置,主要是@Bean 注解
2.2.1 通过 @Bean 注解向 Spring 容器注册 Bean
public class JavaConfig {

    /**
     * 这里 @Bean 的作用类似于刚才的 bean 标签,就表示向 Spring 容器中注册一个 Bean
     * 默认情况下,beanName 就是方法名
     * 
     * @Bean 注解中也有 name 表示表示为这个 bean 指定名称
     * initMethod 和 destroyMethod 为这个 bean 指定生命周期钩子函数
     * @return
     */
    @Bean("user2")
    User user() {
        User user = new User();
        user.setId(1);
        user.setUsername("zhangsan");
        return user;
    }
}

验证一下吧

main{
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
    User user = ctx.getBean("user2" , User.class );
    sout ("user = " + user );
}
2.2.2 @Configuration 注解(实现原理:AOP代理)
2.2.3 通过@Primary 注解 指定 Bean 的优先级
/**
 * 这个注解可以用来标记 Bean 的优先级,如果通过类型去 Spring 容器中查找 Bean 的话,
 * 那么标记了该注解的 Bean 会被优先返回。
 */
@Primary
User user(Book book) {
    User user = new User();
    user.setId(1);
    user.setUsername("zhangsan");
    user.setBook(book);
    return user;
}
2.2.4 @Bean z注解的 autowiredCandidate 属性
/**
 * autowireCandidate 表示这个 Bean 是否作为注入的候选的 Bean
 * 这个属性为 false 就表示这个 Bean 不作为候选 Bean,即其他 Bean 需要注入 User 的时候,不会查找到这个 Bean
 */
@Bean(value = "user",autowireCandidate = false)
User user(Book book) {
    User user = new User();
    user.setId(1);
    user.setUsername("zhangsan");
    user.setBook(book);
    return user;
}
2.3 包扫描
2.3.1 Spring 注解 @ComponentScan
@Configuration
//这个就是组件扫描,默认情况下,扫描的是当前配置类所在包下的所有 Bean
@ComponentScan(basePackages = "org.boyjava.demo")
public class JavaConfig {
}
2.3.2 XML里配置扫描标签
<!--指定要扫描的包,会自动将指定包中的所有组件注册到 Spring 容器中-->
<context:component-scan base-package="org.boyjava.demo"/>

3. Bean 的作用域

Bean 的作用域
Q:都是与容器绑定,一般情况下一个应用绑定一个 Spring 容器,那它们区别在哪里呢?
A:一个应用 允许存在多个容器。

3.1 XML配置单/多例

<bean class="org.boyjava.demo.User" id="user" scope="prototype"/>

3.2 @Bean + @Scope(“prototype”) 注解

@Configuration
public class JavaConfig {

    @Bean
    @Scope("prototype")
    User user() {
        return new User();
    }
}

3.3 singleton 与 prototype 比较

默认情况下,多次从 Spring 容器中获取到的 Bean 是同一个 Bean。

  • singleton:多次从 Spring 容器中获取到的是同一个 bean
  • prototype:每次从 Spring 容器中获取到的都是一个新的 bean

singleton 会在容器初始化的时候,就初始化 bean,而 prototype 则是在去获取 bean 的时候,才初始化 bean。

4. 工厂Bean

4.1 静态工厂

public class OkHttpClientStaticFactory {

    private static OkHttpClient client;

    static {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.setConnectTimeout$okhttp(5000);
        builder.setReadTimeout$okhttp(5000);
        client = builder
                .build();
    }

    public static OkHttpClient getInstance() {
        return client;
    }
}

<!--这个配置,将来向 Spring 容器中注册的就不是 OkHttpClientStaticFactory 类的实例,而是
OkHttpClientStaticFactory#getInstance 方法所返回的实例
-->
<bean class="org.boyjava.demo.OkHttpClientStaticFactory" factory-method="getInstance"/>

4.2 实例工厂

public class OkHttpClientFactory {
    private OkHttpClient okHttpClient;

    public OkHttpClient okHttpClient() {
        if (okHttpClient == null) {
            OkHttpClient.Builder builder = new OkHttpClient.Builder();
            builder.setConnectTimeout$okhttp(5000);
            builder.setReadTimeout$okhttp(5000);
            okHttpClient = builder
                    .build();
        }
        return okHttpClient;
    }
}

<bean class="org.boyjava.demo.OkHttpClientFactory" id="okHttpClientFactory"/>
<!--实例工厂必须要先获取到工厂类的实例,然后才能调用-->
<bean id="client2" factory-bean="okHttpClientFactory" factory-method="okHttpClient"/>

4.3 FactoryBean

以 okhttp 为例,说明 FactoryBean。

public class OkHttpClientFactoryBean implements FactoryBean<OkHttpClient> {
    @Override
    public OkHttpClient getObject() throws Exception {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.setConnectTimeout$okhttp(5000);
        builder.setReadTimeout$okhttp(5000);
        return builder
                .build();
    }

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

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

<!--这里向 Spring 容器中注册的也不是 OKHttpClientFactoryBean ,而是 其 方法getObject() 的返回值 -->
<bean class="org.boyjava.demo.OKHttpClientFactoryBean" id="client3"/>

// 如果想获取 OKHttpClientFactoryBean,而不是其方法getObject() 的返回值,那就需要在Java 代码中 id 值前边 添加 “&”符即可,如下代码示意。

// 获取 工厂 Bean
Object client3 = ctx.getBean("&client3");
4.3.1 FactoryBean的经典使用场景有哪些?

okhttp client、MyBatis SqlSessionFactory、shiro

4.3.2 FactoryBean 的特点

1. 工厂模式:FactoryBean 采用了工厂模式的思想,它充当了一个工厂的角色,用于创建和返回 Bean 实例。通过FactoryBean,我们可以将 Bean 的创建逻辑封装在 FactoryBean 的实现类中,从而实现 Bean 的解耦和灵活配置。
2. 延迟加载:FactoryBean 支持延迟加载,即只有在需要获取 Bean 实例时才会调用 FactoryBean 的 getObject() 方法创建 Bean。这种延迟加载的机制可以提高系统的性能和启动速度。
3. 支持多种类型的 Bean:FactoryBean 可以创建并返回多种类型的 Bean 实例,包括单例 Bean、原型 Bean 等。这使得 FactoryBean 具有更强的灵活性和可扩展性。
4. 支持自定义配置:FactoryBean 允许我们自定义 Bean 的创建和配置过程。通过实现 FactoryBean 接口,我们可以按照自己的需求来定义 Bean 的创建逻辑和配置信息。

5. 条件注解

5.1 条件注解案例

可以定义不同的条件,当条件满足的时候,这个 Bean 才会被注册到 Spring 容器中。

public class LinuxCondition implements Condition {
    /**
     * 这个方法返回 true,就表示条件是满足的,返回 false,就表示条件不满足
     *
     * @param context  the condition context
     * @param metadata the metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
     *                 or {@link org.springframework.core.type.MethodMetadata method} being checked
     * @return
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //获取当前操作系统名字
        String osName = context.getEnvironment().getProperty("os.name");
        return osName.toLowerCase().contains("linux");
    }
}

public class WindowsCondition implements Condition {
    /**
     * 参见LinuxCondition即可
     * @return
     */

    }
}

当条件满足的时候,再去注册 bean :

@Configuration
public class JavaConfig {

    @Bean
    //这个表示WindowsCondition#matches方法返回 true,这个 bean 才会注册到 Spring 容器
    @Conditional(WindowsCondition.class)
    ShowCmd winCmd() {
        System.out.println("winCmd");
        return new WindowsShowCmd();
    }

    @Bean
    //这个表示LinuxCondition#matches方法返回 true,这个 bean 才会注册到 Spring 容器
    @Conditional(LinuxCondition.class)
    ShowCmd linuxCmd() {
        System.out.println("linuxCmd");
        return new LinuxShowCmd();
    }
}

5.2 多环境切换

@Configuration
public class JavaConfig {

    @Bean
    @Profile("dev")
    DataSource devDs() {
        return new DataSource("jdbc:mysql:///dev", "dev", "dev");
    }

    @Bean
    @Profile("prod")
    DataSource prodDs() {
        return new DataSource("jdbc:mysql:///prod", "prod", "prod");
    }
}

public class Demo {
    public static void main(String[] args) {
        //如果直接传入启动类,这容器直接就初始化了,Bean 也都初始化了
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.getEnvironment().addActiveProfile("prod");
        ctx.register(JavaConfig.class);
        ctx.refresh();
        DataSource ds = ctx.getBean(DataSource.class);
        System.out.println("ds = " + ds);
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值