Spring拾遗

一、Spring模块划分

  Spring六大模块:Core、Testing、DataAccess、web、Integration、Languages。

    Core:最核心的模块,包含Bean、Context、AOP,Bean是SpringBean,为了管理Bean又引入了Context和AOP

    Testing:为了方便测试,引入了Testing模块,包含Mock、TestContext

    DataAccess: 为了方便做数据操作,引入了数据模块,包含事务Tx、数据库连接JDBC、ORM

    web:对外提供web访问,包含Spring MVC和WebFlux

    Integration: 集成各种三方组件,例如remoting、JMS、WS;

    Languages: 集成其他基于JVM的语言,例如Kotlin、Groovy

        

  如上图所示:在Spring中最核心的是Bean和AOP,为了更好的使用Bean,需要Context来管理Bean,为了更好的对外提供服务,需要提供对外访问的功能(web),也无需要访问数据库,则需要了JDBC,数据库有事务处理,则需要TX,针对需要提供的功能,首先需要有web功能,spring替换拱了Spring MVC和WebFlux,为了方便做单元测试,引入了Testing,为了更好的做好业务,又引入了Springcore、SpringBatch、Spring Security,为了更好的和三方对接,引入了Interation,为了管理其他的数据源,例如redis、mongo等,引入了统一的Data,为了跟ORM框架集成,则引入了ORM模块。

二、AOP与动态代理

  1、动态代理

  在Spring中有jdk的动态代理和cglib动态代理两种:

    jdk基于接口,生成接口的实现类,cglib基于字节码,生成类的子类,性能相差不大,默认jdk,因为使用jdk不需要依赖三方包,但是由于必须要有接口的限制,还必须引入cglib

    jdk:实现InvocationHandler接口,重新invoke方法,使用Proxy类的newProxyInstance方法,传入目标类的类加载器、目标类的接口进行调用

    cjlib:实现MethodInterceptor,通过methodProxy.invokeSuper进行实现ioc:工厂+反射

    这里需要说明一点,spring framework,有接口默认使用jdk,没有接口则使用cglib,如果是接口,可以使用 @EnableAspectJAutoProxy(proxyTargetClass = true) 修改代理类的方式,默认为false,即JDK自带的动态代理,如果为true,则使用cglib。但是如果使用的springboot2.x,无论是否有接口,默认都是使用cglib,这是因为在AopAutoConfiguration中设置了proxy-target-class的默认值为true,可以通过修改配置文件的方式将其改为false。

  Cglib是字节码增强的技术,类似CGLIB这种字节码增强的技术还有ASM、AspectJ、JavaProxy、Javassist、Instrumentation,其中Intrumentation利用JavaAgent技术,在加载jar包或者类时对其进行增强,目前一些常见的APM工具,许多都是使用JavaAgent实现的。

  目前字节码增强新工具最常用的是ByteBuddy,因为其提供了更友好的操作API。

  2、AOP示例

@Aspect
@Component
public class LogAopService {

    /**
     * 环绕通知
     */
    @Pointcut(value = "execution(* com.lcl.spring.service.*.*(..))")
    public void point(){    }

    @Before(value = "point()")
    public void before(){
        System.out.println("========== BEFORE ========");
    }

    @AfterReturning(value = "point()")
    public void after(){
        System.out.println("======== AFTER ========");
    }

    @Around(value = "point()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("======== around before ========");
        Object proceed = proceedingJoinPoint.proceed();
        System.out.println("======== around AFTER ========");
        return proceed;
    }
}

  3、在springboot中获取SpringApplicationContext上下文的五种方式:

  首先创建一个SpringBeanUtil用来存储SpringApplicationContext

public class SpringBeanUtils {
    private static ApplicationContext applicationContext;
    public static void setApplicationContext(ApplicationContext applicationContext){
        SpringBeanUtils.applicationContext = applicationContext;
    }
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
}

  (1)实现ApplicationContextInitializer接口

