java后端知识点梳理——Spring

开篇:感谢我是祖国的花朵java3y三太子敖丙等优秀博主!他们的文章为我学习java提供了莫大的帮助,膜拜大神!

Spring的优点有哪些呢?

  • Spring的依赖注入将对象之间的依赖关系交给了框架来处理,减小了各个组件之间的耦合性;
  • AOP面向切面编程,可以将通用的任务抽取出来,复用性更高;
  • Spring对于其余主流框架都提供了很好的支持,代码的侵入性很低。

Spring的核心模块

  • Spring Core:是核心类库,提供IOC服务;
  • Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等);
  • Spring AOP:提供AOP服务;
  • Spring DAO:对JDBC进行了抽象,简化了数据访问异常等处理;
  • Spring ORM:对现有的ORM持久层框架进行了支持;
  • Spring Web:提供了基本的面向Web的综合特性;
  • Spring MVC:提供面向Web应用的Model-View-Controller实现。

IOC

控制反转

IOC也叫控制反转,将对象间的依赖关系交给Spring容器,使用配置文件来创建所依赖的对象,由主动创建对象改为了被动方式,实现解耦合。

可以通过注解@Autowired和@Resource来注入对象,被注入的对象必须被下边的四个注解之一标注:

  • @Controller
  • @Service
  • @Repository
  • @Component

在Spring配置文件中配置 context:annotation-config/元素开启注解。

依赖注入(DI)

和控制反转是同一个概念的不同角度的描述,即应用程序在运行时依赖IOC容器来动态注入对象需要的外部资源(对象等)。

更多总结与补充

Spring 如何设计容器的呢

  • BeanFactory
    • BeanFactory 简单粗暴,可以理解为 HashMap:它一般只有 get, put 两个功能
      • Key - bean name
      • Value - bean object
  • ApplicationContext
    • 它是 BeanFactory 的子类,更好的补充并实现了 BeanFactory.ApplicationContext 多了很多功能,因为它继承了多个接口。 ApplicationContext 的里面有两个具体的实现子类,用来读取配置配件的
      • ClassPathXmlApplicationContext - 从 class path 中加载配置文件,更常用一些;
      • FileSystemXmlApplicationContext - 从本地文件中加载配置文件,不是很常用,如果再到 Linux 环境中,还要改路径,不是很方便。

注:几乎所有的场合都是使用ApplicationContext

BeanFactory和ApplicationContext的优缺点分析:

  • BeanFactory的优缺点:

优点:应用启动的时候占用资源很少,对资源要求较高的应用,比较有优势;

缺点:运行速度会相对来说慢一些。而且有可能会出现空指针异常的错误,而且通过Bean工厂创建的Bean生命周期会简单一些。

  • ApplicationContext的优缺点:

优点:所有的Bean在启动的时候都进行了加载,系统运行的速度快;在系统启动的时候,可以发现系统中的配置问题。

缺点:把费时的操作放到系统启动中完成,所有的对象都可以预加载,缺点就是内存占用较大。

IOC容器的原理

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

几个关键问题

1.何为控制,控制的是什么?

是 bean 的创建、管理的权利,控制 bean 的整个生命周期。

2.何为反转,反转了什么?

把这个权利交给了 Spring 容器,而不是自己去控制,就是反转。由之前的自己主动创建对象,变成现在被动接收别人给我们的对象的过程,这就是反转。

3.何为依赖,依赖什么?(DI)

程序运行需要依赖外部的资源,提供程序内对象的所需要的数据、资源。

4.何为注入,注入什么?

配置文件把资源从外部注入到内部,容器加载了外部的文件、对象、数据,然后把这些资源注入给程序内的对象,维护了程序内外对象之间的依赖关系。

  • IoC 是设计思想,DI 是具体的实现方式;
  • IoC 是理论,DI 是实践;

从而实现对象之间的解藕

装配Bean方式

  • XML配置
  • 注解
  • JavaConfig

总的来说:我们以XML配置+注解来装配Bean得多,其中注解这种方式占大部分!

依赖注入方式

  • 属性注入-->通过setter()方法注入
  • 构造函数注入
  • 工厂方法注入

总的来说使用属性注入是比较灵活和方便的,这是大多数人的选择!

对象之间关系

