spring重点知识

Table of Contents

1.spring给java开发带来了哪些帮助?why spring?

2.一个spring mvc项目的启动过程是怎样的,spring boot和springmvc的区别?

3. Spring Boot是什么?

Spring Boot  Starter

约定1:项目结构层面的约定

约定2:springMVC框架层面的约定和定制

约定3:嵌入式web容器层面的约定和定制

Automatically Configuration

Acutator

spring boot启动过程

4. spring IoC

问题1:spring是如何通过反射实现依赖注入的?

问题2:不使用xml,而使用注解@Autowire注入一个bean?spring是如何实现的?

5. spring aop

6. 什么是bean的循环依赖?spring 如何解决的循环依赖?

Spring中循环依赖场景有: 

怎么检查是否存在循环依赖?


1.spring给java开发带来了哪些帮助?why spring?

spring带来的帮助非常之多,现在的spring框架可以说是相当的庞大,在spring的官方文档里面,有一个目录,我们可以从这个目录作为出发点。

其中最为核心的就是Core technologies.包括:依赖注入即ioc,事件,资源,i18n国际化,验证,数据绑定,类型转换,SpEL是spring提供的语法,以及AOP。

总结起来,可以从以下几个方面来看。

- 非侵入式:支持基于POJO的编程模式,不强制性的要求实现Spring框架中的接口或继承Spring框架中的类。
- IoC容器:IoC容器帮助应用程序管理对象以及对象之间的依赖关系,对象之间的依赖关系如果发生了改变只需要修改配置文件而不是修改代码,因为代码的修改可能意味着项目的重新构建和完整的回归测试。有了IoC容器,程序员再也不需要自己编写工厂、单例,这一点特别符合Spring的精神"不要重复的发明轮子"。
- AOP(面向切面编程):将所有的横切关注功能封装到切面(aspect)中,通过配置的方式将横切关注功能动态添加到目标代码上,进一步实现了业务逻辑和系统服务之间的分离。另一方面,有了AOP程序员可以省去很多自己写代理类的工作。
- MVC:Spring的MVC框架是非常优秀的,从各个方面都可以甩Struts 2几条街,为Web表示层提供了更好的解决方案。
- 事务管理:Spring以宽广的胸怀接纳多种持久层技术,并且为其提供了声明式的事务管理,在不需要任何一行代码的情况下就能够完成事务管理。
- 其他:选择Spring框架的原因还远不止于此,Spring为Java企业级开发提供了一站式选择,你可以在需要的时候使用它的部分和全部,更重要的是,你甚至可以在感觉不到Spring存在的情况下,在你的项目中使用Spring提供的各种优秀的功能。

2.一个spring mvc项目的启动过程是怎样的,spring boot和springmvc的区别?

Spring MVC是Spring提供的一个强大而灵活的模块式web框架。通过Dispatcher Servlet, ModelAndView 和 View Resolver,开发web应用变得很容易。

Spring MVC框架提供了开发Web应用的分离方式。通过DispatcherServlet、ModelAndView、View Resolver等简单概念,是Web应用开发变得更加简单。

但是Spring和Spring MVC的众多配置有时却让人望而却步,相信有过Spring MVC开发经验的朋友能深刻体会到这一痛苦。因为即使是开发一个Hello-World的Web应用,都需要我们在pom文件中导入各种依赖,编写web.xml、spring.xml、springmvc.xml配置文件等。特别是需要导入大量的jar包依赖时,我们需要在网上查找各种jar包资源,各个jar间可能存在着各种依赖关系,这时候又得下载其依赖的jar包,有时候jar包间还存在着严格的版本要求,,所以当我们只是想开发一个Hello-World的超简单的Web应用时,却把极大部分的时间在花在了编写配置文件和导入jar包依赖上,极大地影响了我们的开发效率。所以为了简化Spring繁杂的配置,Spring Boot应运而生。正如Spring Boot的名称一样,一键启动,Spring Boot提供了自动配置功能,为我们提供了开箱即用的功能,使我们将重心放在业务逻辑的开发上。

3. Spring Boot是什么?

Spring Boot是为简化Spring MVC/spring的开发而生的项目。主要从以下三个方面:

  1. simplify configuration 简化配置

  2. simplify deployment简化部署

  3. simplify monitor 简化系统监控