@Component
public class MyApplicationContextInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        SpringBeanUtils.setApplicationContext(applicationContext);
    }
}

  (2)实现ApplicationListener接口

@Component
public class MyApplicationListener implements ApplicationListener<ApplicationContextEvent> {
    @Override
    public void onApplicationEvent(ApplicationContextEvent event) {
        SpringBeanUtils.setApplicationContext(event.getApplicationContext());
    }
}

  (3)放在启动类main方法中设置

    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class, args);
        SpringBeanUtils.setApplicationContext(applicationContext);
    }

  (4)实现ApplicationContextAware接口

@Component
public class SpringBeanUtils2 implements ApplicationContextAware {
    private static ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext){
        SpringBeanUtils2.applicationContext = applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

}

  最后使用SpringApplicationContext

DemoService demoService = applicationContext.getBean(DemoService.class);
 

 三、Spring Bean的生命周期

  在Spring刚开始的时候,主要的核心是Bean的管理,那么就有了BeanFactory来创建Bean对象,随着功能的增加,主键引入了环境感知的能力(EnvironmentCapable)、Bean列表能力(ListableBeanFactory)、层次结构能力(HierarchicalBeanFactory)、消息处理、事件处理能力(MessageSource)、事件发布能力(ApplicationEventpublisher)、资源模式转换能力(ResourcePatternResolver);然后Spring就将这些能力统一放到ApplicationContext容器中去,就形成了Spring最核心的容器,所有的Bean都在该容器中,Bean的生命周期也由其管理。

  Spring Bean的加载都是用Beanfactory进行创建的,创建完毕后,放入ApplicationContext管理。

        

 

  Spring Bean的生命周期总体可以分为:实例化、赋值、初始化、使用、销毁这几个阶段

    实例化:在源码中走的是Instantiation方法

    属性赋值:对于类中的属性赋值,可能需要注入其他Bean

    初始化:对Bean做各种处理,处理完成后,Bean创建完毕,如果是单例的(指定作用范围为 scope=“singleton”),放入 Spring IoC 缓存池中,触发 Spring 对该 Bean 生命周期管理;如果是scope=“prototype”,交给调用者管管理

    销毁:如 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法将 Spring 中的 Bean 销毁;如destory-method 属性指定销毁方法,调用该方法。

  源码如下图所示:

        

 

  初始化主要分为:

    1、检查Aware相关接口并设置相关依赖

      (1)如Bean 实现BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。

      (2)如 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。

      (3)如 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。

    2、前置过滤

      如 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工,此处非常重要,Spring 的 AOP 就是利用它实现的。

    3、初始化设置

      (1)如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。

      (2)如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。

    4、后置过滤器

      如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessAfterInitialization()。此时,Bean 可用了。

  初始化源码如下图所示:

        

 

   Bean的整个生命周期如下图所示:

        

  代码演示一下:

    首先是实现几个Aware接口

@AllArgsConstructor
@NoArgsConstructor
@ToString
@Component(value = "student001")
@Data
public class Student implements Serializable, BeanNameAware, ApplicationContextAware {

    private int id;
    private String name;
    private String beanName;
    private ApplicationContext applicationContext;

    public void init(){
        System.out.println("hello...........");
    }
    
    public static Student create(){
        return new Student(102,"KK102",null, null);
    }

    public void print() {
        System.out.println(this.beanName);
        System.out.println("   context.getBeanDefinitionNames() ===>> "
                + String.join(",", applicationContext.getBeanDefinitionNames()));
    }
}

  然后是实现前置过滤器和后置过滤器

@Component
public class HelloBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(" ====> postProcessBeforeInitialization " + beanName  +":"+ bean);
        // 可以加点额外处理
        // 例如
        if (bean instanceof Student) {
            Student student = (Student) bean;
            student.setName(student.getName() + "-" + System.currentTimeMillis());
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(" ====> postProcessAfterInitialization " + beanName +":"+ bean);
        return bean;
    }
} 

