关于spring:三天吃透Spring面试八股文最新整理

1次阅读

共计 25033 个字符,预计需要花费 63 分钟才能阅读完成。

本文曾经收录到 Github 仓库,该仓库蕴含 计算机根底、Java 根底、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享 等外围知识点,欢送 star~

Github 地址:https://github.com/Tyson0314/…


Spring 的长处

  • 通过管制反转和依赖注入实现 松耦合
  • 反对 面向切面 的编程,并且把利用业务逻辑和零碎服务离开。
  • 通过切面和模板缩小样板式代码。
  • 申明式事务的反对。能够从枯燥繁冗的事务管理代码中解脱进去,通过申明式形式灵便地进行事务的治理,进步开发效率和品质。
  • 不便集成各种优良框架。外部提供了对各种优良框架的间接反对(如:Hessian、Quartz、MyBatis 等)。
  • 不便程序的测试。Spring 反对 Junit4,增加注解便能够测试 Spring 程序。

Spring 用到了哪些设计模式?

1、简略工厂模式 BeanFactory 就是简略工厂模式的体现,依据传入一个惟一标识来取得 Bean 对象。

@Override
public Object getBean(String name) throws BeansException {assertBeanFactoryActive();
    return getBeanFactory().getBean(name);
}

2、工厂办法模式 FactoryBean 就是典型的工厂办法模式。spring 在应用 getBean() 调用取得该 bean 时,会主动调用该 bean 的 getObject() 办法。每个 Bean 都会对应一个 FactoryBean,如 SqlSessionFactory 对应 SqlSessionFactoryBean

3、单例模式:一个类仅有一个实例,提供一个拜访它的全局拜访点。Spring 创立 Bean 实例默认是单例的。

4、适配器模式:SpringMVC 中的适配器HandlerAdatper。因为利用会有多个 Controller 实现,如果须要间接调用 Controller 办法,那么须要先判断是由哪一个 Controller 解决申请,而后调用相应的办法。当减少新的 Controller,须要批改原来的逻辑,违反了开闭准则(对批改敞开,对扩大凋谢)。

为此,Spring 提供了一个适配器接口,每一种 Controller 对应一种 HandlerAdapter 实现类,当申请过去,SpringMVC 会调用 getHandler() 获取相应的 Controller,而后获取该 Controller 对应的 HandlerAdapter,最初调用 HandlerAdapterhandle()办法解决申请,实际上调用的是 Controller 的handleRequest()。每次增加新的 Controller 时,只须要减少一个适配器类就能够,无需批改原有的逻辑。

罕用的处理器适配器:SimpleControllerHandlerAdapterHttpRequestHandlerAdapterAnnotationMethodHandlerAdapter

// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

public class HttpRequestHandlerAdapter implements HandlerAdapter {

    @Override
    public boolean supports(Object handler) {//handler 是被适配的对象,这里应用的是对象的适配器模式
        return (handler instanceof HttpRequestHandler);
    }

    @Override
    @Nullable
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {((HttpRequestHandler) handler).handleRequest(request, response);
        return null;
    }
}

5、代理模式 :spring 的 aop 应用了动静代理,有两种形式JdkDynamicAopProxyCglib2AopProxy

6、观察者模式:spring 中 observer 模式罕用的中央是 listener 的实现,如ApplicationListener

7、模板模式:Spring 中 jdbcTemplatehibernateTemplate 等,就应用到了模板模式。

什么是 AOP?

面向切面 编程,作为面向对象的一种补充,将公共逻辑(事务管理、日志、缓存等)封装成切面,跟业务代码进行拆散,能够缩小零碎的反复代码和升高模块之间的耦合度。切面就是那些与业务无关,但所有业务模块都会调用的公共逻辑。

AOP 有哪些实现形式?

AOP 有两种实现形式:动态代理和动静代理。

动态代理

动态代理:代理类在编译阶段生成,在编译阶段将告诉织入 Java 字节码中,也称编译时加强。AspectJ 应用的是动态代理。

毛病:代理对象须要与指标对象实现一样的接口,并且实现接口的办法,会有冗余代码。同时,一旦接口减少办法,指标对象与代理对象都要保护。

动静代理

动静代理:代理类在程序运行时创立,AOP 框架不会去批改字节码,而是在内存中长期生成一个代理对象,在运行期间对业务办法进行加强,不会生成新类。

Spring AOP 的实现原理

SpringAOP 实现原理其实很简略,就是通过 动静代理 实现的。如果咱们为 Spring 的某个 bean 配置了切面,那么 Spring 在创立这个 bean 的时候,实际上创立的是这个 bean 的一个代理对象,咱们后续对 bean 中办法的调用,实际上调用的是代理类重写的代理办法。而 SpringAOP应用了两种动静代理,别离是JDK 的动静代理,以及CGLib 的动静代理

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 做动静代理的。

