源码解析 (一) | spring5

Scroll Down

前言

最近一直想去面试,但是这一两年来感觉进步太少了,准备攻克一下源码分析,从spring boot动手,但是一下子动手可能有点突兀,工欲善其事,必先利其器,这里就先从spring5源码开始动手吧!

一、概述

1、Spring是一个开放源代码的设计层面框架
2、他解决的是业务逻辑层和其他各层的松耦合问题,
3、它是面向接口的编程思想贯穿整个系统应用。
4、Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson创建。
5、简单来说,Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架。

二、整体架构

image.png

2.1、Core Container(核心容器)

里边包含Core,Beans,Context,SpEl(Spring Expression Lanuage)。

  • Core:主要包含Spring框架基本的核心工具类,Spring的其他组件也都要用到这个包里的类,简而言之,它就是其他组件的基本核心。
  • Beans:此模块是所有应用都要用到的,它包含访问配置文件,创建和管理bean和进行IOC,DI的所有类。
  • Context:构建于Core和Beans,是Beans的延伸和扩展,添加了对国际化(比如资源包的绑定),资源加载,事件传播和对Context的透明创建的支持。
  • Spel:提供了强大的表达式语言,用于在运行时查询和操作对象。支持设置/获取属性的值,属性的分配,方法的调用,访问数组上下文,容器和索引器,编辑和算数运算符,命名变量,从Ioc中根据名称检索对象,也支持list投影,选择和一般的list聚合。

2.2、Data Access(数据存取)/Integration(集成)

里边包括JDBC,ORM,OXM,JMS,Transaction。

  • JDBC:包含Spring对JDBC封装的所有类。
  • ORM:对象-关系映射API,比如我们经常用的Hibernate,iBatis,JPA等。
  • OXM:提供了一个对Object/XML映射实现的抽象层。
  • JMS(Java Message Service):包含一些制造和消费的消息。
  • Transaction:支持编程和声明性的事务管理,对所有POJO都适用。

2.3、Web

里面包括Web,WebScoket,Servlet,Portlet。

  • Web:提供了基础的面向web的集成特性,比如,多文件上传,,servlet listeners初始化IOC容器等,还包括一些Spring远程支持中的Web相关部分。
  • WebScoket:浏览器与服务端建立全双工的通信方式,解决http请求-响应带来过多的资源消耗,同时对特殊场景应用提供了全新的实现方式,比如聊天、股票交易、游戏等对对实时性要求较高的领域。
  • Servlet:包含Spring的model-view-controller(MVC)实现。
  • Portlet:基于Java的Web组件,由Portlet容器管理,并由容器处理请求,生产动态内容。提供了用于Protlet环境,Web-Servlet的MVC实现。

2.4、Aop

提供了符合AOP联盟标准的面向切面编程的实现。可以通过定义方法拦截器和切点将传统的逻辑代码分开,降低耦合。

  • Aspects:提供了对Aspect J的集成支持。
  • Instrumentation:提供类级别的工具支持和class loader的实现。使得可以在特定的服务器上使用。

三、spring核心思想

spring核心思想简单来说用两个字形容:解耦。让应用之间形成松藕结构,使得更加灵活性。解耦过程就需要用到控制反转技术

3.1、控制反转(IOC)

通俗讲,控制权由应用代码中转到了外部容器,控制权的转移,是所谓反转。也就是说,正常我们都是新建对象,才可以调用对象。现在不需要了,交给容器来管理,我们只需要通过一些配置来完成把实体类交给容器这么个过程。这样可以减少代码量,简化复杂度和耦合度。
主要形式

  • 依赖查找:容器提供回调接口和上下文条件给组件。EJB和Apache Avalon 都使用这种方式。这样一来,组件就必须使用容器提供的API来查找资源和协作对象,仅有的控制反转只体现在那些回调方法上(也就是上面所说的 类型1):容器将调用这些回调方法,从而让应用代码获得相关资源。
  • 依赖注入:组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。容器全权负责的组件的装配,它会把符合依赖关系的对象通过JavaBean属性或者构造函数传递给需要的对象。通过JavaBean属性注射依赖关系的做法称为设值方法注入(Setter Injection);将依赖关系作为构造函数参数传入的做法称为构造器注入(Constructor Injection)。

3.2、依赖注入(DI)

实现方式

  • 属性注入(setter注入)
package com.spring.demo02.entity;

@Data
public class Programmer {  
  
    private String name;  
    private String sex;  
    // 在这里定义要依赖的computer属性,加上set方法  
    private Computer computer;  
  
} 
@Data
public class Computer {  
      
    private String brand;  
    private String color;  
    private String size;  
      
    public void coding() {  
        System.out.println("Computer is coding!!!");  
    }   
}

总结:可以发现,Programmer类里面,有3个属性,name,sex,computer,并且都有对应的getter、setter方法;Computer类里面也有三个属性,分别是品牌、颜色、尺寸,也都有对应的getter、setter方法。这只是第一步,在类里面声明属性并且实现set方法。