四、Spring 循环依赖

  Spring循环依赖主要依靠提前曝光和三级缓存,其中三级缓存:

    singletonObjects: 一级缓存,存储单例对象,Bean 已经实例化,初始化完成。

    earlySingletonObjects: 二级缓存,存储 singletonObject,这个 Bean 实例化了,还没有初始化。

    singletonFactories: 三级缓存,存储 singletonFactory。

  任何对象初始化时,都是从一二三级缓存中先查找对象是否存在,如果存在,则从缓存中获取,如果不存在,再进行Bean的创建,创建完成,会放入不同的缓存中。

  1、不引入其他Bean时Bean的创建过程


@Service public class CircularServiceA { private String fieldA = "字段 A"; }

  Spring 在创建 Bean 的过程中重点是在 AbstractAutowireCapableBeanFactory 中的以下三个步骤:

    实例化 createBeanInstance: 其中实例化 Bean 并对 Bean 进行赋值,像例子中的 fieldA 字段在这里就会赋值。

    属性注入 populateBean: 可以理解为对 Bean 里面的属性进行赋值。(会依赖其他 Bean)

    初始化 initializeBean: 执行初始化和 Bean 的后置处理器。

  2、引入其他Bean时Bean的创建过程

@Service
public class CircularServiceA {
    private String fieldA = "字段 A";
    @Autowired
    private CircularServiceB circularServiceB;
}
@Service
public class CircularServiceB {
}

    A对象初始化:首先调用createBeanInstance做实例化,先调用addSingletonFactory将当前Bean放入三级缓存,在调用populateBean做属性注入时,属性为B对象的Bean,那么就需要继续对B对象做初始化;

    B对象初始化:首先还是调用createBeanInstance做B对象的实例化,然后将其放入三级缓存,然后做属性注入,属性注入完毕后进行初始化,初始化完成后,从二级、三级缓存中移除对象,将B添加到一级缓存;

    A对象依赖的Bean初始化完成后:此时A对象属性赋值完成,继续进行初始化,初始化完成后,将A从二级三级缓存中移除,放入一级缓存。

        

  3、存在村换依赖时Bean的创建过程