spring boot由四个主要的组件组成:

  1. Spring Boot  Starter:

  2. Automatically Configuration.

  3. CLI. Use with groovy.

  4. Actuator

Spring Boot  Starter

spring boot starter起到的作用是,把一些列的依赖结合起来,依赖之间可以像java类继承一样引用对方。这样的好处是解决了依赖之间冲突的问题(这个问题在maven使用过程中非常常见),减少了依赖的数量。

下面是一个spring boot starter web的例子:

在这个时代,使用spring开发的应用大多数都是在使用springMVC开发web应用,为了帮我们快速搭建并开发一个web项目,spring boot为我们提供了spring-boot-starter-web自动配置模块。

只要将spring-boot-starter-web加入项目的maven依赖,我们就得到了一个直接可执行的web应用,在当前项目下运行spring boot:run,就可以直接启动一个使用了嵌入式Tomcat服务的web应用,只不过我们还没有提供任何可以响应web请求的controller,所以当前访问任何的路径都会返回一个spring boot默认提供的错误页面(white label error page). 如果在当前项目下新建一个服务根路径web请求的controller:

@RestController
public class IndexController {
    @RequestMapping("/")
    public String index(){
        return "hello, there";
    }
}

重新运行mvn spring-boot:run并访问http://localhost:8080,错误页面将被controller返回的消息所替代。至此,一个简单的web应用就已经这样完成了。但是,毫无疑问背后的潜规则(约定)是我们必须去了解的。

约定1:项目结构层面的约定

与传统打包为war包的java web应用的差异在于,静态文件和页面模板的存放位置变了,原来是放在src/main/webapp目录下的一系列资源,现在都统一放在了src/main/resources下。

约定2:springMVC框架层面的约定和定制

spring-boot-starter-web默认为我们配置了一些springMVC必要组件

  1. ViewResolver
  2. Converter,Formatter等bean注册到IoC
  3. HttpMessageConverter
  4. MessageCodesResolver

当然,我们完全可以自己配置这些东西,而不用它提供的配置。

约定3:嵌入式web容器层面的约定和定制

  1. 默认使用嵌入式tomcat作为web容器对外提供http服务,默认使用8080端口对外监听和提供服务
  2. 假设不行使用签署tomcat,可引入spring-boot-starter-jetty或spring-boot-starter-undertow作為替代
  3. 假设不想使用8080作为默认端口,可以更改配置项server.port使用自己指定的端口号,如:server.port = 9000

Automatically Configuration

自动配置的主要职责是:减少spring的配置。

自动配置是什么,用一个例子比较好说明。当我们把spring-boot-starter-web加入到项目的maven依赖里以后,它就会自动的添加spring mvc的相关依赖,如果spring mvc相关的配置在classpath里面被scan到,它就会自动的将相关的bean加到IoC容器里面(messageConvert等)。spring启动的时候会扫描classpath并且去找META-INF/spring.factories这个文件,然后去加载configuration. 这个文件的格式是 key = value的格式,并且key和value都必须是java 类的全限定名(Fully qualified), 比如:

   exmaple.MyService = example.MyServiceImpl

如果我们使用了注解@EnableAutoConfiguration,它会去加载,实例化所有加了@Configuration的注解的类到IoC容器里去(ApplicationContext),通常,这个注解会用在主类里边,并且主类一般是在开发包的最顶部,因此它就可以扫描到所有的类。

@EnableAutoConfiguration这个注解的示意图如下:

@ComponentScan:

会自动扫描包路径下面的所有@Controller、@Service、@Repository、@Component 的类。

 basePackages指定扫描的包,includeFilters包含那些过滤,excludeFilters不包含那些过滤,useDefaultFilters默认的过滤规则是开启的,如果我们要自定义的话是要关闭的

@Configuration

把一个类标记为IoC容器的一个bean

Acutator

spring提供了actutator用于帮助我们监控服务的各种状态:

路径 作用

info

know the service situation
error  
autoconfig list config decisions
beans List  all the config beans
dump List application’s thread information
/env/{name} list all the environment variables
health  
info  
/metrics/{name} list the realated index
shutdown shut down the service
trace list the recent quest metadata,include request and response header
configprops list all the  @ConfigurationProperties

