偷窥Spring中的Bean加载

简介

        Spring一统天下,相信Java开发的小伙胖们都不陌生,而Spring最重要的特点,不在于其技术,而在于其思想,一提到Spring,脑海中自然浮现AOP(面向切面)与IOC(控制反转)两大核心思想,那么今天我们来辩一辩IOC中,依赖注入的一个加载机制。

        

体系

        Spring的结构体系分为如下

1.Spring Core 主要组件是BeanFactory,创建JavaBean的工厂,使用IOC管理所有Bean对象。

2.Spring Aop 集成面向切面的编程功能,AOP将整个方法或业务流程分为几个部分,常用日志切面、权限参数校验等。而面向切面是离不开代理模式的,关于代理模式文章可以参考前文设计模式之不愿被代理的代理模式,到底是怎么被代理的?

3.Spring Context 核心的配置文件,为Spring提供上下文信息。

4.Spring Do 操作数据库的模块

5.Spring ORM Spring 集成了各种ORM框架的模块,如MyBatis

6.Spring Web 集成各种优秀的web层框架的模块 (Struts、SpringMVC)

7.Spring web MVC Spring Web层框架

项目码云地址:https://gitee.com/yiang-hz/blog 源码包:code-analysis/spring-code/src/main/java/com/yiang/code/bean

构建

        Spring的环境构建方式可分为XML、注解等两种形式,一般情况下我们采用注解模式,当然XML方式是一种比较繁琐且过时了的方式。这里主要进行注解方式讲解

导入Maven依赖,这里采用Spring5的jar去进行讲解

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.0.RELEASE</version>
    </dependency>
</dependencies>

创建测试实体类 手动生成set、get、toString、全参|无参构造,或依赖并使用Lombok注解

public class User {
    private Integer id;
    private String name;
}

创建config配置类 注入Bean

@Configuration
public class SpringTestConfig {

    @Bean
    public User user() {
        return new User(2, "yiang");
    }
}

@Configuration 代表一个上下文配置文件在其中可以存入相对应的Bean。

@Bean 则代表注入一个Bean对象,方法名则为注入的id。

        以上注入Java代码等同于下方使用XML时 applicationContext.xml 配置文件中配置一个Bean对象

<bean id="user" class="com.yiang.code.bean.entity.User">    <property name="id" value="10"/>
    <property name="name" value="yiang"/>
</bean>

 创建测试类

public class Test {

    private static AnnotationConfigApplicationContext applicationContext;

    public static void main(String[] args) {
        // 注解注入
        applicationContext = new AnnotationConfigApplicationContext(SpringTestConfig.class);
        System.out.println("环境启动...");
        User user = applicationContext.getBean("user", User.class);
        System.out.println(user.toString());

    }
}

        我们可以看到代码中,通过使用 AnnotationConfigApplicationContext 类来加载了SpringTestConfig的内容,从而获取到了User实例。

        实现原理则是@Configuration用于定义配置类可替换xml配置文件,被注解类包含一个或多个被@Bean注解的方法,并会被AnnotationConfigApplicationContext类进行扫描,用于构建bean定义,初始化Spring容器。若不加入@Configuration则无法注入该类中的Bean对象。

运行调试会在控制台输出以下内容 

 

加载模式 

        Spring组件通过线程安全的CurrentHashMap来存储Bean对象,并使用单例模式(饿汉式程序初始化时加载)。当然如果加了@Lazy注解,则可以变为懒汉式(使用时加载)

        我们来尝试查看Bean注入的情况,首先Bean注入一般肯定会走无参构造的,但是在config中配置的是走的全参构造,所以我们重写User类全参构造,并再次运行。

public User(Integer id, String name) {
    this.id = id;
    this.name = name;
    System.out.println("User实例被加载...(全参构造)");
}

         我们可以看到,默认是在调用前就已经配置启动完毕,所以spring默认是采用饿汉式去进行Bean加载,如果我们给注入时的Bean加个@Lazy注解呢?

        

         再次运行,发现加载处于后方,证明采用了懒加载,也就是懒汉式。

作用域 

        SpringBean注入时可同时通过@Scope注解可以指定四个不同的作用域。这也就是面试常说SpringBean生命周期

1.singleton单例模式(默认) 全局仅有一个实例。

2.prototype原型模式 每次获取Bean对象都会有一个新的实例。

3.request 每次请求获取一个新的实例,仅在当前request请求时生效。

4.session 每次请求获取一个新的实例,仅在当前session存在时生效。

        我们现在来看看作用域,Spring中默认是单例模式的作用域,那我们在config新配置一个empty的User空对象

@Bean
public User emptyUser() {
    return new User();
}

         在main方法加入一下代码,来判断其引用地址是否相同,且运行结果是为true的,证明spring默认为单例

User emptyUser = applicationContext.getBean("emptyUser", User.class);
User emptyUserNew = applicationContext.getBean("emptyUser", User.class);
System.out.println(emptyUser == emptyUserNew);

        那么我们可以通过加上@Scope("prototype")注解,来使其为每次请求都创建一个新的对象

         再次运行,结果为false,原因是因为更改了其作用域。

注解@ComponentScan

        相信大家对这个注解不太陌生,尤其是MyBatis使用时,启动类或配置文件类总会加上@ComponentScan("**.**.**.mapper") 来进行Mapper文件的扫描,那么针对于该注解的重要参数

 进行讲解:

FilterType:

1.ANNOTATION:注解类型

2.ASSIGNABLE_TYPE:ANNOTATION:指定的类型

3.ASPECTJ:按照Aspectj的表达式,基本上不会用到

4.REGEX:按照正则表达式

5.CUSTOM:自定义规则

includeFilters 选择注入的文件仅包含某个类 使用时,参数useDefaultFilters需要为true,而不是false,否则无法只包含该类

excludeFilters 选择注入的文件仅排除某个类 使用时,参数useDefaultFilters需要为false,否则无法注入其它没有被排除的类

        我们先编写一个UserMapper类,并在配置文件中扫描该包

@Repository
public class UserMapper {
}

         在main方法添加打印Bean名称的方法

// 打印Spring容器中加载的Bean的ID
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
    System.out.println(beanDefinitionName);
}

可以看到,打印出来了userMapper,代表已经扫描到该包。

         若转为该注解,代表只会扫描配置包下的带有Service注解的类,如果为其它的注解,则不会被扫描到。

@ComponentScan(value = "com.yiang.code.bean.service", includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Service.class)
}, useDefaultFilters = false)

        如果是使用祛除,则代表不会扫描到该配置包下,所有带有service注解的类 

@ComponentScan(value = "com.yiang.code.bean.service", excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Service.class)
}, useDefaultFilters = true)

总结

        本文主要分析了Spring体系结构,bean加载的机制与其方式,以及扫包注解的使用,对基础的注解@Bean、@Lazy、@ComponentScan、@Configuation、@Scope进行了讲解,以及注解环境类AnnotationConfigApplicationContext的使用。后文会着重与深入源码分析。在了解用法之后要知道为什么其原理与实现,学而知其因。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值