<?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-3.0.xsd">  
      
  <bean id="programmer" class="com.spring.demo2.entity.Programmer">  
    <property name="name" value="小李"></property>  
    <property name="sex" value="男"></property>  
    <property name="computer" ref="computer"></property>  
  </bean>  
    
  <bean id="computer" class="com.spring.demo2.entity.Computer">  
    <property name="brand" value="hp"></property>  
    <property name="color" value="黑"></property>  
    <property name="size" value="14"></property>  
  </bean>  
    
</beans>

解读一下这个xml:

  • 1.声明一个bean,可以理解为实例化了一个对象。那这里实例化了两个对象(programmer和computer),各个属性都已经赋值上去。
  • 2.id为programmer的bean,其实就是Programmer类;通过给property赋值,Spring就会通过Programmer类的各个属性的set方法,逐一给Programmer的属性赋值。
  • 3.在programmer里面,有一个属性是computer的,可以看到它属性值是 ref="computer",这就说明computer这个属性是个引用,这里ref后面的值其实就是指向另一个bean的id值,所以这里引用的是id为computer的bean。

总结:
就是属性注入了。关键的是在类里面声明属性,写set方法,然后在xml里面配置bean和property的值。

  • 构造器注入
    构造器注入,顾名思义,就是在构造器里面注入依赖对象。那是怎么实现的呢?其实跟属性注入差不多,定义一个有参构造器,然后配置xml文件就行了。
@Data
public class Computer {  
    private String brand;  
    private String color;  
    private String size;  
    public Computer(String brand, String color, String size) {  
        this.brand = brand;  
        this.color = color;  
        this.size = size;  
    }  
} 
@Data
public class Programmer {  
      
    private Computer computer;  
      
    public Programmer(Computer computer){  
        this.computer = computer;  
    }  
}

上面两个类都有一个有参的构造器,接下来,在xml里面配置这两个bean,然后再配置构造器的参数值就可以了

<?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-3.0.xsd">  
      
  <bean id="programmer" class="com.spring.demo3.entity.Programmer">  
    <constructor-arg ref="computer"></constructor-arg>  
  </bean>  
  <!-- 构造器里面没有name字段,只有value,是根据构造器的方法参数顺序来定义的 -->  
  <bean id="computer" class="com.spring.demo3.entity.Computer">  
    <constructor-arg value="联想"></constructor-arg>  
    <constructor-arg value="红色"></constructor-arg>  
    <constructor-arg value="15.6寸"></constructor-arg>  
  </bean>  
    
</beans> 
  • 自动装配
    在类前面加注解:@Component,在需要注入的类里面加注解:@Autowired,这样xml里面的自动扫描就会扫描到这些加了注解的类和属性,在实例化bean的时候,Spring容器会把加了@Component的类实例化;在实际运行时,会给加了@Autowired的属性注入对应的实例。@Autowired方式是通过反射来设置属性值的
@Component  
@Data
public class Programmer {  
    @Autowired  
    Computer computer;  
}
@Component
@Data
public class Computer {  
    private String brand;  
    private String color;  
    private String size;
}
<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
xmlns:context="http://www.springframework.org/schema/context"  
xsi:schemaLocation="http://www.springframework.org/schema/beans  
    http://www.springframework.org/schema/context   
    http://www.springframework.org/schema/context/spring-context-3.0.xsd   
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">  
    <context:component-scan base-pakage="com.spring.demo04"> 
</beans>

3.3、面向切面(AOP)

面向切面是一个概念,通常用来为许多其他的类提供相同的服务,而且是固定的服务,是独立的。提升了代码的复用性,减少了代码的耦合度,减轻程序员的工作负担,把程序的重心放在了核心的逻辑上。
它是可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。
应用场景有日志记录,性能统计,安全控制,事务处理,异常处理等。

四、源码分析

  • @Configuration注解使用
    • @Autowired/@Inject
    • @CompomentScan
    • @Controller, @Service, @Repository, @Component
    • @Import注解组合使用
    • @Profile
    • @Comfiguration嵌套使用
    • @Lazy初始化
    • 配置类约束
  • @Comfiguration源码分析
    • ApplicationContext的refresh方法
    • ConfigurationClassPostProcessor
  • 总结

4.1、@Configuration

@Configuration注解提供了全新的bean创建方式,和@bean注解一起使用,spring3.0开始代替xml配置,运行时候完成bean的创建和初始化工作。
默认时候和方法名相同,也可以使用name属性指定

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component //@Component元注解
public @interface Configuration {
    String value() default "";
}

这里面用到了元注解@Componet,因此在Spring容器初始化时Configuration类 会被注册到Bean容器中,最后还会实例化。可替换xml配置文件。
主要用在各种组件的配置类里面

4.2、@Autowired/@Inject

@Configuration本身也是一个@Component,将其注册到应用上下文,这样就可以通过IOC的@Autowired/@Inject等注解来注入所需bean

@Configuration
public class AppConfig {
    @Autowired
    public Environment env;
    @Bean
    IBean appBean(){
        return new AppBean();
    }
}

4.3、@CompomentScan

@CompomentScan,来显式扫描需使用组件,
@ComponentScan("abc.xxx")表示扫描该文件夹下的所有Component类

五、最后问题

5.1、反射