常用框架01 - Spring

1. Spring是如何简化开发的?

(1)基于 POJO 的轻量级和最小侵入编程

        POJO(Plain Ordinary Java Object),简单的Java对象。POJO的内在含义是指那些没有任何继承、没有实现任何接口,更没有被其它框架侵入的java对象。

        如果在使用某种框架时,需要继承框架提供的类或者要实现框架提供的接口,那就说明这个框架是侵入式的。程序对框架产生了依赖,当去除框架时,程序无法正常运行,这就是我们所说的重量级框架。

        Spring框架不强迫应用程序实现Spring提供的规范接口或者继承Spring的规范类。可能你的类里面使用了Spring的注解,但是去掉注解,它仍然是一个普通的Java类。

(2)通过依赖注入和面向接口实现松耦合

        Java程序是由多个类组成的,要实现某个业务逻辑,需要类与类之间协作完成,这就产生了依赖关系。比如ClassA中用到ClassB中的方法,那么就要在ClassA中new一个ClassB的对象。每个对象都要负责管理与自己协作的对象的引用,这将会导致程序的高度耦合。

        采用依赖注入技术之后,只需要在ClassA中定义一个私有的ClassB,不需要直接new得到该对象,而是通过相关的容器控制程序来将ClassB对象在外部new出来并注入到ClassA里的引用中。而具体获取的方法、对象被获取时的状态由配置文件来指定。

(3)基于切面(AOP)和惯例进行声明式编程

        面向切面编程能够允许遍布在应用各处的功能分离出来形成可重用的组件。例如:常见的日志、事务管理和安全等系统服务。通过AOP编程,能够使上述的服务模块化,并以声明的方式将他们应用到需要的组件中去。

(4)通过切面和模板减少样式代码

        我们都使用过JDBC编程,在操作数据库时,其中有大量重复的样板式代码。(包括建立连接,释放等) spring为我们提供了JdbcTemplate来消除样板式代码。并且spring也集成了Hibernate、mybatis以及JPA来简化我们的开发。

2. Spring 框架有哪些主要模块?

        Spring 框架有 7 个模块,组成 Spring 框架的每个模块(或组件)都可以单独存在,也可以与其他一个或多个模块联合实现。

 (1)核心模块(Spring Core)

        SpringCore模块是Spring的核心容器,它实现了IOC模式,提供了Spring框架的基础功能。此模块中包含的BeanFactory类是Spring的核心类,负责JavaBean的配置与管理。它采用Factory模式实现了IOC即依赖注入。

        Spring以bean的方式进行java应用的各大组件及关系的组织和管理。Spring使用BeanFactory来产生和管理bean,是工厂模式的实现。BeanFactory使用控制反转(IOC)模式来将应用的配置和依赖性规范与实际的应用程序代码分开。

(2)应用上下文(Spring Context)

        SpringContext模块继承了BeanFactory类(或者说Spring核心类,或者说实现了ApplicationContext接口),并且添加了事件处理、国际化、资源装载、透明装载以及数据校验等功能。它还提供了框架式的Bean访问方式和很多企业级的功能,如JNDI访问,支持EJB、远程调用、集成模块框架、Email和定时任务调度等。

        BeanFactory是Spring中最底层的接口,是IOC的核心,其功能包含了各种Bean的定义、加载、实例化、依赖注入和生命周期的管理,是IOC最基本的功能。

        而ApplicationContext接口是BeanFactory的子类,具有BeanFactory所有的功能,同时继承了MessageSource,所以提供了更完整的框架功能,支持国际化、资源文件访问、载入多个上下文配置文件,使得每一个上下文都专注于一个特定层次,提供在监听器中注册bean事件。

(3)面向切面编程(Spring AOP)

        提供了对面向切面编程的丰富支持,该模块为基于 Spring 的应用程序中的对象提供了事务管理服务。

        通过使用Spring AOP ,不依赖组件,就可以将声明式事务管理集成到应用程序中。

(4)JDBC 和 DAO(Spring DAO)

        提供对JDBC的支持,还提供了DAO的支持,提供事务支持。

        DAO是DataAccessObject的缩写,DAO模式思想是将业务逻辑代码与数据库交互代码分离,降低两者耦合。通过DAO模式可以使结构变得更为清晰,代码更为简洁。DAO模块提供了JDBC抽象层,简化了数据库厂商的异常错误(不再从SQLException继承大批代码),大幅度减少代码的编写,并且提供了对声明式事务和编程式事务的支持。

(5)对象实体映射(Spring ORM)

        ORM:Object Relational Mapping,指对象实体映射。

        SpringORM模块提供了对现有ORM框架的支持,各种流行的ORM框架已经非常成熟,并且拥有大规模市场,Spring没有必要开发新的ORM工具,它对Hibernate提供了完美的整合功能,同时也支持其它ORM工具。

