Spring

Posted on By Guanzhou Song

IOC/DI

IOC: Inversion of Control

DI: Dependency Injection

控制指的是:当前对象对内部成员的控制权。

反转指的是:这种控制权不由当前对象管理了,由其他(类,第三方容器)来管理。

通过DI,对象的依赖关系将由系统中负责协调各对象的第三方组件在创建对象的时候进行设定,对象无需自行创建或管理它们的依赖关系,依赖关系将被自动注入到需要它们的对象当中去

所谓的IOC容器就是一个大工厂【第三方容器】

原理就是通过Java的反射技术来实现, 通过反射获取类的所有信息(成员变量、类名等 等等)

再通过配置文件(xml)或者注解来描述类与类之间的关系

通过这些配置信息和反射技术来构建出对应的对象和依赖关系

Spring IOC容器如何实现对象的创建和依赖:

  1. 根据Bean配置信息在容器内部创建Bean定义注册表
  2. 根据注册表加载、实例化bean、建立Bean与Bean之间的依赖关系
  3. 将这些准备就绪的Bean放到Map缓存池中,等待应用程序调用

Bean的生命周期

BeanFactory中Bean的生命周期

ApplicationContext中Bean的生命周期

  • Bean自身的方法:如调用 Bean 构造函数实例化 Bean,调用 Setter 设置 Bean 的属性值以及通 过的 init-method 和 destroy-method 所指定的方法;

  • Bean级生命周期接口方法:如 BeanNameAware、 BeanFactoryAware、 InitializingBean 和 DisposableBean,这些接口方法由 Bean 类直接实现;

  • 容器级生命周期接口方法:在上图中带“★” 的步骤是由 InstantiationAwareBean PostProcessor 和 BeanPostProcessor 这两个接口实现,一般称它们的实现类为“ 后处理器” 。 后处理器接口一般 不由 Bean 本身实现,它们独立于 Bean,实现类以容器附加装置的形式注册到Spring容器中并通 过接口反射为Spring容器预先识别。当Spring 容器创建任何 Bean 的时候,这些后处理器都会发 生作用,所以这些后处理器的影响是全局性的。当然,用户可以通过合理地编写后处理器,让其仅 对感兴趣Bean 进行加工处理

ApplicationContext和BeanFactory不同之处在于:

  • ApplicationContext会利用Java反射机制自动识别出配置文件中定义的BeanPostProcessor、 InstantiationAwareBeanPostProcesso 和BeanFactoryPostProcessor后置器,并自动将它们注册到应用上下文中。而BeanFactory需要在代码中通过手工调用 addBeanPostProcessor() 方法进行注册

  • ApplicationContext在初始化应用上下文的时候就实例化所有单实例的Bean。而BeanFactory在 初始化容器的时候并未实例化Bean,直到第一次访问某个Bean时才实例化目标Bean。

  • LazyInit能在初始化上下文的时候暂时不初始化Bean,而是在使用时进行初始化。

Bean的初始化

读取 -> 解析、注册 -> 扫描注册表 -> 实例化 -> 属性设置 -> 放入缓存池

  • BeanDefinitionReader读取Resource所指向的配置文件资源,然后解析配置文件。配置文件中每 一个 解析成一个**BeanDefinition对象**,并**保存**到BeanDefinitionRegistry中;

  • 容器扫描BeanDefinitionRegistry中的BeanDefinition;调用InstantiationStrategy进行Bean实例化的工作;使用BeanWrapper完成Bean属性的设置工作;

  • 单例Bean缓存池:Spring 在DefaultSingletonBeanRegistry类中提供了一个用于缓存单实例 Bean 的缓存器,它是一个用HashMap实现的缓存器,单实例的Bean以beanName为键保存在这个HashMap中。

Bean的作用域

单例Singleton

多例prototype

与Web应用环境相关的Bean作用域

reqeust

session

为了适配Web应用环境相关的Bean作用域—>每个 request都需要一个对象,此时我们返回一个代理对象出去就可以完成我们的需求了!

装配Bean总结

总的来说,Spring IOC容器就是在创建Bean的时候有很多的方式给了我们实现,其中也包括了很多关于Bean的配置

IOC 面试题

什么是spring?

Spring 是java企业级应用的开源开发框架。Spring主要用来开发Java应用,但是有些扩展是针对构建 J2EE平台的web应用。Spring框架目标是简化Java企业级应用开发,并通过POJO为基础的编程模型促进良好的编程习惯。

使用Spring框架的好处是什么?

  • 轻量: Spring是轻量的,基本的版本大约2MB。

  • 控制反转: Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。

  • 面向切面的编程(AOP): Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开。

  • 容器:Spring 包含并管理应用中对象的生命周期和配置。

  • MVC框架:Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品。

  • 事务管理:Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务 (JTA)。

  • 异常处理:Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出 的)转化为一致的unchecked 异常。

什么是Spring的依赖注入?