长处:指标类不须要实现特定的接口,更加灵便。

什么时候采纳哪种动静代理?

  1. 如果指标对象实现了接口,默认状况下会采纳 JDK 的动静代理实现 AOP
  2. 如果指标对象实现了接口,能够强制应用 CGLIB 实现 AOP
  3. 如果指标对象没有实现了接口,必须采纳 CGLIB 库

两者的区别

  1. jdk 动静代理应用 jdk 中的类 Proxy 来创立代理对象,它应用反射技术来实现,不须要导入其余依赖。cglib 须要引入相干依赖:asm.jar,它应用字节码加强技术来实现。
  2. 当指标类实现了接口的时候 Spring Aop 默认应用 jdk 动静代理形式来加强办法,没有实现接口的时候应用 cglib 动静代理形式加强办法。

Spring AOP 相干术语

(1)切面(Aspect):切面是告诉和切点的联合。告诉和切点独特定义了切面的全部内容。

(2)连接点(Join point):指办法,在 Spring AOP 中,一个连接点总是代表一个办法的执行。连接点是在利用执行过程中可能插入切面的一个点。这个点能够是调用办法时、抛出异样时、甚至批改一个字段时。切面代码能够利用这些点插入到利用的失常流程之中,并增加新的行为。

(3)告诉(Advice):在 AOP 术语中,切面的工作被称为告诉。

(4)切入点(Pointcut):切点的定义会匹配告诉所要织入的一个或多个连接点。咱们通常应用明确的类和办法名称,或是利用正则表达式定义所匹配的类和办法名称来指定这些切点。

(5)引入(Introduction):引入容许咱们向现有类增加新办法或属性。

(6)指标对象(Target Object):被一个或者多个切面(aspect)所告诉(advise)的对象。它通常是一个代理对象。

(7)织入(Weaving):织入是把切面利用到指标对象并创立新的代理对象的过程。在指标对象的生命周期里有以下工夫点能够进行织入:

  • 编译期:切面在指标类编译时被织入。AspectJ 的织入编译器是以这种形式织入切面的。
  • 类加载期:切面在指标类加载到 JVM 时被织入。须要非凡的类加载器,它能够在指标类被引入利用之前加强该指标类的字节码。AspectJ5 的加载时织入就反对以这种形式织入切面。
  • 运行期:切面在利用运行的某个时刻被织入。个别状况下,在织入切面时,AOP 容器会为指标对象动静地创立一个代理对象。SpringAOP 就是以这种形式织入切面。

Spring 告诉有哪些类型?

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

Spring 切面能够利用 5 种类型的告诉:

  1. 前置告诉(Before):在指标办法被调用之前调用告诉性能;
  2. 后置告诉(After):在指标办法实现之后调用告诉,此时不会关怀办法的输入是什么;
  3. 返回告诉(After-returning):在指标办法胜利执行之后调用告诉;
  4. 异样告诉(After-throwing):在指标办法抛出异样后调用告诉;
  5. 盘绕告诉(Around):告诉包裹了被告诉的办法,在被告诉的办法调用之前和调用之后执行自定义的逻辑。

什么是 IOC?

IOC:管制反转,由 Spring 容器治理 bean 的整个生命周期。通过反射实现对其余对象的管制,包含初始化、创立、销毁等,解放手动创建对象的过程,同时升高类之间的耦合度。

IOC 的益处?

ioc 的思维最外围的中央在于,资源不禁应用资源者治理,而由不应用资源的第三方治理,这能够带来很多益处。第一,资源集中管理,实现资源的可配置和易治理。第二,升高了应用资源单方的依赖水平,也就是咱们说的耦合度。

也就是说,甲方要达成某种目标不须要间接依赖乙方,它只须要达到的目标通知第三方机构就能够了,比方甲方须要一双袜子,而乙方它卖一双袜子,它要把袜子卖出去,并不需要本人去间接找到一个卖家来实现袜子的卖出。它也只须要找第三方,通知他人我要卖一双袜子。这下好了,甲乙双方进行交易流动,都不须要本人间接去找卖家,相当于程序外部凋谢接口,卖家由第三方作为参数传入。甲乙相互不依赖,而且只有在进行交易流动的时候,甲才和乙产生分割。反之亦然。这样做什么益处么呢,甲乙能够在对方不实在存在的状况下独立存在,而且保障不交易时候无分割,想交易的时候能够很容易的产生分割。甲乙交易流动不须要单方见面,防止了单方的互不信赖造成交易失败的问题。因为交易由第三方来负责分割,而且甲乙都认为第三方牢靠。那么交易就能很牢靠很灵便的产生和进行了。