对象之间有三种关系:

  • 依赖-->挺少用的(使用depends-on就是依赖关系了-->前置依赖【依赖的Bean需要初始化之后,当前Bean才会初始化】)
  • 继承-->可能会用到(指定abstract和parent来实现继承关系)
  • 引用-->最常见(使用ref就是引用关系了)

Bean的作用域

@Scope: 声明 Spring Bean 的作用域

四种常见的 Spring Bean 的作用域:

  • singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。
  • prototype : 每次请求都会创建一个新的 bean 实例。
  • request : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。
  • session : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。

处理自动装配的歧义性

一个接口两个实现类怎么在注入的时候优先调用某个实现类?

  • 使用@Primary注解设置为首选的注入Bean
  • 使用@Qualifier注解设置特定名称的Bean来限定注入!

装配Bean总结

分别的应用场景:

Spring 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 异常。

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

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

解释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()方法。

解释不同方式的自动装配

  • no:默认的方式是不进行自动装配,通过显式设置ref 属性来进行装配。
  • byName:通过参数名 自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byname,之后容器试图匹配、装配和该bean的属性具有相同名字的bean。
  • byType::通过参数类型自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byType,之后容器试图匹配、装配和该bean的属性具有相同类型的bean。如果有多个bean符合条件,则抛出错误。
  • constructor:这个方式类似于byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。
  • autodetect:首先尝试使用constructor来自动装配,如果无法工作,则使用byType方式。

只用注解的方式时,注解默认是使用byType的!

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

Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。但实际上,大部分的Spring bean并没有可变的状态(比如Serview类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。如果你的bean有多种状态的话(比如 View Model 对象),就需要自行保证线程安全。

最浅显的解决办法就是将多态bean的作用域由“singleton”变更为“prototype”

IOOC容器的初始化过程:

IOC容器的初始化主要包括Resource定位,载入和注册三个步骤,接下来我们依次介绍。

  • Resource资源定位:

Resouce定位是指BeanDefinition的资源定位,也就是IOC容器找数据的过程。Spring中使用外部资源来描述一个Bean对象,IOC容器第一步就是需要定位Resource外部资源。由ResourceLoader通过统一的Resource接口来完成定位。

  • BeanDefinition的载入:

载入过程就是把定义好的Bean表示成IOC容器内部的数据结构,即BeanDefinition。在配置文件中每一个Bean都对应着一个BeanDefinition对象。

通过BeanDefinitionReader读取,解析Resource定位的资源,将用户定义好的Bean表示成IOC容器的内部数据结构BeanDefinition。

在IOC容器内部维护着一个BeanDefinitionMap的数据结构,通过BeanDefinitionMap,IOC容器可以对Bean进行更好的管理。

  • BeanDefinition的注册:

注册就是将前面的BeanDefition保存到Map中的过程,通过BeanDefinitionRegistry接口来实现注册。

续上:Spring中延迟加载Bean

IOC容器的初始化过程就是对Bean定义资源的定位、载入和注册,此时容器对Bean的依赖注入并没有发生。接下来,我们看下依赖注入的发生时刻吧。

ApplicationContext默认会在容器启动的时候创建我们配置好的各个Bean,我们的Bean配置如下:

<bean id="oneBean" class="com.nowcoder.oneBean">

这里边的隐藏属性是lazy-init,即上边的配置和下边的是一样的:

<bean id="oneBean" class="com.nowcoder.oneBean"  lazy-init="false">

lazy-init=false表示不开启延迟加载,在容器启动的时候即创建该Bean。对应的,我们还可以配置lazy-init=true表示开启延迟加载,那么该Bean的创建发生在应用程序第一次向容器索取Bean时,通过getBean()方法的调用完成。

BeanFactory和FactoryBean的区别:

  • BeanFactory:Bean工厂,是一个工厂(Factory), 是Spring IOC容器的最顶层接口,它的作用是管理Bean,即实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。
  • FactoryBean:工厂Bean,是一个Bean,作用是产生其他Bean实例,需要提供一个工厂方法,该方法用来返回其他Bean实例。

AOP

AOP,面向切面编程是指当需要在某一个方法之前或者之后做一些额外的操作,比如说日志记录,权限判断,异常统计等,可以利用AOP将功能代码从业务逻辑代码中分离出来。

原理:动态代理

AOP中有如下的操作术语:

  • Joinpoint(连接点): 类里面可以被增强的方法,这些方法称为连接点
  • Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义
  • Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。
  • Aspect(切面):是切入点和通知(引介)的结合
  • Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或属性
  • Target(目标对象):代理的目标对象(要增强的类)
  • Weaving(织入):是把增强应用到目标的过程,把advice 应用到 target的过程
  • Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类

Spring AOP好文

Spring中的AOP主要有两种实现方式:

  • 使用JDK动态代(dai)理实现,使用java.lang.reflection.Proxy类来处理
  • 使用cglib来实现

JDK动态代(dai)理Demo:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DynamicProxy {

    public static void main(String[] args){
        //定义一个people作为被代理的实例
        IPeople ple=new People();
        //定义一个handler
        InvocationHandler handle=new MyHandle(ple);

        //获得类加载器
        ClassLoader cl=ple.getClass().getClassLoader();

        //动态产生一个代理,下边两种方法均可
//        IPeople p=(IPeople) Proxy.newProxyInstance(cl, new Class[]{IPeople.class}, handle);
        IPeople p=(IPeople) Proxy.newProxyInstance(cl, ple.getClass().getInterfaces(), handle);

        //执行被代理者的方法。
        p.func();
    }



}

class MyHandle implements InvocationHandler{


    //被代理的实例
    Object obj=null;

    //我要代理谁
    public MyHandle(Object obj){
        this.obj=obj;

    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object result=method.invoke(this.obj, args);
        return result;
    }

}

interface IPeople{

    public void fun();

    public void func();
}

//实际被代理的类
class People implements IPeople{

    @Override
    public void fun() {
        System.out.println("这是fun方法");

    }

    @Override
    public void func() {
        System.out.println("这是func方法");

    }

}

cglib实现动态代理Demo:

import net.sf.cglib.proxy.*;

import java.lang.reflect.Method;

public class TestCglib {
    public static void main(String[] args) {

        // 定义一个回调接口的数组
        Callback[] callbacks = new Callback[] {
                new MyApiInterceptor(), new MyApiInterceptorForPlay()
        };

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Person.class); // 设置要代理的父类
        enhancer.setCallbacks(callbacks); // 设置回调的拦截器数组
        enhancer.setCallbackFilter(new CallbackFilterImpl()); // 设置回调选择器

        Person person = (Person) enhancer.create(); // 创建代理对象

        person.eat();
        System.out.println("--------------------");
        person.play();
    }
}

class MyApiInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("吃饭前我会先洗手"); // 此处可以做一些操作
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("吃完饭我会先休息会儿" );  // 方法调用之后也可以进行一些操作
        return result;
    }
}
class MyApiInterceptorForPlay implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("出去玩我会先带好玩具"); // 此处可以做一些操作
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("玩一个小时我就回家了" );  // 方法调用之后也可以进行一些操作
        return result;
    }
}

class CallbackFilterImpl implements CallbackFilter {
    @Override
    public int accept(Method method) {
        if (method.getName().equals("play"))
            return 1;
        else
            return 0;
    }
}


// 创建一个普通类做为代理类
class Person {
    //  代理类中由普通方法
    public void eat() {
        System.out.println("我要开始吃饭咯...");
    }

    public void play() {
        System.out.println("我要出去玩耍了,,,");
    }
}

Spring AOP对这两种代理方式的选择:

  • 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP,也可以强制使用cglib实现AOP;
  • 如果目标对象没有实现接口,必须采用cglib库,Spring会自动在JDK动态代理和cglib之间转换。

两种实现方式的不同之处:

JDK动态代理,只能对实现了接口的类生成代理,而不是针对类,该目标类型实现的接口都将被代理。原理是通过在运行期间创建一个接口的实现类来完成对目标对象的代理。实现步骤大概如下:

  • 定义一个实现接口InvocationHandler的类
  • 通过构造函数,注入被代理类
  • 实现invoke( Object proxy, Method method, Object[ ] args)方法
  • 在主函数中获得被代(dai)理类的类加载器
  • 使用Proxy.newProxyInstance( )产生一个代理对象
  • 通过代理对象调用各种方法

cglib主要针对类实现代理,对是否实现接口无要求。原理是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以被代理的类或方法不可以声明为final类型。实现步骤大概如下:

  • 定义一个实现了MethodInterceptor接口的类
  • 实现其 intercept()方法,在其中调用proxy.invokeSuper( )

