Spring的简介

  1. spring是一个轻量级的ioc和aop的容器框架
  2. 通过控制反转和依赖注入实现松耦合。
  3. 支持面向切面的编程,并且把应用业务逻辑和系统服务分开
  4. 方便集成各种优秀框架。内部提供了对各种优秀框架的直接支持(如:Hibernate、MyBatis等)。

Spring用到了哪些设计模式?

  1. 简单工厂设计模式:~~ BeanFactory就是简单工厂模式的体现,根据传入一个唯一标识来获得 Bean 对象。~~
  2. 工厂方法模式:~~ FactoryBean就是典型的工厂方法模式。spring在使用getBean()调用获得该bean时,会自动调用该bean的getObject()方法。每个 Bean 都会对应一个 FactoryBean,如 SqlSessionFactory 对应 SqlSessionFactoryBean。~~
  3. BeanFactory:工厂方法模式
  4. 享元模式:使用享元模式完成单例模式,使用单例对象池(单例享元池返回组件)DefaultSingletonBeanRegistry——————>singletonObjects
  5. 单例模式: 一个类仅有一个实例,提供一个访问它的全局访问点。Spring 创建 Bean 实例默认是单例的。
  6. 适配器模式: SpringMVC中的适配器HandlerAdatper。由于应用会有多个Controller实现,如果需要直接调用Controller方法,那么需要先判断是由哪一个Controller处理请求,然后调用相应的方法。当增加新的 Controller,需要修改原来的逻辑,违反了开闭原则(对修改关闭,对扩展开放)。

为此,Spring提供了一个适配器接口,每一种 Controller 对应一种 HandlerAdapter 实现类,当请求过来,SpringMVC会调用getHandler()获取相应的Controller,然后获取该Controller对应的 HandlerAdapter,最后调用HandlerAdapter的handle()方法处理请求,实际上调用的是Controller的handleRequest()。每次添加新的 Controller 时,只需要增加一个适配器类就可以,无需修改原有的逻辑。

常用的处理器适配器:SimpleControllerHandlerAdapter,HttpRequestHandlerAdapter,AnnotationMethodHandlerAdapter。

  1. // Determine handler for the current request.
  2. mappedHandler = getHandler(processedRequest);
  3. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
  4. // Actually invoke the handler.
  5. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  6. public class HttpRequestHandlerAdapter implements HandlerAdapter {
  7. @Override
  8. public boolean supports(Object handler) {//handler是被适配的对象,这里使用的是对象的适配器模式
  9. return (handler instanceof HttpRequestHandler);
  10. }
  11. @Override
  12. @Nullable
  13. public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
  14. throws Exception {
  15. ((HttpRequestHandler) handler).handleRequest(request, response);
  16. return null;
  17. }
  18. }
  1. 代理模式: spring的aop使用了动态代理,有两种方式JdkDynamicAopProxy和Cglib2AopProxy。
  2. 观察者模式: spring 中 observer 模式常用的地方是 listener 的实现,如ApplicationListener。
  3. 模板模式: Spring 中 jdbcTemplate、hibernateTemplate 等,就使用到了模板模式。
  4. 策略模式:Resource

JDK动态代理和CGLIB动态代理的区别?

Spring AOP中的动态代理主要有两种方式:JDK动态代理和CGLIB动态代理.

JDK动态代理

如果目标类实现了接口,Spring AOP会选择使用JDK动态代理目标类。代理类根据目标类实现的接口动态生成,不需要自己编写,生成的动态代理类和目标类都实现相同的接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。
缺点:目标类必须有实现的接口。如果某个类没有实现接口,那么这个类就不能用JDK动态代理。

CGLIB来动态代理

通过继承实现。如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library)可以在运行时动态生成类的字节码,动态创建目标类的子类对象,在子类对象中增强目标类。
CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

优点:目标类不需要实现特定的接口,更加灵活。

Spring通知有哪些类型?
在AOP术语中,切面的工作被称为通知。通知实际上是程序运行时要通过Spring AOP框架来触发的代码段。

Spring切面可以应用5种类型的通知:

前置通知(Before):在目标方法被调用之前调用通知功能;
后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
返回通知(After-returning ):在目标方法成功执行之后调用通知;
异常通知(After-throwing):在目标方法抛出异常后调用通知;
环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的逻辑。

什么是IOC?

IOC:控制反转,由Spring容器管理bean的整个生命周期。通过反射实现对其他对象的控制,包括初始化、创建、销毁等,解放手动创建对象的过程,同时降低类之间的耦合度。
IOC的好处:降低了类之间的耦合,对象创建和初始化交给Spring容器管理,在需要的时候只需向容器进行申请。