(6)Web 模块(Spring Web)

        此模块建立在SpringContext基础之上,提供了Spring和其他Web框架的集成,还提供了一些面向服务支持。

(7)MVC 模块(SpringWebMVC)

        SpringWebMVC模块建立在Spring核心功能之上,这使得它能拥有Spring框架的所有特征,能够适应多种视图、模板技术、国际化和验证服务,实现控制逻辑和业务逻辑的清晰分离。

1. 什么是Spring框架?

        Spring 是⼀种轻量级开发框架,旨在提高开发人员的开发效率以及系统的可维护性。我们一般说 Spring 框架指的都是 Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是:核心容器、数据访问 / 集成、Web、AOP(面向切面编程)、工具、消息和测试模块。比如:Core Container 中的 Core 组件是Spring 所有组件的核心,Beans 组件和 Context 组件是实现IOC和依赖注入的基础,AOP组件用来实现面向切面编程。

        Spring是一个开源的轻量级的Java开发框架,以 IoC(反转控制)和 AOP(面向切面编程)为内核,提供了展现层 Spring MVC 和持久层 Spring JDBC以及业务层事务管理等众多企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE企业应用开源框架。

        Spring 框架,它是一个容器,是一个整合其他框架的框架,它的核心是IOC和AOP,它由20多个模块构成,

     IOC:控制反转,把创建对象过程交给spring进行管理。

     Aop:面向切面,不修改源代码的情况下进行功能增强(扩展)。

2. 列举一些重要的Spring模块?

        下图对应的是 Spring4.x 版本。目前最新的5.x版本中 Web 模块的 Portlet 组件已经被废弃掉,同时增加了用于异步响应式处理的 WebFlux 组件。

   01 Spring Core :核心容器,提供Spring框架的基本功能。可以说 Spring 其他所有的功能都需要依赖于该类库。主要提供 IoC 依赖注入功能。

   02 Spring Aspects :该模块为与AspectJ的集成提供支持。

   03 Spring AOP :提供了面向切面的编程实现。

   04 Spring JDBC :Java数据库连接。

   05 Spring JMS :Java消息服务。

   06 Spring ORM : 用于支持Hibernate等ORM⼯具。

   07 Spring Web : 为创建Web应用程序提供支持。

   08 Spring Test :提供了对 JUnit 和 TestNG 测试的支持。

3. @RestController vs @Controller 

        @RestController 和 @Controller 都是控制器 Controller 中的注解

        @Controller 将当前修饰的类注入SpringBoot IOC容器,使得从该类所在的项目跑起来的过程中,这个类就被实例化。当然也有语义化的作用,即代表该类是充当Controller的作用。会返回一个 View(View 指 JSP、HTML 用来展示数据给用户)。

        @ResponseBody 的作用是指该类中所有的API接口返回的数据,不管你对应的方法返回 Map 或是其他 Object,它都会以 Json 字符串的形式返回给客户端。假如返回类型是 map,但是没有加@ResponseBody 注解,只有@Controller 注解修饰的时候,Spring 以为会返回一 个View(View 指JSP、HTML用来展示数据给用户),但是返回的东西却是一个 Map,页面会报错。

        @RestController 相当于@Controller + @ResponseBody 两个注解的结合,返回 json 数据不需要在方法前面加@ResponseBody 注解了,但使用@RestController注 解,就不能返回 jsp、html 页面,视图解析器无法解析 jsp、html 页面。

总结:@RestController 注解相当于@ResponseBody + @Controller 合在一起的作用,

        01 如果只是使用@RestController注解Controller,则 Controller 中的方法无法返回 jsp 页面,配置的视图解析器InternalResourceViewResolver不起作用,返回的内容就是Return 里的内容。例如:本来应该到success.jsp页面的,但其显示为success.

        02 如果需要返回到指定页面,则需要用 @Controller + 视图解析器才行。

        03 如果需要返回JSON、XML或自定义mediaType内容到页面,则需要在对应的方法上加上@ResponseBody注解。

 4. 说一说对Spring IoC 和 AOP的理解 / Spring的核心是什么?