这就是 ioc 的核心思想。生存中这种例子亘古未有,支付宝在整个淘宝体系里就是宏大的 ioc 容器,交易单方之外的第三方,提供可靠性可依赖可灵便变更交易方的资源管理核心。另外人事代理也是,雇佣机构和集体之外的第三方。

参考链接:https://www.zhihu.com/questio…

什么是依赖注入?

在 Spring 创建对象的过程中,把对象依赖的属性注入到对象中。依赖注入次要有两种形式:结构器注入和属性注入。

IOC 容器初始化过程?

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

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

// AbstractApplicationContext
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

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

loadBeanDefinitions采纳了模板模式,具体加载 BeanDefinition 的逻辑由各个子类实现。

Bean 的生命周期

1. 调用 bean 的构造方法创立 Bean

2. 通过反射调用 setter 办法进行属性的依赖注入

3. 如果 Bean 实现了 BeanNameAware 接口,Spring 将调用 setBeanName(),设置 Bean 的 name(xml 文件中 bean 标签的 id)

4. 如果 Bean 实现了 BeanFactoryAware 接口,Spring 将调用 setBeanFactory() 把 bean factory 设置给 Bean

5. 如果存在BeanPostProcessor,Spring 将调用它们的postProcessBeforeInitialization(预初始化)办法,在 Bean 初始化前对其进行解决

6. 如果 Bean 实现了 InitializingBean 接口,Spring 将调用它的 afterPropertiesSet 办法,而后调用 xml 定义的 init-method 办法,两个办法作用相似,都是在初始化 bean 的时候执行

7. 如果存在BeanPostProcessor,Spring 将调用它们的postProcessAfterInitialization(后初始化)办法,在 Bean 初始化后对其进行解决

8.Bean 初始化实现,供给用应用,这里分两种状况:

8.1 如果 Bean 为单例的话,那么容器会返回 Bean 给用户,并存入缓存池。如果 Bean 实现了 DisposableBean 接口,Spring 将调用它的 destory 办法,而后调用在 xml 中定义的 destory-method办法,这两个办法作用相似,都是在 Bean 实例销毁前执行。

8.2 如果 Bean 是多例的话,容器将 Bean 返回给用户,剩下的生命周期由用户管制。

public interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}
    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {return bean;}
}
public interface InitializingBean {void afterPropertiesSet() throws Exception;
}

BeanFactory 和 FactoryBean 的区别?

BeanFactory:治理 Bean 的容器,Spring 中生成的 Bean 都是由这个接口的实现来治理的。

FactoryBean:通常是用来创立比较复杂的 bean,个别的 bean 间接用 xml 配置即可,但如果一个 bean 的创立过程中波及到很多其余的 bean 和简单的逻辑,间接用 xml 配置比拟麻烦,这时能够思考用 FactoryBean,能够暗藏实例化简单 Bean 的细节。

当配置文件中 bean 标签的 class 属性配置的实现类是 FactoryBean 时,通过 getBean()办法返回的不是 FactoryBean 自身,而是调用 FactoryBean#getObject()办法所返回的对象,相当于 FactoryBean#getObject()代理了 getBean()办法。如果想得到 FactoryBean 必须应用 ‘&’ + beanName 的形式获取。

Mybatis 提供了 SqlSessionFactoryBean,能够简化 SqlSessionFactory的配置:

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
  @Override
  public void afterPropertiesSet() throws Exception {notNull(dataSource, "Property'dataSource'is required");
    notNull(sqlSessionFactoryBuilder, "Property'sqlSessionFactoryBuilder'is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
              "Property'configuration'and'configLocation'can not specified with together");
    this.sqlSessionFactory = buildSqlSessionFactory();}

  protected SqlSessionFactory buildSqlSessionFactory() throws IOException {// 简单逻辑}
    
  @Override
  public SqlSessionFactory getObject() throws Exception {if (this.sqlSessionFactory == null) {afterPropertiesSet();
    }
    return this.sqlSessionFactory;
  }
}

在 xml 配置 SqlSessionFactoryBean:

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

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

BeanFactory 和 ApplicationContext 有什么区别?

BeanFactory 和 ApplicationContext 是 Spring 的两大外围接口,都能够当做 Spring 的容器。其中 ApplicationContext 是 BeanFactory 的子接口。

两者区别如下:

1、性能上的区别。BeanFactory 是 Spring 外面最底层的接口,蕴含了各种 Bean 的定义,读取 bean 配置文档,治理 bean 的加载、实例化,管制 bean 的生命周期,保护 bean 之间的依赖关系。