IOC容器初始化过程?

  1. 从XML中读取配置文件。
  2. 将bean标签解析成 BeanDefinition,如解析 property 元素, 并注入到 BeanDefinition 实例中。
  3. 将BeanDefinition 注册到容器 BeanDefinitionMap 中。
  4. BeanFactory 根据 BeanDefinition 的定义信息创建实例化和初始化 bean。

单例bean的初始化以及依赖注入一般都在容器初始化阶段进行,只有懒加载(lazy-init为true)的单例bean是在应用第一次调用getBean()时进行初始化和依赖注入。

多例bean 在容器启动时不实例化,即使设置 lazy-init 为 false 也没用,只有调用了getBean()才进行实例化。

Bean的生命周期

image.png

  1. 调用bean的构造方法创建Bean
  2. 通过反射调用setter方法进行属性的依赖注入
  3. 如果Bean实现了BeanNameAware接口,Spring将调用setBeanName(),设置 Bean的name(xml文件中bean标签的id)
  4. 如果Bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()把bean factory设置给Bean
  5. 如果Bean实现了ApplicationContextAware接口,Spring容器将调用setApplicationContext()给Bean设置ApplictionContext
  6. 如果存在BeanPostProcessor,Spring将调用它们的postProcessBeforeInitialization(预初始化)方法,在Bean初始化前对其进行处理
  7. 如果Bean实现了InitializingBean接口,Spring将调用它的afterPropertiesSet方法,然后调用xml定义的 init-method 方法,两个方法作用类似,都是在初始化 bean 的时候执行
  8. 如果存在BeanPostProcessor,Spring将调用它们的postProcessAfterInitialization(后初始化)方法,在Bean初始化后对其进行处理
  9. Bean初始化完成,供应用使用,直到应用被销毁
  10. 如果Bean实现了DisposableBean接口,Spring将调用它的destory方法,然后调用在xml中定义的 destory-method方法,这两个方法作用类似,都是在Bean实例销毁前执行

    BeanFactory和FactoryBean的区别?

    BeanFactory:管理Bean的容器,Spring中生成的Bean都是由这个接口的实现来管理的。
    FactoryBean:通常是用来创建比较复杂的bean,一般的bean 直接用xml配置即可,但如果一个bean的创 建过程中涉及到很多其他的bean 和复杂的逻辑,直接用xml配置比较麻烦,这时可以考虑用FactoryBean,实现该接口隐藏实例化复杂Bean的细节,进而采取配置对应的FactoryBean即可。
    当配置文件中bean标签的class属性配置的实现类是FactoryBean时,通过 getBean()方法返回的不是FactoryBean本身,而是调用FactoryBean#getObject()方法所返回的对象,相当于FactoryBean#getObject()代理了getBean()方法。如果想得到FactoryBean必须使用 ‘&’ + beanName 的方式获取。
    Mybatis 提供了 SqlSessionFactoryBean,可以简化 SqlSessionFactory的配置:(注册的不是SqlSessionFactoryBean,而是SqlSessionFactoryBean这个工厂调用了getObject()返回的对象,类型是getObjectType() )

    1. public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
    2. @Override
    3. public void afterPropertiesSet() throws Exception {
    4. notNull(dataSource, "Property 'dataSource' is required");
    5. notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    6. state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
    7. "Property 'configuration' and 'configLocation' can not specified with together");
    8. this.sqlSessionFactory = buildSqlSessionFactory();
    9. }
    10. protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    11. //复杂逻辑
    12. }
    13. @Override
    14. public SqlSessionFactory getObject() throws Exception {
    15. if (this.sqlSessionFactory == null) {
    16. afterPropertiesSet();
    17. }
    18. return this.sqlSessionFactory;
    19. }
    20. }

    在 xml 配置 SqlSessionFactoryBean:

    1. <bean id="tradeSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    2. <property name="dataSource" ref="trade" />
    3. <property name="mapperLocations" value="classpath*:mapper/trade/*Mapper.xml" />
    4. <property name="configLocation" value="classpath:mybatis-config.xml" />
    5. <property name="typeAliasesPackage" value="com.bytebeats.mybatis3.domain.trade" />
    6. </bean>

    Spring 将会在应用启动时创建 SqlSessionFactory,并使用 sqlSessionFactory 这个名字存储起来。

    Bean注入容器有哪些方式?

    将普通类交给Spring容器管理,通常有以下方法:
    1、使用@Configuration与@Bean注解
    2、使用@Controller、@Service、@Repository、@Component 注解标注该类,然后启用@ComponentScan自动扫描
    3、使用@Import 方法。使用@Import注解把bean导入到当前容器中,代码如下:

    1. //@SpringBootApplication
    2. @ComponentScan
    3. /*把用到的资源导入到当前容器中*/
    4. @Import({Dog.class, Cat.class})
    5. public class App {
    6. public static void main(String[] args) throws Exception {
    7. ConfigurableApplicationContext context = SpringApplication.run(App.class, args);
    8. System.out.println(context.getBean(Dog.class));
    9. System.out.println(context.getBean(Cat.class));
    10. context.close();
    11. }
    12. }

    Bean的作用域

    1、singleton:单例,Spring中的bean默认都是单例的。
    2、prototype:每次请求都会创建一个新的bean实例。
    3、request:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
    4、session:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有效。
    5、global-session:全局session作用域。

    @Bean和@Component有什么区别?

    都是使用注解定义 Bean。@Bean 是使用 Java 代码装配 Bean,@Component 是自动装配 Bean。
    @Component 注解用在类上,表明一个类会作为组件类,并告知Spring要为这个类创建bean,每个类对应一个 Bean。
    @Bean 注解用在方法上,表示这个方法会返回一个 Bean。@Bean 需要在配置类中使用,即类上需要加上@Configuration注解。 ```java @Component public class Student { private String name = “lkm”;

    public String getName() {

    1. return name;

    } }

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

  1. @Bean 注解更加灵活。当需要将第三方类装配到 Spring 容器中,**因为没办法源代码上添加@Component注解,只能使用@Bean 注解的方式,当然也可以使用 xml 的方式。**
  2. <a name="O6Jf1"></a>
  3. ## Spring 事务实现方式有哪些?
  4. 事务就是一系列的操作原子执行。Spring事务机制主要包括声明式事务和编程式事务。
  5. - **编程式事务**:通过编程的方式管理事务,这种方式带来了很大的灵活性,但很难维护。
  6. - **声明式事务**:将事务管理代码从业务方法中分离出来,通过aop进行封装。Spring声明式事务使得我们无需要去处理获得连接、关闭连接、事务提交和回滚等这些操作。使用 @Transactional 注解开启声明式事务。
  7. @Transactional相关属性如下:
  8. | **属性** | **类型** | **描述** |
  9. | --- | --- | --- |
  10. | value | String | 可选的限定描述符,指定使用的事务管理器 |
  11. | propagation | enum: Propagation | 可选的事务传播行为设置 |
  12. | isolation | enum: Isolation | 可选的事务隔离级别设置 |
  13. | readOnly | boolean | 读写或只读事务,默认读写 |
  14. | timeout | int (in seconds granularity) | 事务超时时间设置 |
  15. | rollbackFor | Class对象数组,必须继承自Throwable | 导致事务回滚的异常类数组 |
  16. | rollbackForClassName | 类名数组,必须继承自Throwable | 导致事务回滚的异常类名字数组 |
  17. | noRollbackFor | Class对象数组,必须继承自Throwable | 不会导致事务回滚的异常类数组 |
  18. | noRollbackForClassName | 类名数组,必须继承自Throwable | 不会导致事务回滚的异常类名字数组 |
  19. <a name="GI6Vn"></a>
  20. ## 有哪些事务传播行为?
  21. TransactionDefinition接口中定义了七个事务传播行为:
  22. 1. **PROPAGATION_REQUIRED**如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。如果嵌套调用的两个方法都加了事务注解,并且运行在相同线程中,则这两个方法使用相同的事务中。如果运行在不同线程中,则会开启新的事务。
  23. 1. **PROPAGATION_SUPPORTS** 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。
  24. 1. **PROPAGATION_MANDATORY **如果已经存在一个事务,支持当前事务。如果不存在事务,则抛出异常IllegalTransactionStateException
  25. 1. **PROPAGATION_REQUIRES_NEW **总是开启一个新的事务。需要使用JtaTransactionManager作为事务管理器。
  26. 1. **PROPAGATION_NOT_SUPPORTED **总是非事务地执行,并挂起任何存在的事务。需要使用JtaTransactionManager作为事务管理器。
  27. 1. **PROPAGATION_NEVER** 总是非事务地执行,如果存在一个活动事务,则抛出异常。
  28. 1. **PROPAGATION_NESTED **如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务, 则按PROPAGATION_REQUIRED 属性执行。
  29. **PROPAGATION_NESTED PROPAGATION_REQUIRES_NEW的区别:**<br />使用PROPAGATION_REQUIRES_NEW时,内层事务与外层事务是两个独立的事务。一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。<br />使用PROPAGATION_NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。
  30. <a name="S5aPj"></a>
  31. ## <br />Spring怎么解决循环依赖的问题?
  32. 构造器注入的循环依赖:Spring处理不了,直接抛出BeanCurrentlylnCreationException异常。<br />单例模式下属性注入的循环依赖:通过三级缓存处理循环依赖。<br />非单例循环依赖:无法处理。<br />下面分析单例模式下属性注入的循环依赖是怎么处理的:<br />首先,Spring单例对象的初始化大略分为三步:
  33. 1. createBeanInstance:实例化bean,使用构造方法创建对象,为对象分配内存。
  34. 1. populateBean:进行依赖注入。
  35. 1. initializeBean:初始化bean
  36. Spring为了解决单例的循环依赖问题,使用了三级缓存:<br />singletonObjects:完成了初始化的单例对象mapbean name --> bean instance<br />earlySingletonObjects :完成实例化未初始化的单例对象mapbean name --> bean instance<br />singletonFactories 单例对象工厂mapbean name --> ObjectFactory,单例对象实例化完成之后会加入singletonFactories。<br />在调用createBeanInstance进行实例化之后,会调用addSingletonFactory,将单例对象放到singletonFactories中。
  37. ```java
  38. protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
  39. Assert.notNull(singletonFactory, "Singleton factory must not be null");
  40. synchronized (this.singletonObjects) {
  41. if (!this.singletonObjects.containsKey(beanName)) {
  42. this.singletonFactories.put(beanName, singletonFactory);
  43. this.earlySingletonObjects.remove(beanName);
  44. this.registeredSingletons.add(beanName);
  45. }
  46. }
  47. }