(1)IoC

        IoC(Inverse of Control:控制反转)是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由Spring框架来管理。 IoC 在其他语言中也有应用,并非 Spring 特有。 IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。    

        将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。IoC 容器就像一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件 / 注解即可,不需要考虑对象是如何被创建出来的。    即创建对象、依赖注入的控制权交给spring容器来管理

        例如,原本要在类 A 中调用类 B 的方法,就需要直接在 A 中 new 出B类对象,然后调用B类中的方法,但是存在一个问题,更改需求会对源代码进行修改。当使用Spring的时候,会把创建B类对象交给Spring,在Spring中,B类对象被看成是Bean对象(Spring中类就是Bean),这个Bean对象由Spring容器进行创建和管理,最后只需要配置好配置文件 / 注解,就能完成B类中方法的调用。这样,A类调用B类中的方法,由主动 new,变成被动等待Spring创建。主动变被动,就可以理解为控制反转

        主动变被动,这样就大大降低了耦合,Spring中全都用这种思想,即依赖类不由程序员实例化,而是通过Spring容器帮我们new指定实例,并且将实例注入到需要该对象的类中,Spring通过DI(依赖注入)实现IOC(控制反转)。

        IoC 是一种实现思想,DI 是具体的实现方式

        Spring 是使用 DI 实现了 IOC 的功能,Spring 底层创建对象使用的是反射机制

        IOC 的依赖注入 DI 有两种方式的实现:xml 文件实现、注解实现。

              xml 的方式在注入成员变量的时候有两种方式:set 注入、构造方法注入。

        Spring 中的 IOC 的实现原理就是工厂模式 + 反射机制。

        工厂模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,而是使用一个共同的接口来指向新创建的对象。