ApplicationContext 接口作为 BeanFactory 的派生,除了提供 BeanFactory 所具备的性能外,还提供了更残缺的框架性能,如继承 MessageSource、反对国际化、对立的资源文件拜访形式、同时加载多个配置文件等性能。

2、加载形式的区别。BeanFactroy 采纳的是提早加载模式来注入 Bean 的,即只有在应用到某个 Bean 时(调用 getBean()),才对该 Bean 进行加载实例化。这样,咱们就不能发现一些存在的 Spring 的配置问题。如果 Bean 的某一个属性没有注入,BeanFacotry 加载后,直至第一次应用调用 getBean 办法才会抛出异样。

而 ApplicationContext 是在容器启动时,一次性创立了所有的 Bean。这样,在容器启动时,咱们就能够发现 Spring 中存在的配置谬误,这样有利于查看所依赖属性是否注入。ApplicationContext 启动后预载入所有的单例 Bean,那么在须要的时候,不须要期待创立 bean,因为它们曾经创立好了。

绝对于根本的 BeanFactory,ApplicationContext 惟一的有余是占用内存空间。当应用程序配置 Bean 较多时,程序启动较慢。

3、创立形式的区别。BeanFactory 通常以编程的形式被创立,ApplicationContext 还能以申明的形式创立,如应用 ContextLoader。

4、注册形式的区别。BeanFactory 和 ApplicationContext 都反对 BeanPostProcessor、BeanFactoryPostProcessor 的应用,但两者之间的区别是:BeanFactory 须要手动注册,而 ApplicationContext 则是主动注册。

Bean 注入容器有哪些形式?

1、@Configuration + @Bean

@Configuration 用来申明一个配置类,而后应用 @Bean 注解,用于申明一个 bean,将其退出到 Spring 容器中。

@Configuration
public class MyConfiguration {
    @Bean
    public Person person() {Person person = new Person();
        person.setName("大彬");
        return person;
    }
}

2、通过包扫描特定注解的形式

@ComponentScan 搁置在咱们的配置类上,而后能够指定一个门路,进行扫描带有特定注解的 bean,而后加至容器中。

特定注解包含 @Controller、@Service、@Repository、@Component

@Component
public class Person {//...}
 
@ComponentScan(basePackages = "com.dabin.test.*")
public class Demo1 {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Demo1.class);
        Person bean = applicationContext.getBean(Person.class);
        System.out.println(bean);
    }
}

3、@Import 注解导入

@Import 注解平时开发用的不多,然而也是十分重要的,在进行 Spring 扩大时常常会用到,它常常搭配自定义注解进行应用,而后往容器中导入一个配置文件。

@ComponentScan
/* 把用到的资源导入到以后容器中 */
@Import({Person.class})
public class App {public static void main(String[] args) throws Exception {ConfigurableApplicationContext context = SpringApplication.run(App.class, args);
        System.out.println(context.getBean(Person.class));
        context.close();}
}

4、实现 BeanDefinitionRegistryPostProcessor 进行后置解决。

在 Spring 容器启动的时候会执行 BeanDefinitionRegistryPostProcessor 的 postProcessBeanDefinitionRegistry 办法,就是等 beanDefinition 加载结束之后,对 beanDefinition 进行后置解决,能够在此进行调整 IOC 容器中的 beanDefinition,从而烦扰到前面进行初始化 bean。

在上面的代码中,咱们手动向 beanDefinitionRegistry 中注册了 person 的 BeanDefinition。最终胜利将 person 退出到 applicationContext 中。

public class Demo1 {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        MyBeanDefinitionRegistryPostProcessor beanDefinitionRegistryPostProcessor = new MyBeanDefinitionRegistryPostProcessor();
        applicationContext.addBeanFactoryPostProcessor(beanDefinitionRegistryPostProcessor);
        applicationContext.refresh();
        Person bean = applicationContext.getBean(Person.class);
        System.out.println(bean);
    }
}
 
class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Person.class).getBeanDefinition();
        registry.registerBeanDefinition("person", beanDefinition);
    }
    
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}}

5、应用 FactoryBean 接口

如下图代码,应用 @Configuration + @Bean 的形式将 PersonFactoryBean 退出到容器中,这里没有向容器中间接注入 Person,而是注入 PersonFactoryBean,而后从容器中拿 Person 这个类型的 bean。

@Configuration
public class Demo1 {
    @Bean
    public PersonFactoryBean personFactoryBean() {return new PersonFactoryBean();
    }
 
    public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Demo1.class);
        Person bean = applicationContext.getBean(Person.class);
        System.out.println(bean);
    }
}
 
