简介
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的使用。后文会着重与深入源码分析。在了解用法之后要知道为什么其原理与实现,学而知其因。


485

被折叠的 条评论
为什么被折叠?