(2)AOP

        面向切面编程(AOP—Aspect Oriented Programming)可以说是对OOP(面向对象编程)的补充和完善。面向对象就是将事物的特性和行为抽象为一个对象,如people类有身高、体重、年龄等属性,也有吃饭、睡觉等行为。把这些特性和行为封装成一个类,然后可以统一调用。面向切面也可以举个例子,比如people类有自己的属性和行为,但是有小一部分人生病要去医院看病,看病这个业务逻辑就不属于哪一个类,因为people泛指所有人,所有人不会都看病。AOP就是把医院看病这一个业务逻辑功能抽取出来,然后动态把这个功能切入到需要的方法(或行为)中,需要的才切入,这样便于减少系统的重复代码,降低模块间的耦合度。常用到AOP的就是安全校验、日志操作、事务操作等,给你先定义好,然后在想用的地方用,这样不会影响已经在服务器运行的项目,然后又能注入新功能,灵活。我们开发dao->service->controller是纵向的,这个AOP就是横向切入,如横向切入一个日志Log,打印执行过程。

        AOP(Aspect-Oriented Programming:面向切面编程,能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性

AOP底层原理:AOP底层使用动态代理,动态代理有两种,

        01 有接口的情况下,使用JDK动态代理,创建接口实现类代理对象,增强类的方法

         02 没有接口的情况下,使用CGLIB动态代理,创建子类的代理对象,增强类的方法

注意:动态代理底层利用了反射机制,反射包下Proxy类。

      当然你也可以使用 AspectJ,Spring AOP 已经集成了AspectJ ,AspectJ 应该算的上是 Java ⽣态系统中最完整的 AOP 框架了。

4.1 Spring AOP 和 AspectJ AOP 有什么区别?

(1)Spring AOP 属于运行时增强,AspectJ 是编译时增强。

(2)Spring AOP 基于代理(Proxying), AspectJ 基于字节码操作(Bytecode Manipulation)。

(3)AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单。 

(4)如果切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。

4.2 AOP 术语

(1)连接点:类里面哪些方法可以被增强,这些方法被称为连接点。

(2)切入点:实际被真正增强的方法。

(3)通知(增强):实际增强的逻辑部分被称为通知,切分为五种,前置通知、后置通知、环绕通知、异常通知、最终通知。

(4)切面:把通知应用到切入点的过程。

5. Spring Bean

      bean 和 BeanDefinition 就类似Java中的实例和Class,是Spring对对象的一种增强,以BeanDefinition为模板创建出来的bean实例本质上也是对象,不过多了很多对象没有的功能,例如作用域,生命周期回调,各种各样的后置处理等等

Spring Bean

(1)什么是Spring Bean

        在Spring中,组成应用程序的主体(backbone)并由Spring IoC容器所管理的对象,被称为bean。 简单地讲,bean就是由Spring容器初始化、装配及管理的对象,除此之外,bean就与应用程序中的其他对象没有什么区别了。 而bean定义以及bean相互间的依赖关系将通过配置元数据来描述。

        01 bean 是对象,一个或多个不限定;

        02 bean 是由 Spring 中的一个叫 IOC 的东西管理;

        03 应用程序由一个个 bean 构成。

(2)如何定义 Spring Bean?      

        Spring 负责创建 bean 对象。但首先,你需要告诉框架它应该创建哪些对象。你是怎么做到的?   通过提供bean定义(即利用 BeanDefinition)。Bean定义告诉Spring框架应该将哪些类用作bean。但那还不是全部。Bean定义就像食谱一样,它们还描述了bean的属性

        可以通过三种不同的方式定义 bean:使用@Component、使用@Bean、使用 XML 配置文件。

        Spring 通过 BeanDefinition 来创建 bean 对象,BeanDefinition 有很多的属性用来描述 Bean,如

        01 beanClass:表示一个 bean 的类型,比如UserService.class、OrderService.class,Spring 在创建 Bean 的过程中会根据此属性来实例化得到对象。

        02 scope:表示一个 bean 的作用域。

5.1 Spring 中的 bean 的作用域有哪些?

        首先,Spring 框架里面的IOC容器,可以非常方便的去帮助我们管理应用里面的Bean对象实例。我们只需要按照Spring里面提供的 xml 或者注解等方式去告诉IOC容器,哪些Bean需要被IOC容器管理就行了。其次,既然是Bean对象实例的管理,那意味着这些实例,是存在作用域的。

        01 singleton单例 bean,IOC 容器只存在一个共享的实例 bean,并且所有对 bean 的请求,只要 id 与该 bean 定义相匹配,则只会返回 bean 的同一实例。单一实例会被存储在单例缓存中。Spring 中的 Bean 默认是单例的。Singleton 是单例类型,就是在创建容器时就同时自动创建了一个 bean 对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。

        02 prototype原型 bean,每次从 IOC 容器去获取指定 Bean 的时候,都会返回一个新的实例对象。也就是一个bean定义对应多个对象实例。每一次请求(将其注入到另一个 bean 中,或执行 getBean() 方法)都会产生一个新的 bean 实例,相当于 new 操作。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。

        根据经验,对于有状态的 bean 应该使用 prototype 作用域,对于无状态的 bean 应该使用 singleton 作用域

        03 request:HTTP 请求, 每一次HTTP请求都会创建一个新的 Bean 实例,该bean仅在当前HTTP 请求内有效。

        04 session:HTTP 会话,每一次HTTP会话都会产生一个新的Bean实例,该Bean实例仅在当前HTTP会话中共享(即同一个会话共享一个实例,不同会话使用不同的实例)。

        05 global Session:全局 HTTP 会话,所有会话共享一个 Bean 实例。

请求与会话

        后3个是 Spring 专门用于 Web 应用程序的 bean 作用域,只能用在基于 Web 的Spring ApplicationContext 环境。

5.2 Spring中单例bean的线程安全问题了解吗?

       首先我们应该知道线程安全问题一般发生在成员变量上,这是为什么?因为成员变量是存放在堆内存中,而堆内存又是线程共享的,这就造成了线程安全问题。因为 Spring 中的 Bean 默认是单例的,所以在定义成员变量时也有可能会发生线程安全问题。

       Spring 容器中线程是否安全,要从单例与原型分别进行说明。

(1)对于原型Bean,每次创建一个新对象,也就是线程之间不存在  Bean 共享,自然也就不会存在线程安全问题。

(2)对于单例Bean,所有线程都共享一个单例实例 Bean,存在资源竞争。如果单例 bean 只关注于方法,不会对 bean 的成员执行查询以外的操作,那么这个 bean 是安全的。【重点在于有无对 bean 属性的查询以外的操作】

        如果单例 Bean 是一个无状态的 Bean,也就是线程中的操作不会对 Bean 的成员执行查询以外的操作,那么这个单例 Bean 是线程安全的。比如Spring MVC 的 Controller、Service、Dao等,这些Bean大多都是无状态的,只关注于方法本身。  

        对于有状态的bean,Spring官方提供的bean,一般提供了通过 ThreadLocal 去解决线程安全的方法,比如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等。

        使用ThreadLocal的好处使得多线程场景下,多个线程对这个单例Bean的成员变量并不存在资源的竞争,因为ThreadLocal为每个线程保存线程私有的数据。这是一种以空间换时间的方式。

        当然也可以通过加锁的方法来解决线程安全,这种以时间换空间的场景在高并发场景下显然是不实际的。

        Spring 容器本身没有提供线程安全的策略,因此是否线程安全完全取决于 bean 本身的特性

        单例 bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。常见的两种解决方法

        01 在Bean对象中尽量避免定义可变的成员变量(不太现实)。

        02 在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中。        

5.3 @Component 和 @Bean 的区别是什么?

      组件扫描:查找带注解的类的过程被称为组件扫描。

      Spring 帮助我们管理 Bean 分为两个部分,一个是创建 Bean 对象,一个是装配 Bean。完成这两个动作有三种方式,使用自动配置的方式(即注解)、使用 JavaConfig 的方式、使用 XML 配置的方式。

       @Component 和 @Bean 是两种使用注解来定义 bean 的方式类上面有@Component 注解,表明当前类是Bean)。

         01 作用对象不同 @Component 注解作用于类,@Bean 注解作用于方法。         

        02 @Component 通常是通过类路径扫描来自动检测以及自动装配到Spring容器中(可以使用 @ComponentScan 注解定义要扫描的路径,从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中),注释类和bean之间存在隐式的一对一映射(即每个类一个bean)。相当于是 XML 配置

@Component
public class Student {
    private String name = "lkm";
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