Spring中有哪些不同的通知类型

通知(advice)是你在你的程序中想要应用在其他模块中的横切关注点的实现。Advice主要有以下5种类型:

  • 前置通知(Before Advice): 在连接点之前执行的Advice,不过除非它抛出异常,否则没有能力中断执行流。使用 @Before 注解使用这个Advice。
  • 返回之后通知(After Retuning Advice): 在连接点正常结束之后执行的Advice。例如,如果一个方法没有抛出异常正常返回。通过 @AfterReturning 关注使用它。
  • 抛出(异常)后执行通知(After Throwing Advice): 如果一个方法通过抛出异常来退出的话,这个Advice就会被执行。通用 @AfterThrowing 注解来使用。
  • 后置通知(After Advice): 无论连接点是通过什么方式退出的(正常返回或者抛出异常)都会执行在结束后执行这些Advice。通过 @After 注解使用。
  • 围绕通知(Around Advice): 围绕连接点执行的Advice,就你一个方法调用。这是最强大的Advice。通过 @Around 注解使用。

循环依赖

引子

“如果A对象创建的过程需要使用到B对象,但是B对象创建的时候也需要A对象,也就是构成了循环依赖的现象,那么Spring会如何解决?”

这是一种构造器循环依赖,通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出BeanCurrentlyInCreationException异常表示循环依赖。

解答循环依赖,主要分下面几点

  • 什么是循环依赖?
  • 什么情况下循环依赖可以被处理?
  • Spring是如何解决的循环依赖?

注意:

只有在setter方式注入的情况下,循环依赖才能解决(错)
三级缓存的目的是为了提高效率(错)

什么是循环依赖?

从字面上来理解就是A依赖B的同时B也依赖了A

例如:

以代码为例

@Component
public class A {
    // A中注入了B
 @Autowired
 private B b;
}

@Component
public class B {
    // B中也注入了A
 @Autowired
 private A a;
}

特殊的:

// 自己依赖自己
@Component
public class A {
    // A中注入了A
 @Autowired
 private A a;
}

【未完待续】

Spring的事务

Spring支持编程式事务管理和声明式事务管理两种方式:

  • 编程式事务管理:使用TransactionTemplate实现。
  • 声明式事务管理:建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

声明式事务的优点:

就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中。

【简单一点的概括】

  • 编程式事务管理
    • 编程帮助下实现对事务的管理
  • 声明式事务管理
    • 事务管理和业务代码分离,靠注解或者xml配置来管理事务

声明式事务的传播属性

隔离级别

  • 读未提交:READ UNCOMMITTED
  • 读已提交:READ COMMITTED
  • 可重复读:REPEATABLE READ
  • 串行化:SERIALIZABLE

SpringMVC工作流程

1、用户发送请求至前端控制器DispatcherServlet
2、DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3、处理器映射器找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
4、DispatcherServlet调用HandlerAdapter处理器适配器
5、HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
6、Controller执行完成返回ModelAndView
7、HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
8、DispatcherServlet将ModelAndView传给ViewReslover视图解析器
9、ViewReslover解析后返回具体View
10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
11、DispatcherServlet响应用户

SpringBoot的核心注解

@SpringBootApplication

创建 SpringBoot 项目之后会默认在主类加上

我们可以把 @SpringBootApplication看作是 @Configuration、@EnableAutoConfiguration、@ComponentScan 注解的集合。

  • @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制
  • @ComponentScan: 扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描该类所在的包下所有的类。
  • @Configuration:允许在 Spring 上下文中注册额外的 bean 或导入其他配置类

SpringBoot项目启动分析

Application类是通过SpringApplication类的静态run方法来启动应用的。打开这个静态方法,该静态方法真正执行的是两部分:new SpringApplication( )并且执行对象run()方法。

SpringBoot的自动配置原理

首先是上述的三个核心注解

其中@EnableAutoConfiguration是关键(启用自动配置),内部实际上就去加载META-INF/spring.factories文件的信息,然后筛选出以EnableAutoConfiguration为key的数据,加载到IOC容器中,实现自动配置功能!

posted @ 2020-11-16 14:55  猫坚果NutCat  阅读(407)  评论(0编辑  收藏  举报