@Service
public class CircularServiceA {
    private String fieldA = "字段 A";
    @Autowired
    private CircularServiceB circularServiceB;
}
@Service
public class CircularServiceB {
    @Autowired
    private CircularServiceA circularServiceA;
}

  循环依赖和上面的区别就在于B又引入了A,那么在流程如下:

    A对象实例化:和上面一样

    B对象实例化:这里就开始有区别,首先B对象依赖A对象,那么在调用populateBean方法做属性赋值的时候,需要获取对象A的实例;

    获取A对象实例:那么就会从缓存中获取,此时A对象在三级缓存中,获取到后,将A对象从那个三级缓存移到二级缓存,由于二级缓存的对象已经实例化没有初始化,因此可以认为A对象可以引用。

    B对象初始化:B对象此时已经拿到A对象的实例,则B对象可以进行初始化,初始化完成后将B添加到一级缓存,并从二级三级缓存中删除。

    A对象初始化:A对象此时也拿到了B对象的实例,因此也可以做初始化,初始化完成后,将A添加到一级缓存,并从二级三级缓存中删除。

        

  4、为什么要使用三级缓存解决循环依赖

   使用二级缓存是可以的,使用三级缓存主要的考虑点是因为动态代理。

  在Spring中,初始化Bean的方法initializeBean方法,这里需要初始化Bean并处理后置处理器,其中有一个处理器为:AnnotationAwareAspectJAutoProxyCreator其实就是加的注解切面,会跳转到AbstractAutoProxyCreator 类的 postProcessAfterInitialization 方法,这里就是Spring AOP的处理,最终返回的是一个代理对象,

  如果是有循环依赖的动态代理:

    创建 B 的时候,需要从三级缓存获取 A。此时在 getSingleton 方法中会调用:singletonObject = singletonFactory.getObject(); 这一块调用的是 getEarlyBeanReference,开始遍历执行 BeanPostProcessor。也就是说此时返回,并放到二级缓存的是一个 A 的代理对象。这样 B 就创建完毕了!到 A 开始初始化并执行后置处理器了!因为 A 也有代理,所以 A 也会执行到 postProcessAfterInitialization 这一部分!   

         

  可以看到,循环依赖下,有没有代理情况下的区别就在:singletonObject = singletonFactory.getObject();。在循环依赖发生的情况下 B 中的 A 赋值时:无代理:getObject 直接返回原来的 Bean;有代理:getObject 返回的是代理对象;然后都放到二级缓存。

  5、使用二级缓存是否可以?或者说singletonFactories有什么作用?去掉singletonFactories行不行

  去掉三级缓存之后,Bean 直接创建 earlySingletonObjects, 看着好像也可以。如果有代理的时候,在 earlySingletonObjects 直接放代理对象就行了。但是会导致一个问题:在实例化阶段就得执行后置处理器,判断有 AnnotationAwareAspectJAutoProxyCreator 并创建代理对象。

  这么一想,是不是会对 Bean 的生命周期有影响。

  同样,先创建 singletonFactory 的好处就是:在真正需要实例化的时候,再使用 singletonFactory.getObject() 获取 Bean 或者 Bean 的代理。相当于是延迟实例化。

  6、使用二级缓存是否可以?去掉二级缓存行不行

  如果去掉了二级缓存,则需要直接在 singletonFactory.getObject() 阶段初始化完毕,并放到一级缓存中。

  那有这么一种场景,B 和 C 都依赖了 A。要知道在有代理的情况下 singletonFactory.getObject() 获取的是代理对象。多次获取代理对象不同,而多次调用 singletonFactory.getObject() 返回的代理对象是不同的,就会导致 B 和 C 依赖了不同的 A。

   7、注意点

  Spring只能解决基于属性的循环依赖,如果是基于构造函数的循环依赖,则解决不了,因为实例化是就会循环等待,相当于造成死锁。

五、Spring XML原理

  1、xsd与配置文件

    描述XML文件格式定义有两种方式:XSD(XML Schema定义)、DTD(文档类型定义),Spring中用的是XSD,在Spring中,所有的XSD都被汇总到spring.schemas文件中,在开发时期,使用其定义和限制XML配置文件中的标签名称,而在Spring运行时,是需要将XML中定义的Bean加载成文真正的对象Bean,这块由spring.handler来控制。

    如下是Spring的XML配置文件,其中xmlns表示xml的namespace,如果不加冒号,表示当前的空间,如果加了冒号,表示指定的空间。在使用时,标签的名称需要加上xml的namespace,如果是默认的,不需要加。