依赖注入,是IOC的一个方面,是个通常的概念,它有多种解释。这概念是说你不用创建对象,而只需要描述它如何被创建。你不在代码里直接组装你的组件和服务,但是要在配置文件里描述哪些组件需要哪些服务,之后一个容器(IOC容器)负责把他们组装起来。

有哪些不同类型的IOC(依赖注入)方式?

  • 构造器依赖注入: 构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。

  • Setter方法注入: Setter方法注入是容器通过调用无参构造器或无参static工厂方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入。

哪种依赖注入方式你建议使用,构造器注入,还是 Setter方法注入?

你两种依赖方式都可以使用,构造器注入和Setter方法注入。最好的解决方案是用构造器参数实现强制依赖,setter方法实现可选依赖。

什么是Spring beans?

Spring beans 是那些形成Spring应用的主干的java对象。它们被Spring IOC容器初始化,装配和管理。这些beans通过容器中配置的元数据创建。比如,以XML文件中 的形式定义。

有四种重要的方法给Spring容器提供配置元数据。

XML配置文件

基于注解的配置

基于java的配置

Groovy DSL配置

解释Spring框架中bean的生命周期

Spring容器 从XML 文件中读取bean的定义,并实例化bean。

Spring根据bean的定义填充所有的属性。

如果bean实现了BeanNameAware 接口,Spring 传递bean 的ID 到 setBeanName方法 。 如果Bean实现了 BeanFactoryAware 接口, Spring传递beanfactory 给setBeanFactory 方法。

如果有任何与bean相关联的BeanPostProcessors,Spring会在 postProcesserBeforeInitialization()方法内调用它们。

如果bean实现IntializingBean了,调用它的afterPropertySet方法,如果bean声明了初始化方法,调用此初始化方法。

如果有BeanPostProcessors 和bean 关联,这些bean的postProcessAfterInitialization() 方法将 被调用。

如果bean实现了 DisposableBean,它将调用destroy()方法。

IOC的优点是什么?

IOC 或 依赖注入把应用的代码量降到最低。它使应用容易测试, 单元测试不再需要单例和JNDI查找机制。最小的代价和最小的侵入性使松散耦合得以实现。IOC容器支持加载服务时的饿汉式初始化(一直存在)和懒加载(需要时初始化)。

Spring框架中的单例Beans是线程安全的么?

Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。

但实际上,大部分的Spring bean并没有可变的状态(比如Service类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。

如果你的bean有多种状态的话(比如 View Model 对象),就需要自行保证线程安全。

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

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

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

FileSystemResource和ClassPathResource有何区别?

在 FileSystemResource 中需要给出spring-config.xml文件在你项目中的相对路径或者绝对路径。

在 ClassPathResource 中spring会在ClassPath中自动搜寻配置文件,所以要把ClassPathResource文件放在ClassPath下。

如果将spring-config.xml保存在了src文件夹下的话,只需给出配置文件的名称即可,因为src文件夹是默认的。

简而言之,ClassPathResource在环境变量中读取配置文件,FileSystemResource在配置文件中读取配置文件。

AOP

cglib代理

由于静态代理需要实现目标对象的相同接口,那么可能会导致代理类会非常非常多….不好维护

动态代理也有个约束:目标对象一定是要有接口的,没有接口就不能实现动态代理…..—–>因此出现了 cglib代理

cglib代理也叫子类代理,从内存中构建出一个子类来扩展目标对象的功能.

CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。

它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception.

public class ProxyFactory implements MethodInterceptor{
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("开始事务.....");
        // 执行目标对象的方法
        //Object returnValue = method.invoke(target, args);
        proxy.invokeSuper(object, args);
        System.out.println("提交事务....."); 
        return returnValue;
    } 
}


public class App {
    public static void main(String[] args) {
        UserDao userDao = new UserDao(); 
        UserDao factory = (UserDao) new ProxyFactory(userDao).getProxyInstance(); 
        factory.save();
    } 
}

概述

Aop: aspect object programming 面向切面编程

  • 功能: 让关注点代码与业务代码分离!

  • 面向切面编程就是指: 对很多功能都有的重复的代码抽取,再在运行的时候往业务方法上动态植入“切面类代码”。

关注点

重复代码就叫做关注点。

AOP 面向切面的编程:AOP可以实现“业务代码”与“关注点代码”分离

  • 关注点代码写一次即可;

  • 开发者只需要关注核心业务;

  • 运行时期,执行核心业务代码时候动态植入关注点代码; 【代理】

切入点

执行目标对象方法,动态植入切面代码。

可以通过切入点表达式,指定拦截哪些类的哪些方法; 给指定的类在运行的时候植入切面类代码。

切入点表达式

指定哪些类的哪些方法被拦截

Spring and AOP

现在有了Spring,就不需要我们自己写代理工厂了。

Spring内部会帮我们创建代理工厂。也就是说,不用我们自己写代理对象了。

因此,我们只要关心切面类、切入点、编写切入表达式指定拦截什么方法就可以了!