         @Bean 需要在配置类(配置类,即存在@Configuration注解的类)中使用。@Bean 注解通常是我们在标有该注解的方法中定义产生这个 bean,并告诉 Spring 这是某个类的实例,当我需要用它的时候还给我。@Bean 常和@Configuration注解搭配使用,

@Configuration
public class WebSocketConfig {
    @Bean
    public Student student(){
        return new Student();
    }
}

        @Component 和 @Bean 都可以使用@Autowired 或者@Resource 注解注入,

@Autowired
Student student;

5.4 为什么有了@Component,还需要 @Bean?

        如果想将第三方的类变成组件,但又没有没有源代码,就不能使用@Component 进行自动配置了,这种时候使用@Bean 比较合适,当然也可以使用 XML 配置。

5.5 Spring 中的 DI 介绍 Bean管理

      DI(Dependency Injection),即依赖注入,对象之间的依赖由容器在运行期决定,即容器动态的将某个依赖注入到对象之中。 有基于 XML 配置注入依赖和基于注解形式注入依赖两种,

         IOC 的依赖注入 DI 有两种方式的实现:xml 文件实现、注解实现。

              xml 的方式在注入成员变量的时候又可以使用两种方式实现:set 注入、构造方法注入。

        Bean 的装配可以理解为依赖关系注入,Bean 的装配方式即 Bean 依赖注入的方式。

5.5.1 基于 XML 配置注入依赖。该方法有两种方式实现,

(1)方式一:有参构造函数注入依赖

        01 bean 类实现有参构造函数,如下

         02 在配置文件中配置参数通过有参构造函数给对象属性赋值。有参构造使用的是 constructor-arg 标签,如下

 <bean id="student" class="com.spring.IOC.Student" >
        <constructor-arg name="id" value="1"/>
        <constructor-arg name="name" value="吕布" />
</bean>

(2)方式二:set 方法注入依赖

        01 给对象的属性提供 setter() 方法,如下

在这里插入图片描述

         02 在配置文件中通过 set 方式赋值。通过 set 方式使用的是 property 标签,如下,

<bean id="student" class="com.spring.IOC.Student">
       <property name="id" value="1"/>
       <property name="name" value="王昭君"/>
</bean>

5.5.2 基于注解形式注入依赖

(1)首先,开启注解扫描,如下

<?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/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--开启扫描:指定到包路径或者类名;会扫描类、方法、属性上是否有注解-->
    <context:component-scan base-package="com.wyscoder.spring.IOC"/>
</beans>

(2)使用到的注解有以下几种,

        01 @Value:该注解只能添加到普通类型上。 @Value(“1”)注解中可以赋值完成对基础属性的依赖注入。

@Component(value = "student")
public class Student {
    @Value("1")
    private Integer id;
}

        02 @Resource:该注解是注入对象类型,该注解是由Java 提供的,注意不是spring框架提供,默认按照名称进行装配,

@Component(value = "student")
public class Student {
    //自定义类型
   @Resource(name = "user")
    private User user;  // User是个类

        03 @Autowired:注入对象类型 ,是Spring框架提供的,按照类型来注入,

@Component(value = "student")
public class Student {
    //自定义类型
   @Autowired
    private User user;

        首先,利用@Component、@Service等注解,创建 bean 对象,再来注入依赖。

5.5 将一个类声明为 Spring 的 bean 的注解有哪些?(即创建 bean 对象)

      首先了解一下IOC操作Bean管理,bean管理是指:1)Spring 创建 bean 对象;2)Spring 注入属性。

        当我们在将一个类上标注@Service 或者@Controller 或@Component 或@Repository 注解之后,spring的组件扫描就会自动发现它,并且会将其初始化为spring应用上下文中的bean。 当需要使用这个bean的时候,例如加上@Autowired注解的时候,这个bean就会被创建。而且初始化是根据无参构造函数。

        在使用Spring进行项目开发的时候,会大量使用到自动装配( @Autowired 注解),那自动装配是什么呢?简单来说:Spring 利用依赖注入(DI)功能,完成SpringIOC容器中各个组件之间的依赖关系赋值管理。

      @Autowired可以标注在属性上、方法上和构造器上,来完成自动装配。默认是根据属性类型,spring自动将匹配到的属性值进行注入,然后就可以使用这个属性。

        

基于XML的装配方式有两种:属性 setter 注入和构造方法注入

      在Spring实例化Bean的过程中,Spring首先会调用Bean默认的构造方法来实例化Bean对象,然后通过反射的方式调用setter方法来注入属性值。属性setter方法注入要求Bean必须满足两点:

         01 Bean类必须提供一个默认的构造方法。

         02 Bean类必须为需要注入的属性提供对应的setter方法。