class PersonFactoryBean implements FactoryBean<Person> {
    @Override
    public Person getObject() throws Exception {return new Person();
    }

    @Override
    public Class<?> getObjectType() {return Person.class;}
}

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 作用域。

Spring 主动拆卸的形式有哪些?

Spring 的主动拆卸有三种模式:byType(依据类型),byName(依据名称)、constructor(依据构造函数)。

byType

找到与依赖类型雷同的 bean 注入到另外的 bean 中,这个过程须要借助 setter 注入来实现,因而必须存在 set 办法,否则注入失败。

当 xml 文件中存在多个雷同类型名称不同的实例 Bean 时,Spring 容器依赖注入依然会失败,因为存在多种适宜的选项,Spring 容器无奈晓得该注入那种,此时咱们须要为 Spring 容器提供帮忙,指定注入那个 Bean 实例。能够通过 标签的 autowire-candidate 设置为 false 来过滤那些不须要注入的实例 Bean

<bean id="userDao"  class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />

<!-- autowire-candidate="false" 过滤该类型 -->
<bean id="userDao2" autowire-candidate="false" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />

<!-- byType 依据类型主动拆卸 userDao-->
<bean id="userService" autowire="byType" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />

byName

将属性名与 bean 名称进行匹配,如果找到则注入依赖 bean。

<bean id="userDao"  class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />
<bean id="userDao2" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" />

<!-- byName 依据名称主动拆卸,找到 UserServiceImpl 名为 userDao 属性并注入 -->
<bean id="userService" autowire="byName" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />

constructor

存在单个实例则优先按类型进行参数匹配(无论名称是否匹配),当存在多个类型雷同实例时,按名称优先匹配,如果没有找到对应名称,则注入失败。

@Autowired 和 @Resource 的区别?

Autowire 是 spring 的注解。默认状况下 @Autowired 是按类型匹配的 (byType)。如果须要按名称(byName) 匹配的话,能够应用 @Qualifier 注解与 @Autowired 联合。@Autowired 能够传递一个 required=false 的属性,false 指明当 userDao 实例存在就注入不存就疏忽,如果为 true,就必须注入,若 userDao 实例不存在,就抛出异样。

public class UserServiceImpl implements UserService {
    // 标注成员变量
    @Autowired
    @Qualifier("userDao1")
    private UserDao userDao;   
 }

Resource 是 j2ee 的注解,默认按 byName 模式主动注入。@Resource 有两个中重要的属性:name 和 type。name 属性指定 bean 的名字,type 属性则指定 bean 的类型。因而应用 name 属性,则按 byName 模式的主动注入策略,如果应用 type 属性,则按 byType 模式主动注入策略。假使既不指定 name 也不指定 type 属性,Spring 容器将通过反射技术默认按 byName 模式注入。

@Resource(name="userDao")
private UserDao  userDao;// 用于成员变量

// 也能够用于 set 办法标注
@Resource(name="userDao")
public void setUserDao(UserDao userDao) {this.userDao= userDao;}

上述两种主动拆卸的依赖注入并不适宜简略值类型,如 int、boolean、long、String 以及 Enum 等,对于这些类型,Spring 容器也提供了 @Value 注入的形式。

@Value 和 @Autowired、@Resource 相似,也是用来对属性进行注入的,只不过 @Value 是用来从 Properties 文件中来获取值的,并且 @Value 能够解析 SpEL(Spring 表达式)。

比方,jdbc.properties 文件如下:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/test?characterEncoding=UTF-8&allowMultiQueries=true
jdbc.username=root
jdbc.password=root

利用注解 @Value 获取 jdbc.url 和 jdbc.username 的值,实现如下:

public class UserServiceImpl implements UserService {
    // 占位符形式
    @Value("${jdbc.url}")
    private String url;
    //SpEL 表达方式,其中代表 xml 配置文件中的 id 值 configProperties
    @Value("#{configProperties['jdbc.username']}")
    private String userName;

}

@Qualifier 注解有什么作用

当须要创立多个雷同类型的 bean 并心愿仅应用属性拆卸其中一个 bean 时,能够应用@Qualifier 注解和 @Autowired 通过指定应该拆卸哪个 bean 来打消歧义。

@Bean 和 @Component 有什么区别?

都是应用注解定义 Bean。@Bean 是应用 Java 代码拆卸 Bean,@Component 是主动拆卸 Bean。

@Component 注解用在类上,表明一个类会作为组件类,并告知 Spring 要为这个类创立 bean,每个类对应一个 Bean。

@Bean 注解用在办法上,示意这个办法会返回一个 Bean。@Bean 须要在配置类中应用,即类上须要加上 @Configuration 注解。

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

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