<?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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <context:component-scan base-package="com.lcl.spring02" />
    <bean id="aop1" class="com.lcl.spring02.Aop1" />

 

    和xmlns对应的是xsi,其表示不同的xmlns对应的描述文件所在的地址,该地址都是网络地址。如果电脑不能联网,实际上在jar包的spring.schemas文件中也配置了全量的描述文件地址,在spring.schemas中配置的有具体对应的地址

        

 

 

     综上,在使用XML配置Spring时,其原理是:

      定义xsd文件,定义各种自定义标签

      在spring的配置文件中,通过schema.location找到对应的xsd文件,然后使用xsd用来检查配置的标签是否正确,如果不能联网,还提供了spring.schemas来获取到xsd

      是spring容器初始化过程中,通过spring.handlers用来从xml文件中加载解析成dom树,并加载成为Bean。

    例如像Dubbo等项目,都是对其中的XML做了重写。

  2、 简化编写xsd

    XML的xsd文件编写起来非常的繁琐,那如何能简化xsd文件编写呢?在Spring-commons中有一个XmlBeans,其可以来做Bean和xsd之间的转换,因此就产生了一个Spring-xbean的插件,其可以根据Bean字段的定义自动生成XSD文件,并根据Bean的字段结构,加载XML文件。好处是我们不需要再自己定义繁琐的XSD文件,缺点是丧失了灵活性,XML的标签属性必须要和Bean的属性一致。

    解析XML文件的方式有DOM和SAX两种,其中DOM是将XML全部加载到内存中,然后将文件解析成为DOM树,常见的技术有dom4j,SAX是流式的加载XML。

    SAX可以快速扫描一个大型的XML文档,当它找到查询标准时就会立即停止,然后再处理之。DOM是把XML全部加载到内存中建立一棵树之后再进行处理。所以DOM不适合处理大型的XML【会产生内存的急剧膨胀】。同理,DOM的弱项就是SAX的强项,SAX不必把全部的xml都加载到内存中。但是SAX的缺点也很明显,它只能对文件顺序解析一遍,不支持对文件的随意存取。SAX也仅仅能够读取文件的内容,并不能修改内容。DOM可以随意修改文件树,从而修改了xml文件。

    XML和Bean之间的相互转换,除了上面提到的Spring-xbean之外,还有xStream。

  3、SpringBean配置方式的演进变化

    上面描述了XML配置的方式,但是实际上现在已经很少使用XML的配置方式了,SpringBean配置方式的演变如下:

      刚开始时,使用的全都是XML配置

      spring 1.0-2.0版本时,增加了@Autowire注解,是XML配置+注解注入的方式

      spring 2.5,增加了@Service注解,实现了半自动注解

      spring 3.0,增加了@Bean和@Configration注解,实现了Java Config的配置

      spring 4.0 和Springboot,增加了@Condition和@AutoConfigX,实现了全自动的注解配置,可以动态的做配置

 六、其他

  1、resource和autowired的区别是什么;多个实现类使用autowired会有问题么:

    autowired是spring的,默认按类型装配,autowired是jdk的默认按名称装配;多个实现类使用autowired启动会报错,可以使用@Qualifier设置装配名称

  2、Spring中有哪些设计模式

    1、工厂模式:Spring使用工厂模式可以通过 BeanFactory 或 ApplicationContext 创建 bean 对象(两者对比:BeanFactory :延迟注入(使用到某个 bean 的时候才会注入),相比于BeanFactory 来说会占用更少的内存,程序启动速度更快。ApplicationContext :容器启动的时候,不管你用没用到,一次性创建所有 bean 。BeanFactory 仅提供了最基本的依赖注入支持,ApplicationContext 扩展了 BeanFactory ,除了有BeanFactory的功能还有额外更多功能,所以一般开发人员使用ApplicationContext会更多。ApplicationContext的三个实现类:ClassPathXmlApplication:把上下文文件当成类路径资源。FileSystemXmlApplication:从文件系统中的 XML 文件载入上下文定义信息。XmlWebApplicationContext:从Web系统中的XML文件载入上下文定义信息。)

    2、模版模式,在各种BeanFactory以及ApplicationContext实现中也都用到了

    3、代理模式,Spring AOP 利用了 AspectJ AOP实现的! AspectJ AOP 的底层用了动态代理

    4、策略模式,加载资源文件的方式,使用了不同的方法,比如:ClassPathResourece,FileSystemResource,ServletContextResource,UrlResource但他们都有共同的借口Resource;在Aop的实现中,采用了两种不同的方式,JDK动态代理和CGLIB代理

    5、单例模式,比如在创建bean的时候。

    6、观察者模式,spring中的ApplicationEvent,ApplicationListener,ApplicationEventPublisher

    7、适配器模式,MethodBeforeAdviceAdapter,ThrowsAdviceAdapter,AfterReturningAdapter

    8、装饰者模式,源码中类型带Wrapper或者Decorator的都是

  3、spring有多少IOC容器:

    Spring 提供了两种( 不是“个” ) IoC 容器,分别是 BeanFactory、ApplicationContext 。BeanFactory ,就像一个包含 Bean 集合的工厂类。它会在客户端要求时实例化 Bean 对象。ApplicationContext 接口扩展了 BeanFactory 接口,它在 BeanFactory 基础上提供了一些额外的功能。

    区别:BeanFactory使用懒加载,ApplicationContext即时加载;Beanfactory显式提供资源对象,ApplicationContext自己创建和管理对象;Beanfactory不支持国际化,ApplicationContext支持;Beanfactory不支持基于依赖的注解,ApplicationContext支持;Beanfactory被称为低级容器,ApplicationContext被称为高级容器。

    BeanFactory 最常用的是 XmlBeanFactory 。它可以根据 XML 文件中定义的内容,创建相应的 Bean。

    常见的 ApplicationContext 实现方式:

      1、ClassPathXmlApplicationContext :从 ClassPath 的 XML 配置文件中读取上下文,并生成上下文定义。应用程序上下文从程序环境变量中取得。示例代码如下:ApplicationContext context = new ClassPathXmlApplicationContext(“bean.xml”);

      2、FileSystemXmlApplicationContext :由文件系统中的XML配置文件读取上下文。示例代码如下:ApplicationContext context = new FileSystemXmlApplicationContext(“bean.xml”);

      3、XmlWebApplicationContext :由 Web 应用的XML文件读取上下文。例如我们在 Spring MVC 使用的情况。

      4、目前我们更多的是使用 Spring Boot 为主,所以使用的是第四种 ApplicationContext 容器,ConfigServletWebServerApplicationContext 。

  4、Spring 提供了以下五种标准的事件:

    上下文更新事件(ContextRefreshedEvent):该事件会在ApplicationContext 被初始化或者更新时发布。也可以在调用ConfigurableApplicationContext 接口中的 #refresh() 方法时被触发。

    上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext 的 #start() 方法开始/重新开始容器时触发该事件。

    上下文停止事件(ContextStoppedEvent):当容器调用 ConfigurableApplicationContext 的 #stop() 方法停止容器时触发该事件。

    上下文关闭事件(ContextClosedEvent):当ApplicationContext 被关闭时触发该事件。容器被关闭时,其管理的所有单例 Bean 都被销毁。

    请求处理事件(RequestHandledEvent):在 We b应用中,当一个HTTP 请求(request)结束触发该事件。

    除了上面介绍的事件以外,还可以通过扩展 ApplicationEvent 类来开发自定义的事件:继承ApplicationEvent自定义事件类,继承ApplicationListener自定义事件监听,通过 ApplicationContext 接口的 #publishEvent(Object event) 方法,来发布自定义事件

  5、如何实现一个IOC容器:

    先准备一个基本的容器对象,包含一些map结构的集合,用来方便后续过程中存储具体的对象

    进行配置文件的读取工作或者注解的解析工作,将需要创建的bean对象都封装成BeanDefinition对象存储在容器中

    容器将封装好的BeanDefinition对象通过反射的方式进行实例化,完成对象的实例化工作

    进行对象的初始化操作,也就是给类中的对应属性值就行设置,也就是进行依赖注入,完成整个对象的创建,变成一个完整的bean对象,存储在容器的某个map结构中

    通过容器对象来获取对象,进行对象的获取和逻辑处理工作

    提供销毁操作,当对象不用或者容器关闭的时候,将无用的对象进行销毁

  6、spring支持的bean作用域有哪些?

    singleton:使用该属性定义Bean时,IOC容器仅创建一个Bean实例,IOC容器每次返回的是同一个Bean实例。

    prototype:使用该属性定义Bean时,IOC容器可以创建多个Bean实例,每次返回的都是一个新的实例。

    request:该属性仅对HTTP请求产生作用,使用该属性定义Bean时,每次HTTP请求都会创建一个新的Bean,适用于WebApplicationContext环境。

    session:该属性仅用于HTTP Session,同一个Session共享一个Bean实例。不同Session使用不同的实例。

    global-session:该属性仅用于HTTP Session,同session作用域不同的是,所有的Session共享一个Bean实例。

  7、Spring框架中的单例Bean是线程安全的么?

    controller,service和dao本身并不是线程安全的,只是调用里面的方法,而且多线程调用一个实例的方法,会在内存中复制遍历,这是自己线程的工作内存,是最安全的。

    因此在进行使用的时候,不要在bean中声明任何有状态的实例变量或者类变量,如果必须如此,也推荐大家使用ThreadLocal把变量变成线程私有,如果bean的实例变量或者类变量需要在多个线程之间共享,那么就只能使用synchronized,lock,cas等这些实现线程同步的方法了。

  8、事务失效场景:

    选择的数据库存储引擎不支持事务

    被@Transactional 注解修饰的方法为非public类型

    异常没有抛出

    rollbackFor默认回滚的是:RuntimeException,实际抛出的异常比这个大,例如Exception

    本类方法调用,可以重新获取apo的方式:AopContext.currentProxy()).method

    事务传播机制不对,例如A方法有事务,调用B方法,B方法的事务设置为不支持原事务

    没有被spring管理

  9、事务传播属性:

    PROPAGATION_REQUIRED:Spring的默认传播级别,如果上下文中存在事务则加入当前事务,如果不存在事务则新建事务执行。

    PROPAGATION_SUPPORTS:如果上下文中存在事务则加入当前事务,如果没有事务则以非事务方式执行。

    PROPAGATION_MANDATORY:该传播级别要求上下文中必须存在事务,否则抛出异常。

    PROPAGATION_REQUIRES_NEW:该传播级别每次执行都会创建新事务,并同时将上下文中的事务挂起,执行完当前线程后再恢复上下文中事务。(子事务的执行结果不影响父事务的执行和回滚)

    PROPAGATION_NOT_SUPPORTED:当上下文中有事务则挂起当前事务,执行完当前逻辑后再恢复上下文事务。(降低事务大小,将非核心的执行逻辑包裹执行。)

    PROPAGATION_NEVER:该传播级别要求上下文中不能存在事务,否则抛出异常。

    PROPAGATION_NESTED:嵌套事务,如果上下文中存在事务则嵌套执行,如果不存在则新建事务。(save point概念)

  10、如何自定义一个注解:

    1、拦截器 + 注解 场景:

      定义注解类,使用@interface修饰,设置元注解,@Target表示注解可以修饰的作用域(类、方法、属性)@Retention表示注解的生命周期(源文件阶段、编译阶段、运行阶段)@Documented表示是否生成javaDoc,@Inherited表示子类是否可以继承父类的该注解

      自定义拦截器,验证拦截的方法或类上是否有自定义的注解,如果有,则作响应的处理

    2、AOP + 注解 实现优雅分布式锁/缓存场景

      定义注解

      定义AOP切面,在切面中配置环绕通知的包,在环绕通知中进行加锁解锁操作

  11、MVC 各个模块职责

    DispatcherServlet:中央控制器,把请求给转发到具体的控制类

    Controller:具体处理请求的控制器

    HandlerMapping:映射处理器,负责映射中央处理器转发给controller时的映射策略

    ModelAndView:服务层返回的数据和视图层的封装类

    ViewResolver:视图解析器,解析具体的视图

    Interceptors :拦截器,负责拦截我们定义的请求然后做处理工作

   DispatcherServlet拦截请求,根据请求的url调用handlermapping获取对应的handler,根据handler调用对应的handleradpter,然后由handleradpter调用hander,hander执行完毕后返回给handleradpter、DispatcherServelet,然后DispatcherServelet调用事务解析器进行解析渲染。

  在应用启动的时候,Spring容器会加载这些Controller类,并且解析出URL对应的处理函数,封装成Handler对象,存储到HandlerMapping对象中。当有请求到来的时候,DispatcherServlet从HanderMapping中,查找请求URL对应的Handler,然后调用执行Handler对应的函数代码,最后将执行结果返回给客户端

 
posted @ 2022-06-04 13:56  李聪龙  阅读(95)  评论(0编辑  收藏  举报