   在Spring配置文件中,使用属性setter方法注入时,在<bean>元素的子元素<property>中为每个属性注入值;而使用构造方法注入时,在<bean>元素的子元素<constructor-arg>中定义构造方法的参数。可以使用其value属性或者子元素来设置该值的参数。

<bean id="adminInfo" class="com.ssm.entity.AdminInfo" scope="prototype">
    <property name="id" value="5"></property>
    <constructor-arg name="name" value="admin" index="0"></constructor-arg>
    <constructor-arg name="pwd" value="123456" index="1"></constructor-arg>
</bean>

基于注解的装配:一些常用的注解,

     @Repository:用于将数据访问层(Dao层)的类标识为Spring中的Bean

     @Service:用于将业务层(Service层)的类标识为Spring中的Bean

     @Controller:用于将控制层(如Spring MVC中的Controller层)的类标识为Spring中的Bean

     @Autowired:用于对Bean的属性变量、属性的setter方法及构造方法进行标注,配合对应的注解处理器完成Bean的自动装配工作,默认按照Bean的类型进行装配

     @Resource:作用和@Autowired一样,但默认按照Bean实例名称装配

     

      我们一般使用 @Autowired 注解自动装配 bean,想要把一个类标识为可以用@Autowired注解自动装配的 bean,可以采用以下的注解实现(即将类声明为 bean 的注解有以下4个),

        01 @Component :通用的注解,可标注任意类为 Spring 组件。如果一个Bean不知道属于哪个层,可以使用 @Component 注解标注。

       @Component,创建对象的,等同于<bean> </bean>的功能。属性:value 就是对象的名字,也就是bean的id值, value的值是唯一的,创建的对象在整个spring容器中就一个且位置在类的上面

       @Component(value = "myStudent") 等同于 <bean id="myStudent" class="ba01.Student"/>,调用无参构造。

        02 @Repository : 对应持久层 Dao,主要用于数据库相关操作。

        03 @Service : 对应业务层 Service,主要涉及一些复杂的逻辑,需要用到 Dao层。

        04 @Controller : 对应 Spring MVC 的 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。

        @Service、@Repository、@Controller 是 @Component 的衍生物,它们允许你根据通用职责轻松对bean进行分类。你可以使用这些注释将bean类标记为特定应用程序层的成员,Spring框架会将它们全部视为@Component

5.6 Spring 中 bean 的生命周期?

        bean 的生命周期:从对象创建到对象销毁的过程。Spring 的 IoC 容器可以管理 bean 的生命周期,其允许在 bean 的生命周期的特定时间点执行相应的任务。

        ApplicationContext 容器是 Spring 推出的先进 IoC 容器,它继承了旧版本 IoC 容器 BeanFactory,并进一步扩展了容器的功能,增加了 bean 的自动识别、自动初始化等功能,同时引入了 BeanFactoryPostProcessor、BeanPostProcessor 等逻辑处理组件。ApplicationContext 容器在启动阶段便会调用所有 bean 的构建方法 getBean(),所以当项目启动好后,容器内所有对象都已被构建好了。

        Spring 中的 bean 的生命周期主要包含四个阶段:实例化 Instantiation --> 属性填充 Populate --> 初始化 Initialization -->销毁 Destruction。实例化和属性赋值对应构造方法和 setter() 方法的注入,初始化和销毁是用户能自定义扩展的两个阶段。主要逻辑都在 doCreate() 方法中,逻辑很清晰,就是顺序调用以下三个方法,这三个方法与三个生命周期阶段一一对应,

        01 createBeanInstance() -> 实例化:其实就是Bean对象的构造(利用构造器)。不过此时构造出的对象,他们内部的依赖对象仍然没有注入,只是通过反射(或Cglib)生成了具体对象的实例(执行构造函数),其实有点类似于我们手动new对象,new出的对象已经执行过了构造函数,并且内部的基本数据也已经准备好了,但如果内部还有其他对象的依赖,就需要后续的流程去主动注入。

        02 populateBean() -> 属性赋值:主要是对实例化好后的Bean进行依赖注入的过程(为 bean 的属性赋值与对其他 bean 的引用)。

        03 initializeBean() -> 初始化:执行用户自定义的一些初始化方法,注册Bean的销毁方法、缓存初始化好的Bean等。

// 忽略了无关代码
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
      throws BeanCreationException {

   // Instantiate the bean.
   BeanWrapper instanceWrapper = null;
   if (instanceWrapper == null) {
       // 实例化阶段!
      instanceWrapper = createBeanInstance(beanName, mbd, args);
   }

   // Initialize the bean instance.
   Object exposedObject = bean;
   try {
       // 属性赋值阶段!
      populateBean(beanName, mbd, instanceWrapper);
       // 初始化阶段!
      exposedObject = initializeBean(beanName, exposedObject, mbd);
   } 
}

至于销毁,是在容器关闭时调用的,详见 ConfigurableApplicationContext#close()。
 