另外,关于@SpringBootApplication这个注解的作用,记住下面这张图即可:

spring boot启动过程

spring boot项目的启动过程大致如下图:

4. spring IoC

IoC即Inversion of control,直译过来就是控制反转,它的意思是将对象,以及对象之间的引用关系,交给容器管理。从spring的官方文档来看,我们在说ioc的时候后面其实漏掉了一个单词:container。spring core的第一部分的题目就是:The IOC container。即IOC容器的意思。这一部分的内容其实官方文档讲的是最清楚的。因此我这里只列出最重要的核心内容。

IoC是spring的核心,贯穿始终。所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系.

Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。

DI(依赖注入)

IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的.  

问题1:spring是如何通过反射实现依赖注入的?

我们来看下面的代码

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context =
                new ClassPathXmlApplicationContext("BeansIOCConstructor.xml");

        TextEditor te = (TextEditor) context.getBean("textEditor");

        te.spellCheck();
    }
}

在这里xml文件里面到底是setter注入还是构造方法注入,我们根本就无需关系,这些类怎么定义的我们也不去关心,就只看这个运行类。这个运行类不就是想做一件事情:调用TextEditor 这个类的spellCheck方法吗?那自然我们就需要这个类的一个砬对象,既然spring容器帮我们管理了这个对象,于是我们就不通过关键字new,而是通过spring去获得这个多谢,怎么获得的呢?在xml文件里必然定义了这个类的相关信息,其中class的全限定名是必须定义的,为什么要定义全限定名,自然就是因为spring要帮我们加载这个类,只能是利用反射机制,根据这个类的全限定名称,才能够做到动态的去加载这个类(联系Class.forName方法理解),然后加载了之后,通过这里的getBean方法去获得这个类的对象,就可以去调用它的方法了。至于别的可以配的参数,我们现在根本无需关心。

以上就是spring通过反射实现依赖注入的思想。

问题2:不使用xml,而使用注解@Autowire注入一个bean?spring是如何实现的?

首先,要回答这个问题必须弄明白的是spring是如何解析注解的。

我们来看一下一个注解的定义是怎样的:

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {

	/**
	 * Declares whether the annotated dependency is required.
	 * <p>Defaults to {@code true}.
	 */
	boolean required() default true;

}

以Autowired为例,可以发现,事实上每一个注解本质上都是一个接口,当我们在某一个类当中用到这个注解的时候,就可以通过反射知道是哪个类用了这个注解。

总结起来说:扫描器采用asm技术扫描java字节码文件,即.class文件。扫描时是扫描指定包下的全部class文件,转换成指定的MetadataReader结构后,再去判断是否符合扫描规则,符合的才加入候选bean中,并注册到容器中。到这里我们知道了利用asm技术,使用注解的.class文件也扫描完了,注解处理器也注册完了,那么注解是什么时候处理的呢?总结起来也就是两个字:反射。

这里我先贴出一个国外程序员的回答,之后再做翻译:

The first main distinction between kinds of annotation is whether they're used at compile time and then discarded (like @Override) or placed in the compiled class file and available at runtime (like Spring's @Component). This is determined by the @Retention policy of the annotation. If you're writing your own annotation, you'd need to decide whether the annotation is helpful at runtime (for autoconfiguration, perhaps) or only at compile time (for checking or code generation).

When compiling code with annotations, the compiler sees the annotation just like it sees other modifiers on source elements, like access modifiers (public/private) or final. When it encounters an annotation, it runs an annotation processor, which is like a plug-in class that says it's interested a specific annotation. The annotation processor generally uses the Reflection API to inspect the elements being compiled and may simply run checks on them, modify them, or generate new code to be compiled. @Override is an example of the first; it uses the Reflection API to make sure it can find a match for the method signature in one of the superclasses and uses the Messagerto cause a compile error if it can't.

5. spring aop

spring aop是spring核心技术的一部分,可谓重中之重。spring aop是通过代理来实现的。有jdk动态代理和cglib动态代理。

6. 什么是bean的循环依赖?spring 如何解决的循环依赖?

循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,示意图如下:

值得注意的是,我们即便不使用spring,自己管理对象之间的关系,仍然会遇到这个问题。因为我们使用了spring来管理对象,spring必然需要去考虑这个问题。

