欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

spring简述

程序员文章站 2022-03-07 11:13:36
...

spring

  • 介绍:
    • Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。Spring的核心是控制反转(IoC)和面向切面(AOP)。简单来说,Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架。
  • 优点:
    • 轻量级:Spring在大小和透明性方面绝对属于轻量级的,基础版本的Spring框架大约只有2MB。
    • 控制反转(IOC):Spring使用控制反转技术实现了松耦合。依赖被注入到对象,而不是创建或寻找依赖对象。
    • 面向切面编程(AOP): Spring支持面向切面编程,同时把应用的业务逻辑与系统的服务分离开来。
    • 容器:Spring包含并管理应用程序对象的配置及生命周期。
    • MVC框架:Spring的web框架是一个设计优良的web MVC框架,很好的取代了一些web框架。
    • 事务管理:Spring对下至本地业务上至全局业务(JAT)提供了统一的事务管理接口。
    • 异常处理:Spring提供一个方便的API将特定技术的异常(由JDBC, Hibernate, 或JDO抛出)转化为一致的、Unchecked异常。
    • 方便程序的测试:Spring对Junit4支持,可以通过注解方便的测试Spring程序
    • 方便集成各种优秀框架:Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如:Struts、Hibernate、MyBatis、Quartz等)的直接支持

核心技术(IOC和AOP)

控制反转(IOC):inversion of controll

  • 概念:
    • 将控制权反转到IoC容器的手中。所谓的控制权,就是对象的创建以及对象之间依赖关系注入的权利。
  • 实现原理:
    • IOC容器和配置文件以及标准
    • 控制反转我们需要IOC容器,配置文件来让容器知道需要创建的对象与对象之间的关系并且使用统一的标准和语法来解析。
  • IOC容器:
    • IOC的体系架构:(BeanFactory、BeanDefinition)
    • 核心类:
      • BeanFactory:基本的IOC容器接口,定义了IOC容器的基本功能规范,定义bean对象及其相互的关系
      • BeanDefinition:描述bean对象在spring的实现,bean的抽象数据结构,它包括属性参数,构造器参数,以及其他具体的参数。
    • IOC容器的初始化的步骤:
      • 第一个过程是:BeanDefinition的Resource定位
      • 第二个过程是:BeanDefinition的载入和解析
      • 第三个过程是:BeanDefinitionI在IoC容器中的注册
    • 具体实现:(BeanFactory、ApplicationContext)
      • BeanFactory(beanfactory接口定义了容器的基本行为) XmlBeanFactory实现BeanFactory接口,从名字上看就可以知道他是用来读取xml格式的BeanDefinition,是BeanDefinition对对象依赖关系管理的核心数据结构。就是那个pojo类在IoC容器中的抽象。但它读取的内部实现是由相应的Reader对象,负责BeanDefinition的读入,在读入的时候,需要用资源的位置来初始化一个Resource对象,这个对象就包含了BeanDefinition的路径信息。然后Reader对象会调用loadBeanDefinition()方法,参数就是Resource对象,对相应的BeanDefinition进行加载与注册。
      • ApplicationContext:它除了xmlbeanfactory的功能之外
        • 提供了支持国际化的文本消息
        • 统一的资源文件读取方式
        • 已在监听器中注册的bean的事件,事件机制
      • ApplicationContext的初始化:
        1. 入口是refresh()方法,若之前有容器就关掉或者销毁重新开启一个IOC容器,
        2. IoC容器的创建:创建DefaultLisableFactory(两个工作)
          1. 调用父类容器的构造方法为容器设置好bean资源加载器,super(parent);最终调用的是父类的父类来创建appalicationContext,这个父类中的构造方法中设置spring source加载器保证applicationcontext有统一的资源文件读取方式。
          2. 调用父类的设置bean定义资源文件的定位路径的方法。 setConfigLocations(configLocations);
        3. BeanDefinition的载入和注册:
          1. 载入,通过封装BeanDefinition的Resource对象,获取IO流,获得相应的document对象,document对象就是一颗文档树,按照Spring中Bean的规则对文档树进行解析,得到的结果交由BeanDefinitionHolder持有
          2. 注册,IoC容器底部的数据结构是HashMap表,将BeanDefinition添加到HashMap中