AOP注解API

  • @Aspect 指定一个类为切面类

  • @Pointcut(“execution(* cn.itcast.e_aop_anno..(..))”) 指定切入点表达式

  • @Before(“pointCut_()”) 前置通知: 目标方法之前执行

  • @After(“pointCut_()”) 后置通知:目标方法之后执行(始终执行)

  • @AfterReturning(“pointCut_()”) 返回后通知: 执行方法结束前执行(异常不执行)

  • @AfterThrowing(“pointCut_()”) 异常通知: 出现异常时候执行

  • @Around(“pointCut_()”) 环绕通知: 环绕目标方法执行

使用@Pointcut这个注解,来指定切入点表达式,在用到的地方中,直接引用

@Component
//指定为切面类 
@Aspect
public class AOP {
// 指定切入点表达式,拦截哪个类的哪些方法 
    @Pointcut("execution(* aa.*.*(..))") 
    public void pt() {}
    
    @Before("pt()") 
    public void begin() {
        System.out.println("开始事务");
    }

    @After("pt()")
    public void close() {
        System.out.println("关闭事务"); 
    }
}

分散在各个业务逻辑代码中相同的代码通过横向切割 的方式抽取到一个独立的模块中!

Spring AOP使用纯Java实现,它不需要专⻔的编译过程,也不需要特殊的类装载器,它在运行期通过代理方式向目标类织入增强代码。在Spring中可以无缝地将Spring AOP、IoC和AspectJ整合在一起。

Spring AOP构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法拦截。

  • JDK动态代理是需要实现某个接口了,而我们类未必全部会有接口

CGLib代理动态生成代理,其生成的动态代理对象是目标类的子类

Spring AOP默认是使用JDK动态代理,如果代理的类没有接口则会使用CGLib代理

  • 如果是单例的我们最好使用CGLib代理,创建对象时的性能较低,但生成代理对象的运行性能高于JDK。

  • 如果是多例的我们最好使用JDK代理,JDK在创建代理对象时的性能要高于CGLib代理,而生成代理对象的运行性能却比CGLib的低。

总结: Spring AOP 将相同逻辑的重复代码横向抽取出来,使用动态代理技术将这些重复代码织入到目标对象方法中,实现和原来一样的功能。这样一来,就在写业务时只关心业务代码,而不用关心与业务无关的代码。

引介/引入(Introduction)

当引入接口方法被调用时,代理对象会把此调用委托给实现了新接口的某个其他对象。实际上,一个Bean的实现被拆分到多个类中

public interface Waiter { // 向客人打招呼
    void greetTo(String clientName);
    // 服务
    void serveTo(String clientName);
}


 
public class NaiveWaiter implements Waiter {
    public void greetTo(String clientName) {
        System.out.println("NaiveWaiter:greet to " + clientName + "..."); 
    }

    @NeedTest
    public void serveTo(String clientName) { 
        System.out.println("NaiveWaiter:serving " + clientName + "...");
    } 
}

 
@Aspect
public class EnableSellerAspect {
    @DeclareParents(value = "com.smart.NaiveWaiter", // 指定服务员具体的实现 
    defaultImpl = SmartSeller.class) // 售货员具体的实现
        public Seller seller; // 要实现的目标接口 
}


 
public class Test {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("com/smart/aspectj/basic/beans.xml");
        Waiter waiter = (Waiter) ctx.getBean("waiter");
        // 调用服务员原有的方法 
        waiter.greetTo("Me"); 
        waiter.serveTo("Me");
        // 通过引介/引入切面已经将waiter服务员实现了Seller接口,所以可以强制转换 
        Seller seller = (Seller) waiter;
        seller.sell("soda", "Me");

    }
}

总结

  • AOP的底层实际上是动态代理,动态代理分成了JDK动态代理和CGLib动态代理。如果被代理对象 没有接口,那么就使用的是CGLIB代理(也可以直接配置使用CBLib代理)

  • 如果是单例的话,那我们最好使用CGLib代理,因为CGLib代理对象运行速度要比JDK的代理对象要快

  • AOP既然是基于动态代理的,那么它只能对方法进行拦截,它的层面上是方法级别的

  • 无论经典的方式、注解方式还是XML配置方式使用Spring AOP的原理都是一样的,只不过形式变了而已。一般我们使用注解的方式。

  • 注解的方式使用Spring AOP需了解切点表达式,几个增强/通知的注解(Before,After,Around)

  • 使用XML的方式和注解其实没有很大的区别

  • 引介/引入切面也算是一个比较亮的地方,可以用代理的方式为某个对象实现接口,从而够使用接口下的方法。这种方式是非侵入式的

Spring事务

By default checked exceptions do not result in the transactional interceptor marking the transaction for rollback and instances of RuntimeException and its subclasses do

结论:如果是编译时异常不会自动回滚,如果是运行时异常,那会自动回滚!

带有 @Transactional 注解所包围的方法就能被Spring事务管理起来,那如果我在当前类 下使用一个没有事务的方法去调用一个有事务的方法,那我们这次调用会怎么样?是否会有事务呢?

如果是在本类中没有事务的方法来调用标注注解 @Transactional 方法, 最后的结论是没有事务的。