        01 Spring 启动查找并加载需要被 Spring 管理的 bean,并进行 bean 的实例化。当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用doCreateBean()方法进行实例化,实际上就是通过反射的方式创建出个bean对象。

        02 Bean实例创建后,接着就是给这个Bean对象进行属性填充,也就是注入这个Bean依赖的其它bean对象;

        03 属性填充完成后,进行初始化Bean操作,初始化阶段又可以分为几个步骤,

          001 执行Aware接口的方法:Spring会检测该对象是否实现了xxxAware接口,通过Aware类型的接口,可以让我们拿到Spring容器的某些资源。如实现BeanNameAware接口可以获取到BeanName、实现BeanFactoryAware接口可以获取到工厂对象BeanFactory等。

          002 如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法。

          003 如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用。

          004 如果Bean 实现了BeanPostProcessor接口,Spring就会执行后置方法postProcessAfterInitialization()。

        04 初始化完成后,Bean就成功创建了,可以被应用程序使用了。 当Bean不再被需要时,会进行销毁操作,

           001 首先判断Bean是否实现了DestructionAwareBeanPostProcessor接口,如果实现了,则会执行DestructionAwareBeanPostProcessor后置处理器的销毁回调方法。
          002 其次会判断Bean是否实现了DisposableBean接口,如果实现了将会调用其实现的destroy()方法。
           003 最后判断这个Bean是否配置了dlestroy-method等自定义的销毁方法,如果有的话,则会自动调用其配置的销毁方法。

6. Bean 生命周期 常用扩展点

      Spring生命周期相关的常用扩展点非常多,下面通过分类的方式帮助记忆。

6.1 第一大类:影响多个 Bean 的接口

     实现了以下接口的 Bean 会切入到多个 Bean 的生命周期中。正因为如此,这些接口的功能非常强大,Spring 内部扩展也经常使用这些接口,例如自动注入以及 AOP 的实现都和他们有关。

  • BeanPostProcessor
  • InstantiationAwareBeanPostProcessor

      这两兄弟可能是 Spring 扩展中最重要的两个接口!InstantiationAwareBeanPostProcessor 作用于实例化阶段的前后,BeanPostProcessor 作用于初始化阶段的前后。正好和第一、第三个生命周期阶段对应。通过图能更好理解:

在这里插入图片描述

在这里插入图片描述

 

     InstantiationAwareBeanPostProcessor实际上继承了BeanPostProcessor接口,严格意义上来看他们不是两兄弟,而是两父子。但是从生命周期角度我们重点关注其特有的对实例化阶段的影响,图中省略了从BeanPostProcessor继承的方法。

     

        可以看到,postProcessBeforeInstantiation 在 doCreateBean 方法之前调用,也就是在 bean 实例化之前调用的,英文源码注释解释道该方法的返回值会替换原本的 Bean 作为代理,这也是 Aop 等功能实现的关键点。

6.2 第二大类:只调用一次的接口

    这类接口的特点是功能丰富,常用于用户自定义扩展。第二大类中又可以分为两类:

  • Aware 类型的接口
  • 生命周期接口

(1)无所不知的 Aware

        Aware 类型的接口的作用就是让我们能够拿到 Spring 容器中的一些资源。基本都能够见名知意,Aware 之前的名字就是可以拿到什么资源,例如 BeanNameAware 可以拿到 BeanName,以此类推。调用时机需要注意:所有的 Aware 方法都是在初始化阶段之前调用的!

       Aware接口具体可以分为两组,如下排列顺序同样也是 Aware 接口的执行顺序,能够见名知意的接口不再解释,

      Aware Group1 :BeanNameAware 、BeanClassLoaderAware、BeanFactoryAware。

      Aware Group2:EnvironmentAware、EmbeddedValueResolverAware、ApplicationContextAware。

(2)生命周期接口

        至于剩下的两个生命周期接口就很简单了,实例化和属性赋值都是 Spring 帮助我们做的,能够自己实现的有初始化和销毁两个生命周期阶段。

        01 InitializingBean:对应生命周期的初始化阶段。有一点需要注意,因为Aware方法都是执行在初始化方法之前,所以可以在初始化方法中放心大胆的使用Aware接口获取的资源,这也是我们自定义扩展Spring的常用方式。

        02 DisposableBean:类似于InitializingBean,对应生命周期的销毁阶段,以ConfigurableApplicationContext#close()方法作为入口,实现是通过循环取所有实现了DisposableBean接口的Bean然后调用其destroy()方法。


7. Spring 生命周期总结

       Spring Bean的生命周期分为四个阶段多个扩展点。扩展点又可以分为影响多个Bean影响单个Bean。整理如下:
01 四个阶段

  • 实例化 Instantiation
  • 属性赋值 Populate
  • 初始化 Initialization
  • 销毁 Destruction

02  多个扩展点