事件机制应用的是观察者模式,其中的观察者就是各个Bean(实现ApplicationListener接口),被观察者就是ApplicationContext(继承ApplicationEvent类)。每当被观察者发布一个新的事件时,就会触发通知观察者进行相应的动作。

DI:依赖注入

  • 概述:
    • 依赖注入作为控制反转(IOC)的一个层面,可以有多种解释方式。在这个概念中,你不用创建对象而只需要描述如何创建它们。你不必通过代码直接的将组件和服务连接在一起,而是通过配置文件说明哪些组件需要什么服务。之后IOC容器负责衔接。
基于对象属性之间的注入
  • DI方式:
    • 构造器依赖注入:构造器依赖注入在容器触发构造器的时候完成,该构造器有一系列的参数,每个参数代表注入的对象。《需要有带对象参数的构造器》
    • Setter方法依赖注入:首先容器会触发一个无参构造函数或无参静态工厂方法实例化对象,之后容器调用bean中的setter方法完成Setter方法依赖注入。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"
>
<!-- spring 的配置  -->
    <bean   id="userService"
            class="service.UserService"
            scope="singleton"
            init-method="init"
            destroy-method="destroy"
            lazy-init="true"
            autowire="byName">
            
        //在bean标签里有<property>标签可以实现属性注入
        <property name="userDao" ref="userDaoImpldatabase"/>
        <property name="userDao" ref="userDaoImplFile"/>
        
        //<constructor>标签可以实现构造注入
         index="构造方法的参数下标" ref="对象id"
        <constructor-arg index="0" ref="userDaoImpldatabase"/>
        
        //两种任选其一即可
    </bean>
  • 简化注入的办法
    • autowired 自动织入 - 要么根据名字匹配, 要么根据类型匹配
<!-- 根据属性名字查找容器中的bean,找到了就进行依赖注入, 可以唯一确定容器中的bean -->
<bean autowire="byName">
</bean>

<!-- 根据属性类型查找容器中的bean,找到了就进行依赖注入, 如果容器中有多个类型相同的bean, byType就不适用了 -->
<bean autowire="byType">
</bean>
  • 注解注入
    • @Autowired 可以加在要注入的属性上,也可以加在属性对应的set方法或构造方法上,底层根据 byType 进行匹配注入

IOC简化使用

  • bean注入方式:
    • 前提:需要开启扫描
    • 注解:@Component 加在类上,spring 扫描到它之后,就把它交给 spring 容器管理
    • @Controller(表现层或叫控制层), @Service(业务逻辑层或叫服务层), @Repository(对应数据访问层), @Component(不属于前3层,不好分类时)
    • 控制单例多例子: @Scope(“singleton|prototype”)
    • 控制初始化和销毁方法:@PostConstruct 用来标记初始化方法, @PreDestroy 用来标记销毁方法
    • 控制懒惰初始化: @Lazy 加在类上,表示这个类在容器中是懒惰初始化的
    • ==此外这些bean注解还可以在括号里添加id
配置文件,添加扫描的标签
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"
>


<!-- 开启扫描 -->
<context:component-scan base-package="需要扫面的类或者包,逗号隔开"/>
  • 属性注入
    • 前提:开启注解
    • @Autowired 可以加在要注入的属性上,也可以加在属性对应的set方法或构造方法上,底层根据 byType 进行匹配注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"
>

<!-- 启用 @Autowired 等注解 -->
<context:annotation-config />
  • 事务开启
    • 在面对数据库时,事务的注解开启,会对性能以及一些出错处理有更好的效果
    • 将@Transactional()添加在一些对数据库数据的操作方法上,括号里可以有(rollbackFor=要回滚的异常类.class)或者(readOnly=true,在只有查询操作时)
    • timeout:超时时间,如果事务处理时间超过所设定的时间,那么将会结束事务并且回滚操作
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"
>

<!-- 4. 启用事务注解 -->
    <tx:annotation-driven/>
    <!-- 5. 配置事务管理器 默认名称为:transactionManager-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="数据库连接对象"/>
    </bean>