@Bean 注解更加灵便。当须要将第三方类拆卸到 Spring 容器中,因为没方法源代码上增加 @Component 注解,只能应用 @Bean 注解的形式,当然也能够应用 xml 的形式。

@Component、@Controller、@Repositor 和 @Service 的区别?

@Component:最一般的组件,能够被注入到 spring 容器进行治理。

@Controller:将类标记为 Spring Web MVC 控制器。

@Service:将类标记为业务层组件。

@Repository:将类标记为数据拜访组件,即 DAO 组件。

Spring 事务实现形式有哪些?

事务就是一系列的操作原子执行。Spring 事务机制次要包含申明式事务和编程式事务。

  • 编程式事务:通过编程的形式治理事务,这种形式带来了很大的灵活性,但很难保护。
  • 申明式事务:将事务管理代码从业务办法中分离出来,通过 aop 进行封装。Spring 申明式事务使得咱们无须要去解决取得连贯、敞开连贯、事务提交和回滚等这些操作。应用 @Transactional 注解开启申明式事务。

@Transactional相干属性如下:

属性 类型 形容
value String 可选的限定描述符,指定应用的事务管理器
propagation enum: Propagation 可选的事务流传行为设置
isolation enum: Isolation 可选的事务隔离级别设置
readOnly boolean 读写或只读事务,默认读写
timeout int (in seconds granularity) 事务超时工夫设置
rollbackFor Class 对象数组,必须继承自 Throwable 导致事务回滚的异样类数组
rollbackForClassName 类名数组,必须继承自 Throwable 导致事务回滚的异样类名字数组
noRollbackFor Class 对象数组,必须继承自 Throwable 不会导致事务回滚的异样类数组
noRollbackForClassName 类名数组,必须继承自 Throwable 不会导致事务回滚的异样类名字数组

有哪些事务流传行为?

在 TransactionDefinition 接口中定义了七个事务流传行为:

  1. PROPAGATION_REQUIRED如果存在一个事务,则反对以后事务。如果没有事务则开启一个新的事务。如果嵌套调用的两个办法都加了事务注解,并且运行在雷同线程中,则这两个办法应用雷同的事务中。如果运行在不同线程中,则会开启新的事务。
  2. PROPAGATION_SUPPORTS 如果存在一个事务,反对以后事务。如果没有事务,则非事务的执行。
  3. PROPAGATION_MANDATORY 如果曾经存在一个事务,反对以后事务。如果不存在事务,则抛出异样IllegalTransactionStateException
  4. PROPAGATION_REQUIRES_NEW 总是开启一个新的事务。须要应用 JtaTransactionManager 作为事务管理器。
  5. PROPAGATION_NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务。须要应用 JtaTransactionManager 作为事务管理器。
  6. PROPAGATION_NEVER 总是非事务地执行,如果存在一个流动事务,则抛出异样。
  7. PROPAGATION_NESTED 如果一个流动的事务存在,则运行在一个嵌套的事务中。如果没有流动事务, 则按 PROPAGATION_REQUIRED 属性执行。

PROPAGATION_NESTED 与 PROPAGATION_REQUIRES_NEW 的区别:

应用 PROPAGATION_REQUIRES_NEW 时,内层事务与外层事务是两个独立的事务。一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。

应用 PROPAGATION_NESTED 时,外层事务的回滚能够引起内层事务的回滚。而内层事务的异样并不会导致外层事务的回滚,它是一个真正的嵌套事务。

Spring 事务在什么状况下会生效?

1. 拜访权限问题

java 的拜访权限次要有四种:private、default、protected、public,它们的权限从左到右,顺次变大。

如果事务办法的拜访权限不是定义成 public,这样会导致事务生效,因为 spring 要求被代理办法必须是 public 的。

打开源码,能够看到,在 AbstractFallbackTransactionAttributeSource 类的 computeTransactionAttribute 办法中有个判断,如果指标办法不是 public,则返回 null,即不反对事务。

protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
    // Don't allow no-public methods as required.
    if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {return null;}
    ...
}

2. 办法用 final 润饰

如果事务办法用 final 润饰,将会导致事务生效。因为 spring 事务底层应用了 aop,也就是通过 jdk 动静代理或者 cglib,帮咱们生成了代理类,在代理类中实现的事务性能。

但如果某个办法用 final 润饰了,那么在它的代理类中,就无奈重写该办法,而增加事务性能。

同理,如果某个办法是 static 的,同样无奈通过动静代理,变成事务办法。

3. 对象没有被 spring 治理