另外,这里的循环依赖,要和循环调用区分开来,循环调用是一个死循环,java里面一个很著名的循环调用死循环就是HashMap在并发下可能出现的环装链表。

代码如下:

public class TestA {
    // TestA依赖TestB
    private TestB testB;

    public TestA(TestB testB) {
        this.testB = testB;
    }

    public TestB getTestB() {
        return testB;
    }

    public void setTestB() {
        this.testB = testB;
    }
}

public class TestB {
    // TestB依赖TestC
    private TestC testC;

    public TestB(TestC testC) {
        this.testC = testC;
    }

    public TestC getTestC() {
        return testC;
    }

    public void setTestC() {
        this.testC = testC;
    }
}

public class TestC {
    // TestC依赖TestA
    private TestA testA;

    public TestC(TestA testA) {
        this.testA = testA;
    }

    public TestA getTestA() {
        return testA;
    }

    public void setTestA() {
        this.testA = testA;
    }
}

Spring中循环依赖场景有: 

  1. 构造器的循环依赖 。
    <bean id="testA" class="com.bean.TestA">
        <constructor-arg index="0" ref="testB" />
    </bean>
    <bean id="testB" class="com.bean.TestB">
        <constructor-arg index="0" ref="testC" />
    </bean>
    <bean id="testC" class="com.bean.TestC">
        <constructor-arg index="0" ref="testA" />
    </bean>

    解决方法:spring无法解决这种问题,为何?只能抛出BeanCurrentlyInCreationException异常来终止该依赖。 
    具体的处理步骤: 
    1.Spring将每个正在创建的bean的标识符放在一个池子里 
    2.如果bean在创建的时候发现自己已经存在于这个池子里 
    3.则抛出异常,中断循环依赖。 例如:

    Spring容器先创建单例A,A依赖B,然后将A放在“当前创建Bean池”中,此时创建B,B依赖C ,然后将B放在“当前创建Bean池”中,此时创建C,C又依赖A, 但是,此时A已经在池中,所以会报错,,因为在池中的Bean都是未初始化完的,所以会依赖错误 ,(初始化完的Bean会从池中移除)

  2. field属性的循环依赖, setter循环依赖
    <bean id="testA" class="com.bean.TestA">
        <property name="testB" ref="testB" />
    </bean>
    <bean id="testB" class="com.bean.TestB">
        <property name="testC" ref="testC" />
    </bean>
    <bean id="testC" class="com.bean.TestC">
        <property name="testA" ref="testA" />
    </bean>

    解决方案: 
    1.spring将已经完成了构造注入但是未完成setter注入的bean暴露出来 
    2.当进行setter注入时,发现这个需被注入的bean已经被暴露出来(存在该bean的引用),直接注入即可,而不需要再次加载所需的bean(无需再经历从头开始加载一个bean的过程)

Spring先是用构造实例化Bean对象 ,创建成功后,Spring会通过以下代码提前将对象暴露出来,此时的对象A还没有完成属性注入,属于早期对象,此时Spring会将这个实例化结束的对象放到一个Map(缓存)中,并且Spring提供了获取这个未设置属性的实例化对象引用的方法。 结合我们的实例来看,当Spring实例化了A、B、C后,紧接着会去设置对象的属性,此时A依赖B,就会去Map中取出存在里面的单例B对象,以此类推,不会出来循环的问题。

另外,spring解决的setter循环依赖仅仅针对于单例的bean,对于非单例的bean,Spring无法完成依赖注入,因为spring容器不缓存“prototype”作用域的bean,因此无法提前暴露一个创建中的bean,但是对于单例(singleton)的bean,可以通过“setAllowCircularReferences( false );”来禁用循环引用。

怎么检查是否存在循环依赖?

检测循环依赖相对比较容易,Bean在创建的时候可以给该Bean一个标记(如 boolean isCreating = false),如果递归调用回来发现正在创建中(isCreating = true)的话,即说明了循环依赖了.

参考:https://blog.csdn.net/chejinqiang/article/details/80003868#3849-1524062318691

猜你喜欢

转载自blog.csdn.net/topdeveloperr/article/details/81414039
今日推荐