bean的生命周期

  • 在一个bean实例被初始化时,需要执行一系列的初始化操作以达到可用的状态。同样的,当一个bean不在被调用时需要进行相关的析构操作,并从bean容器中移除。Spring bean factory负责管理在spring容器中被创建的bean的生命周期。Bean的生命周期由两组回调(call back)方法组成。
    • 初始化之后调用的回调方法。
    • 销毁之前调用的回调方法。
  • 和普通对象一样
    1. 实例化(为bean对象开辟空间)
    2. 初始化(对对象的属性进行依赖注入,使用setter方法)
    3. 销毁
  • 具体讲:
    1. 实例化一个Bean,也就是我们通常说的new,默认为单例
    2. 按照Spring上下文对实例化的Bean进行配置,也就是IOC注入
    3. 如果这个Bean实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的是Spring配置文件中Bean的ID
    4. 如果这个Bean实现了BeanFactoryAware接口,会调用它实现的setBeanFactory(),传递的是Spring工厂本身(可以用这个方法获取到其他Bean)
    5. 如果这个Bean实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文,该方式同样可以实现步骤4,但比4更好,以为ApplicationContext是BeanFactory的子接口,有更多的实现方法
    6. 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor经常被用作是Bean内容的更改,并且由于这个是在Bean初始化结束时调用After方法,也可用于内存或缓存技术
    7. 如果这个Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法
    8. 如果这个Bean关联了BeanPostProcessor接口,将会调用postAfterInitialization(Object obj, String s)方法 注意:以上工作完成以后就可以用这个Bean了,那这个Bean是一个single的,所以一般情况下我们调用同一个ID的Bean会是在内容地址相同的实例
    9. 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean接口,会调用其实现的destroy方法
    10. 最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法
  • IoC的好处
    • 对象创建统一管理
    • 规范的对象生命周期管理
    • 获取一致的对象(单例模式)
    • 灵活的依赖注入

一、Spring装配Bean的过程

  1. 实例化;
  2. 设置属性值;
  3. 如果实现了BeanNameAware接口,调用setBeanName设置Bean的ID或者Name;
  4. 如果实现BeanFactoryAware接口,调用setBeanFactory 设置BeanFactory;
  5. 如果实现ApplicationContextAware,调用setApplicationContext设置ApplicationContext
  6. 调用BeanPostProcessor的预先初始化方法;
  7. 调用InitializingBean的afterPropertiesSet()方法;
  8. 调用定制init-method方法;
  9. 调用BeanPostProcessor的后初始化方法;

Spring容器关闭过程

  1. 调用DisposableBean的destroy();
  2. 调用定制的destroy-method方法;

AOP

概述:

  • AOP (aspect切面 oriented 面向 programming 编程)
  • spring aop,面向切面编程,基于ioc基础。利用的是一种横切的技术,解刨开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用的模块,也就是切面。就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

底层原理(代理技术)

  • AOP框架创建的对象,包含通知。在Spring中,AOP代理可以是JDK动态代理CGLIB代理