应用 spring 事务的前提是:对象要被 spring 治理,须要创立 bean 实例。如果类没有加 @Controller、@Service、@Component、@Repository 等注解,即该类没有交给 spring 去治理,那么它的办法也不会生成事务。

4. 表不反对事务

如果 MySQL 应用的存储引擎是 myisam,这样的话是不反对事务的。因为 myisam 存储引擎不反对事务。

5. 办法外部调用

如下代码所示,update 办法下面没有加 @Transactional 注解,调用有 @Transactional 注解的 updateOrder 办法,updateOrder 办法上的事务会生效。

因为产生了本身调用,调用该类本人的办法,而没有通过 Spring 的代理类,只有在内部调用事务才会失效。

@Service
public class OrderServiceImpl implements OrderService {public void update(Order order) {this.updateOrder(order);
    }

    @Transactional
    public void updateOrder(Order order) {// update order}
}

解决办法:

1、再申明一个 service,将外部调用改为内部调用

2、应用编程式事务

3、应用 AopContext.currentProxy()获取代理对象

@Servcie
public class OrderServiceImpl implements OrderService {public void update(Order order) {((OrderService)AopContext.currentProxy()).updateOrder(order);
   }

    @Transactional
    public void updateOrder(Order order) {// update order}
 }

6. 未开启事务

如果是 spring 我的项目,则须要在配置文件中手动配置事务相干参数。如果忘了配置,事务必定是不会失效的。

如果是 springboot 我的项目,那么不须要手动配置。因为 springboot 曾经在 DataSourceTransactionManagerAutoConfiguration 类中帮咱们开启了事务。

7. 吞了异样

有时候事务不会回滚,有可能是在代码中手动 catch 了异样。因为开发者本人捕捉了异样,又没有手动抛出,把异样吞掉了,这种状况下 spring 事务不会回滚。

如果想要 spring 事务可能失常回滚,必须抛出它可能解决的异样。如果没有抛异样,则 spring 认为程序是失常的。

Spring 怎么解决循环依赖的问题?

首先,有两种 Bean 注入的形式。

结构器注入和属性注入。

对于结构器注入的循环依赖,Spring 解决不了,会间接抛出 BeanCurrentlylnCreationException 异样。

对于属性注入的循环依赖(单例模式下),是通过三级缓存解决来循环依赖的。

而非单例对象的循环依赖,则无奈解决。

上面剖析单例模式下属性注入的循环依赖是怎么解决的:

首先,Spring 单例对象的初始化大略分为三步:

  1. createBeanInstance:实例化 bean,应用构造方法创建对象,为对象分配内存。
  2. populateBean:进行依赖注入。
  3. initializeBean:初始化 bean。

Spring 为了解决单例的循环依赖问题,应用了三级缓存:

singletonObjects:实现了初始化的单例对象 map,bean name –> bean instance

earlySingletonObjects :实现实例化未初始化的单例对象 map,bean name –> bean instance

singletonFactories :单例对象工厂 map,bean name –> ObjectFactory,单例对象实例化实现之后会退出 singletonFactories。

在调用 createBeanInstance 进行实例化之后,会调用 addSingletonFactory,将单例对象放到 singletonFactories 中。

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

如果 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 解决线程平安问题。

Spring Bean 如何保障并发平安?

Spring 的 Bean 默认都是单例的,某些状况下,单例是并发不平安的。

Controller 举例,如果咱们在 Controller 中定义了成员变量。当多个申请降临,进入的都是同一个单例的 Controller 对象,并对此成员变量的值进行批改操作,因而会相互影响,会有并发平安的问题。

应该怎么解决呢?

为了让多个 HTTP 申请之间不相互影响,能够采取以下措施:

1、单例变原型

对 web 我的项目,能够 Controller 类上加注解 @Scope("prototype")@Scope("request"),对非 web 我的项目,在 Component 类上增加注解 @Scope("prototype")

这种形式实现起来非常简单,然而很大水平上增大了 Bean 创立实例化销毁的服务器资源开销。

2、尽量避免应用成员变量

在业务容许的条件下,能够将成员变量替换为办法中的局部变量。这种形式集体认为是最失当的。

3、应用并发平安的类

如果非要在单例 Bean 中应用成员变量,能够思考应用并发平安的容器,如 ConcurrentHashMapConcurrentHashSet 等等,将咱们的成员变量包装到这些并发平安的容器中进行治理即可。

4、分布式或微服务的并发平安

如果还要进一步思考到微服务或分布式服务的影响,形式 3 便不适合了。这种状况下能够借助于能够共享某些信息的分布式缓存中间件,如 Redis 等。这样即可保障同一种服务的不同服务实例都领有同一份共享信息了。

@Async 注解的原理

当咱们调用第三方接口或者办法的时候,咱们不须要期待办法返回才去执行其它逻辑,这时如果响应工夫过长,就会极大的影响程序的执行效率。所以这时就须要应用异步办法来并行执行咱们的逻辑。在 springboot 中能够应用 @Async 注解实现异步操作。

应用 @Async 注解实现异步操作的步骤:

1. 首先在启动类上增加 @EnableAsync 注解。

@Configuration
@EnableAsync
public class App {public static void main(String[] args) {
         ApplicationContext ctx = new  
             AnnotationConfigApplicationContext(App.class);
        MyAsync service = ctx.getBean(MyAsync.class);
        System.out.println(service.getClass());
        service.async1();
        System.out.println("main thread finish...");
    }
}

2. 在对应的办法上增加 @Async 注解。

@Component
public class MyAsync {
    @Async
    public void asyncTest() {
        try {TimeUnit.SECONDS.sleep(20);
        } catch (InterruptedException e) {e.printStackTrace();
        }
        System.out.println("asyncTest...");
    }
}

运行代码,控制台输入:

main thread finish...
asyncTest...

证实 asyncTest 办法异步执行了。

原理:

咱们在主启动类上贴了一个 @EnableAsync 注解,能力应用 @Async 失效。@EnableAsync 的作用是通过 @import 导入了 AsyncConfigurationSelector。在 AsyncConfigurationSelector 的 selectImports 办法将 ProxyAsyncConfiguration 定义为 Bean 注入容器。在 ProxyAsyncConfiguration 中通过 @Bean 的形式注入 AsyncAnnotationBeanPostProcessor 类。

代码如下:

@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
}

public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {public String[] selectImports(AdviceMode adviceMode) {switch (adviceMode) {
            case PROXY:
                return new String[] { ProxyAsyncConfiguration.class.getName() };
            //...
        }
    }
}

public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {@Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
    public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
        // 创立 postProcessor
        AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
        //...
    }
}