  • 影响多个Bean
    • BeanPostProcessor
    • InstantiationAwareBeanPostProcessor
  • 影响单个Bean
    • Aware
      • Aware Group1
        • BeanNameAware
        • BeanClassLoaderAware
        • BeanFactoryAware
      • Aware Group2
        • EnvironmentAware
        • EmbeddedValueResolverAware
        • ApplicationContextAware(ResourceLoaderAware\ApplicationEventPublisherAware\MessageSourceAware)
    • 生命周期
      • InitializingBean
      • DisposableBean

8. ApplicationContext(应用上下文)和 BeanFactory(Bean 工厂)的区别

        Spring 框架有两个 IoC 容器 - BeanFactory 和 ApplicationContext。 Spring容器最基本的接口就是BeanFactory。BeanFactory负责配置、创建、管理Bean,它有一个子接口ApplicationContext,也被称为Spring上下文,该容器同时还管理着Bean和Bean之间的依赖关系。

        BeanFactory负责读取bean配置文档、管理bean的加载、实例化、维护bean之间的依赖关系负责bean的声明周期。

(1)BeanFactory

        是 Spring 的原始接口,采用延迟加载形式来注入 Bean,即只有在使用到某个 Bean 时(调用getBean()),才对该 Bean 进行加载实例化。这样,我们就不能发现一些存在的 Spring 的配置问题。如果Bean的某一个属性没有注入,BeanFacotry 加载后,直至第一次使用调用 getBean 方法才会抛出异常。什么时候根据 id 获取对象了,什么时候才真正地创建对象。

        BeanFactory 通常以编程的方式被创建。

(2)ApplicationContext

        是 BeanFactory 的子接口,创建对象采用的策略是立即加载的方式,即只要一读取完配置文件就立即创建配置文件中配置的对象。是在容器启动时,一次性创建了所有的Bean(即进行实例化)。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean,确保当你需要的时候,你就不用等待,因为它们已经创建好了。因此,BeanFactory与ApplicationContext相比是轻量级的。

        相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。

        ApplicationContext还能以声明的方式创建,如使用ContextLoader。

    BeanFactory,是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:

        ① 继承MessageSource,因此支持国际化。

        ② 统一的资源文件访问方式。

        ③ 提供在监听器中注册bean的事件。

        ④ 同时加载多个配置文件。

        ⑤ 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。

9. Spring 启动流程

(1)初始化Spring容器,注册内置BeanPostProcessor(后置处理器)的BeanDefinition到容器中。初始化的时候,通过this()调用了无参构造函数,主要做了以下三件事,

        01 实例化BeanFactory,用于生成bean对象;

        02 实例化BeanDefinitionReader注解配置读取器,用于对有特定注解(@Service、@Component、@Repository、@Controller)的类进行读取,并转化为BeanDefinition对象;

        03 实例化ClassPathBeanDefinitionScanner路径扫描器,用于对指定的包目录进行扫描查找bean对象。

(2)将配置类的BeanDefinition注册到容器中

        该步骤主要利用doRegisterBean()方法,用来解析用户传入的Spring配置类,解析成一个BeanDefinition,然后注册到容器中。

(3)调用refresh()方法刷新容器

        refresh()主要用于容器的刷新,Spring 中的每一个容器都会调用 refresh() 方法进行刷新,每一个容器都会调用这个方法完成初始化。refresh() 可以分为12个步骤。

Spring启动

       

Spring启动   Spring启动   Spring启动​​​​​​​​​​​​​​

        因为是基于 java-config 技术分析源码,所以这里的入口是 AnnotationConfigApplicationContext ,如果是使用 xml 分析,那么入口即为 ClassPathXmlApplicationContext ,它们俩的共同特征便是都继承了 AbstractApplicationContext 类,而大名鼎鼎的 refresh 方法便是在这个类中定义的,我们接着分析 AnnotationConfigApplicationContext 类,可以绘制成如下流程图:

思考:如果让你去设计一个 IOC 容器,你会怎么做?首先我肯定会提供一个入口(AnnotationConfigApplicationContext )给用户使用,然后需要去初始化一系列的工具组件: 

    ①:如果我想生成 bean 对象,那么就需要一个 beanFactory 工厂 - DefaultListableBeanFactory

    ②:如果我想对加了特定注解(如 @Service@Repository)的类进行读取转化成 BeanDefinition 对象(BeanDefinition 是 Spring 中极其重要的一个概念,它存储了 bean 对象的所有特征信息,如是否单例,是否懒加载,factoryBeanName 等),那么就需要一个注解配置读取器 - AnnotatedBeanDefinitionReader

    ③:如果我想对用户指定的包目录进行扫描查找 bean 对象,那么还需要一个路径扫描器 - ClassPathBeanDefinitionScanner

通过上面的思考,是不是上面的图理解起来就轻而易举呢?

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值