关键词
  • 切面(aspect):散落在系统各处的通用的业务逻辑代码,如上图中的日志模块,权限模块,事务模块等,切面用来装载pointcut和advice
  • 通知(advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
  • 连接点(joinpoint):被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
  • 切入点(pointcut):拦截的方法,连接点拦截后变成切入点
  • 目标对象(Target Object):代理的目标对象,指要织入的对象模块,如上图的模块一、二、三
  • 织入(weave):通过切入点切入,将切面应用到目标对象并导致代理对象创建的过程
  • AOP代理(AOP Proxy):AOP框架创建的对象,包含通知。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理

五个通知

  • 前置通知(Before advice):在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。
  • 后置通知(After returning advice):在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
  • 异常通知(After throwing advice):在方法抛出异常退出时执行的通知。
  • 最终通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
  • 环绕通知(Around Advice):包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。

环绕通知是最常用的通知类型。和AspectJ一样,Spring提供所有类型的通知,我们推荐你使用尽可能简单的通知类型来实现需要的功能。例如,如果你只是需要一个方法的返回值来更新缓存,最好使用后置通知而不是环绕通知,尽管环绕通知也能完成同样的事情。用最合适的通知类型可以使得编程模型变得简单,并且能够避免很多潜在的错误。比如,你不需要在JoinPoint上调用用于环绕通知的proceed()方法,就不会有调用的问题。
在Spring 2.0中,所有的通知参数都是静态类型,因此你可以使用合适的类型(例如一个方法执行后的返回值类型)作为通知的参数而不是使用Object数组。
通过切入点匹配连接点的概念是AOP的关键,这使得AOP不同于其它仅仅提供拦截功能的旧技术。 切入点使得通知可以独立对应到面向对象的层次结构中。例如,一个提供声明式事务管理 的环绕通知可以被应用到一组横跨多个对象的方法上(例如服务层的所有业务操作)。

AOP使用

假如业务模块一、二、三都需要日志记录,那么如果都在三个模块内写日志逻辑,那么会有两个问题:
1.打破模块的封装性
2.有很多重复代码
解决重复代码问题,可以通过封装日志逻辑为一个类,然后在各个模块需要的地方通过该类来试下日志功能,但是还是不能解决影响模块封装性的问题。
那么AOP就可以解决,它使用切面,动态地织入到各模块中(实际就是使用代理来管理模块对象),这样既解决了重复代码问题又不会影响模块的封装性

Spring AOP 实现原理

  • Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理
    • JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类
    • 如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。
    • CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
Spring中 AOP中的两种代理
  • Java动态代理,这样就可以为任何接口实例创建代理了,默认使用
  • 当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB
  • 使用动态代理机制和字节码生成技术实现。动态代理机制和字节码生成都是在运行期间为目标对象生成一个代理对象,使用横切逻辑植入到这个代理对象中,系统最终使用的是植入了横切逻辑的代理对象,而不是真正的目标对象。
动态代理
  • 缺点:
    • 动态代理只能对实现了相应Interface的类使用,如果某个类没有实现任何的Interface,就无法使用动态代理对其产生相应的代理对象
    • 因此:在默认情况下,如果Spring AOP发现目标实现了相应的Interface,则采用动态代理为其生成代理对象实例;而如果目标对象没有实现任何的Interface,Spring AOP会尝试使用CGLIB动态字节码生成类库,为目标对象生成代理对象实例!
cglib字节码增强
  • 使用CGLIB扩展对象行为的原理是:对目标对象进行继承扩展,为其生成相应的子类,而子类可以通过覆写来扩展父类的行为,只要将横切逻辑的实现放到子类中,然后让系统使用扩展后的目标对象的子类,就可以达到与代理模式相同的效果了。

使用步骤

  1. 编写切面类
@Aspect
public class 切面类 {

    @Around("切点表达式") // 切点表达式用来匹配目标和通知
    public Object 通知方法(ProceedingJoinPoint pjp) {
        // 写一些重复的逻辑, 计时, 事务控制,权限控制
        // 调用目标 , 其中 result 是目标方法返回的结果
        Object result = pjp.proceed();
        return result;
    }

}
  1. 把切面类和目标类都交给 spring 容器管理
可以用 `<context:component-scan>` 配合 @Component @Service 等注解扫描
也可以使用 `<bean>` 
  1. 使用切面
    • 从容器 getBean 根据接口获取,容器返回代理对象,代理对象中进行切点匹配,匹配到了执行通知,通知内部间接调用目标
// 1. 偷梁换柱, spring 容器返回的是一个代理对象
UserService userService = context.getBean(UserService.class);
System.out.println(userService.getClass());

// 2. 调用的是代理对象的 a 方法
userService.a();

切点表达式

    • within 匹配类中的所有方法(粒度粗)
    • 语法:within(包名.类名)
    • execution 表达式 可以精确到类中的每个方法 (粒度细)
    • 语法:execution(访问修饰符 返回值 包名.类名.方法名(参数信息))
    • @annotation 注解匹配
    • 语法:@annotation(包名.注解名)
    • 看执行方法上有没有这个注解,有的话就算匹配
within(service.*ServiceTarget)  // 其中 * 表示所有
匹配service包下所有以ServiceTarget为后缀的类

@Around("execution(public * service.UserService.*a())")
匹配到service.UserService下以a为后缀qie返回值不限类型的方法

@Around("execution(public * service.UserService.a(*))")
但是 * 写在参数位置,只能匹配一个任意类型的参数

@Around("execution(public * service.UserService.a(..))")
.. 写在参数位置, 匹配任意个数和任意类型的参数
相关标签: spring 框架