AsyncAnnotationBeanPostProcessor 往往期创立了一个增强器 AsyncAnnotationAdvisor。在 AsyncAnnotationAdvisor 的 buildAdvice 办法中,创立了 AnnotationAsyncExecutionInterceptor。

public class AsyncAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor {
    @Override
    public void setBeanFactory(BeanFactory beanFactory) {super.setBeanFactory(beanFactory);
        // 创立一个增强器
        AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(this.executor, this.exceptionHandler);
        //...
        advisor.setBeanFactory(beanFactory);
        this.advisor = advisor;
    }
}


public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
    public AsyncAnnotationAdvisor(@Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {
        // 加强办法
        this.advice = buildAdvice(executor, exceptionHandler);
        this.pointcut = buildPointcut(asyncAnnotationTypes);
    }

    // 委托给 AnnotationAsyncExecutionInterceptor 拦截器
    protected Advice buildAdvice(@Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {
        // 拦截器
        AnnotationAsyncExecutionInterceptor interceptor = new AnnotationAsyncExecutionInterceptor(null);
        interceptor.configure(executor, exceptionHandler);
        return interceptor;
    }
}

AnnotationAsyncExecutionInterceptor 继承自 AsyncExecutionInterceptor,间接实现了 MethodInterceptor。该拦截器的实现的 invoke 办法把原来办法的调用提交到新的线程池执行,从而实现了办法的异步。

public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport implements MethodInterceptor, Ordered {public Object invoke(final MethodInvocation invocation) throws Throwable {
        //...
        // 构建放到 AsyncTaskExecutor 执行 Callable Task
        Callable<Object> task = () -> {//...};
        // 提交到新的线程池执行
        return doSubmit(task, executor, invocation.getMethod().getReturnType());
    }
}

由下面剖析能够看到,@Async 注解其实是通过代理的形式来实现异步调用的。

那应用 @Async 有什么要留神的呢?

1. 应用 @Aysnc 的时候最好配置一个线程池 Executor 以让线程复用节俭资源,或者为 SimpleAsyncTaskExecutor 设置基于线程池实现的 ThreadFactory,在否则会默认应用 SimpleAsyncTaskExecutor,该 executor 会在每次调用时新建一个线程。

2. 调用本类的异步办法是不会起作用的。这种形式绕过了代理而间接调用了办法,@Async 注解会生效。


最初给大家分享一个 Github 仓库,下面有大彬整顿的 300 多本经典的计算机书籍 PDF,包含 C 语言、C++、Java、Python、前端、数据库、操作系统、计算机网络、数据结构和算法、机器学习、编程人生 等,能够 star 一下,下次找书间接在下面搜寻,仓库继续更新中~

Github 地址:https://github.com/Tyson0314/…

正文完
 0