假如A依赖了B的实例对象,同时B也依赖A的实例对象。

  1. A首先完成了实例化,并且将自己添加到singletonFactories中
  2. 接着进行依赖注入,发现自己依赖对象B,此时就尝试去get(B)
  3. 发现B还没有被实例化,对B进行实例化
  4. 然后B在初始化的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects和二级缓存earlySingletonObjects没找到,尝试三级缓存singletonFactories,由于A初始化时将自己添加到了singletonFactories,所以B可以拿到A对象,然后将A从三级缓存中移到二级缓存中
  5. B拿到A对象后顺利完成了初始化,然后将自己放入到一级缓存singletonObjects中
  6. 此时返回A中,A此时能拿到B的对象顺利完成自己的初始化

由此看出,属性注入的循环依赖主要是通过将实例化完成的bean添加到singletonFactories来实现的。而使用构造器依赖注入的bean在实例化的时候会进行依赖注入,不会被添加到singletonFactories中。比如A和B都是通过构造器依赖注入,A在调用构造器进行实例化的时候,发现自己依赖B,B没有被实例化,就会对B进行实例化,此时A未实例化完成,不会被添加到singtonFactories。而B依赖于A,B会去三级缓存寻找A对象,发现不存在,于是又会实例化A,A实例化了两次,从而导致抛异常。
总结:1、利用缓存识别已经遍历过的节点; 2、利用Java引用,先提前设置对象地址,后完善对象。

Spring启动过程

  1. 读取web.xml文件。
  2. 创建 ServletContext,为 ioc 容器提供宿主环境。
  3. 触发容器初始化事件,调用 contextLoaderListener.contextInitialized()方法,在这个方法会初始化一个应用上下文WebApplicationContext,即 Spring 的 ioc 容器。ioc 容器初始化完成之后,会被存储到 ServletContext 中。
  4. 初始化web.xml中配置的Servlet。如DispatcherServlet,用于匹配、处理每个servlet请求。

    Spring 的单例 Bean 是否有线程安全问题?

    当多个用户同时请求一个服务时,容器会给每一个请求分配一个线程,这时多个线程会并发执行该请求对应的业务逻辑,如果业务逻辑有对单例状态的修改(体现为此单例的成员属性),则必须考虑线程安全问题。
    无状态bean和有状态bean
  • 有实例变量的bean,可以保存数据,是非线程安全的。
  • 没有实例变量的bean,不能保存数据,是线程安全的。

在Spring中无状态的Bean适合用单例模式,这样可以共享实例提高性能。有状态的Bean在多线程环境下不安全,一般用Prototype模式或者使用ThreadLocal解决线程安全问题。