从零学习Spring

1,248 阅读26分钟

1、Spring简介

Spring是一个开源框架,它由Rod Johnson创建。它是为了解决企业应用开发的复杂性而创建的。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。 Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。

2、Spring体系结构

Spring 有可能成为所有企业应用程序的一站式服务点,然而,Spring 是模块化的,允许你挑选和选择适用于你的模块,不必要把剩余部分也引入。下面的部分对在 Spring 框架中所有可用的模块给出了详细的介绍。

Spring 框架提供约 20 个模块,可以根据应用程序的要求来使用。

其中有七大主要的模块

2.1、核心容器

核心容器由spring-core,spring-beans,spring-context,spring-context-support和spring-expression(SpEL,Spring表达式语言,Spring Expression Language)等模块组成,它们的细节如下:

  • spring-core模块提供了框架的基本组成部分,包括 IoC 和依赖注入功能。

  • spring-beans 模块提供 BeanFactory,工厂模式的微妙实现,它移除了编码式单例的需要,并且可以把配置和依赖从实际编码逻辑中解耦。

  • context模块建立在由core和 beans 模块的基础上建立起来的,它以一种类似于JNDI注册的方式访问对象。Context模块继承自Bean模块,并且添加了国际化(比如,使用资源束)、事件传播、资源加载和透明地创建上下文(比如,通过Servelet容器)等功能。Context模块也支持Java EE的功能,比如EJB、JMX和远程调用等。ApplicationContext接口是Context模块的焦点。spring-context-support提供了对第三方库集成到Spring上下文的支持,比如缓存(EhCache, Guava, JCache)、邮件(JavaMail)、调度(CommonJ, Quartz)、模板引擎(FreeMarker, JasperReports, Velocity)等。

  • spring-expression模块提供了强大的表达式语言,用于在运行时查询和操作对象图。它是JSP2.1规范中定义的统一表达式语言的扩展,支持set和get属性值、属性赋值、方法调用、访问数组集合及索引的内容、逻辑算术运算、命名变量、通过名字从Spring IoC容器检索对象,还支持列表的投影、选择以及聚合等。

它们的完整依赖关系如下图所示:

2.2、数据访问/集成

数据访问/集成层包括 JDBC,DAO,ORM,OXM,JMS 和事务处理模块,它们的细节如下:

(注:JDBC=Java Data Base Connectivity,ORM=Object Relational Mapping,OXM=Object XML Mapping,JMS=Java Message Service)

  • JDBC 模块提供了JDBC抽象层,它消除了冗长的JDBC编码和对数据库供应商特定错误代码的解析。

  • DAO 模块提供了对JDBC、Hibernate、Mybatis等DAO层支持。

  • ORM 模块提供了对流行的对象关系映射API的集成,包括JPA、JDO和Hibernate等。通过此模块可以让这些ORM框架和spring的其它功能整合,比如前面提及的事务管理。

  • OXM 模块提供了对OXM实现的支持,比如JAXB、Castor、XML Beans、JiBX、XStream等。

  • JMS 模块包含生产(produce)和消费(consume)消息的功能。从Spring 4.1开始,集成了spring-messaging模块。。

  • 事务模块为实现特殊接口类及所有的 POJO 支持编程式和声明式事务管理。(注:编程式事务需要自己写beginTransaction()、commit()、rollback()等事务管理方法,声明式事务是通过注解或配置由spring自动处理,编程式事务粒度更细)

2.3、Web

Web 层由 Web,Web-MVC,Web-Socket 和 Web-Portlet 组成,它们的细节如下:

  • Web 模块提供面向web的基本功能和面向web的应用上下文,比如多部分(multipart)文件上传功能、使用Servlet监听器初始化IoC容器等。它还包括HTTP客户端以及Spring远程调用中与web相关的部分。。

  • Web-MVC 模块为web应用提供了模型视图控制(MVC)和REST Web服务的实现。Spring的MVC框架可以使领域模型代码和web表单完全地分离,且可以与Spring框架的其它所有功能进行集成。

  • Web-Socket 模块为 WebSocket-based 提供了支持,而且在 web 应用程序中提供了客户端和服务器端之间通信的两种方式。

  • Web-Portlet 模块提供了用于Portlet环境的MVC实现,并反映了spring-webmvc模块的功能。

2.4、其他

还有其他一些重要的模块,像 AOP,Aspects,Instrumentation,Web 和测试模块,它们的细节如下:

  • AOP 模块提供了面向方面的编程实现,允许你定义方法拦截器和切入点对代码进行干净地解耦,从而使实现功能的代码彻底的解耦出来。使用源码级的元数据,可以用类似于.Net属性的方式合并行为信息到代码中。

  • Aspects 模块提供了与 AspectJ 的集成,这是一个功能强大且成熟的面向切面编程(AOP)框架。

  • Instrumentation 模块在一定的应用服务器中提供了类 instrumentation 的支持和类加载器的实现。

  • Messaging 模块为 STOMP 提供了支持作为在应用程序中 WebSocket 子协议的使用。它也支持一个注解编程模型,它是为了选路和处理来自 WebSocket 客户端的 STOMP 信息。

  • 测试模块支持对具有 JUnit 或 TestNG 框架的 Spring 组件的测试。

3、Spring体系详解

3.1、Spring IoC 容器

Spring 容器是 Spring 框架的核心。容器将创建对象,把它们连接在一起,配置它们,并管理他们的整个生命周期从创建到销毁。Spring 容器使用依赖注入(DI)来管理组成一个应用程序的组件。 IOC 容器具有依赖注入功能的容器,它可以创建对象,IOC 容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。通常new一个实例,控制权由程序员控制,而"控制反转"是指new实例工作不由程序员来做而是交给Spring容器来做。在Spring中BeanFactory是IOC容器的实际代表者。

Spring 提供了以下两种不同类型的容器。

序号 容器 & 描述
1 Spring BeanFactory 容器,它是最简单的容器,给 DI 提供了基本的支持,它用 org.springframework.beans.factory.BeanFactory 接口来定义。BeanFactory 或者相关的接口,如 BeanFactoryAware,InitializingBean,DisposableBean,在 Spring 中仍然存在具有大量的与 Spring 整合的第三方框架的反向兼容性的目的。
2 Spring ApplicationContext 容器,该容器添加了更多的企业特定的功能,例如从一个属性文件中解析文本信息的能力,发布应用程序事件给感兴趣的事件监听器的能力。该容器是由 org.springframework.context.ApplicationContext 接口定义。

ApplicationContext 容器包括 BeanFactory 容器的所有功能,所以通常建议超过 BeanFactory。BeanFactory 仍然可以用于轻量级的应用程序,如移动设备或基于 applet 的应用程序,其中它的数据量和速度是显著。

最常被使用的 ApplicationContext 接口实现:

  • FileSystemXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你需要提供给构造器 XML 文件的完整路径。

  • ClassPathXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,因为,容器会从 CLASSPATH 中搜索 bean 配置文件。

  • WebXmlApplicationContext:该容器会在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean。

3.1.1、Spring Bean定义的三种方式

Spring容器启动配置(web.xml文件)

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring/applicationContext.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

一、基于XML的配置

适用场景:

  • Bean实现类来自第三方类库,如:DataSource等
  • 需要命名空间配置,如:context,aop,mvc等
<beans>
    <import resource=“resource1.xml” />//导入其他配置文件Bean的定义
    <import resource=“resource2.xml” />
    
    <bean id="userService" class="cn.lovepi.***.UserService" init-method="init" destory-method="destory"> 
    </bean>
    <bean id="message" class="java.lang.String">
        <constructor-arg index="0" value="test"></constructor-arg>
    </bean>
</beans>

二、基于注解的配置

适用场景:

  • 项目中自己开发使用的类,如controller、service、dao等

1、在applicationContext.xml配置扫描包路径

<context:component-scan base-package="com.lovepi.spring"> 
    <context:include-filter type="regex" expression="com.lovepi.spring.*"/> //包含的目标类
    <context:exclude-filter type="aspectj" expression="cn.lovepi..*Controller+"/>   //排除的目标类
</context:component-scan>
  1. 使用注解声明bean

Spring提供了四个注解,这些注解的作用与上面的XML定义bean效果一致,在于将组件交给Spring容器管理。组件的名称默认是类名(首字母变小写),可以自己修改:

  • @Component:当对组件的层次难以定位的时候使用这个注解
  • @Controller:表示控制层的组件
  • @Service:表示业务逻辑层的组件
  • @Repository:表示数据访问层的组件

三、基于Java类的配置

适用场景:

  • 需要通过代码控制对象创建逻辑的场景
  • 实现零配置,消除xml配置文件

步骤如下:

  • 使用@Configuration注解需要作为配置的类,表示该类将定义Bean的元数据
  • 使用@Bean注解相应的方法,该方法名默认就是Bean的名称,该方法返回值就是Bean的对象。
  • AnnotationConfigApplicationContext或子类进行加载基于java类的配置

3.1.2、Spring Bean 作用域

当在 Spring 中定义一个 bean 时,你必须声明该 bean 的作用域的选项。例如,为了强制 Spring 在每次需要时都产生一个新的 bean 实例,你应该声明 bean 的作用域的属性为 prototype。同理,如果你想让 Spring 在每次需要时都返回同一个bean实例,你应该声明 bean 的作用域的属性为 singleton。

Spring 框架支持以下五个作用域,分别为singleton、prototype、request、session和global session,5种作用域说明如下所示

作用域 描述
singleton 在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,默认值
prototype 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()
request 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境
session 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean,仅适用于WebApplicationContext环境
global-session 一般用于Portlet应用环境,该运用域仅适用于WebApplicationContext环境

3.1.3、Spring Bean 生命周期

理解 Spring bean 的生命周期很容易。当一个 bean 被实例化时,它可能需要执行一些初始化使它转换成可用状态。同样,当 bean 不再需要,并且从容器中移除时,可能需要做一些清除工作。

为了定义安装和拆卸一个 bean,我们只要声明带有 init-method 和/或 destroy-method 参数的 。init-method 属性指定一个方法,在实例化 bean 时,立即调用该方法。同样,destroy-method 指定一个方法,只有从容器中移除 bean 之后,才能调用该方法。

Bean的生命周期可以表达为:Bean的定义——Bean的初始化——Bean的使用——Bean的销毁

3.1.4、Spring 依赖注入

使用了Spring框架之后,对象的实例不再由调用者来创建,而是由Spring容器来创建,Spring容器会负责控制程序之间的关系。这样控制权便由应用代码转移到Spring容器,控制权发生了反转,这就是Spring的控制反转

从Spring容器来看,Spring容器负责将被依赖对象赋值给调用者的成员变量,这就相当于为调用者注入了它依赖的实例,这就是Spring的依赖注入

一、setter 注入

这是最简单的注入方式,假设有一个TestController,类中需要实例化一个TestService对象,那么就可以定义一个private的TestService成员变量,然后创建TestService的set方法(这是ioc的注入入口):

public class TestController {
    private TestService testService;

    //注入testService
    public void setTestService(TestService testService) {
        this.testService = testService;
    }

    public void test() {
        testService.test();
    }

}

public class TestService {
    public void test(){
        System.out.println("测试方法");
    }
}

spring的xml文件:

<bean id="testController111" class="com.test.day.TestController">
    <property name="testService" ref="testService"></property>
</bean>

<bean id="testService" class="com.test.day.TestService">

</bean>

二、构造器注入

这种方式的注入是指带有参数的构造函数注入,看下面的例子,创建成员变量TestService,但是并未设置对象的set方法,所以就不能支持第一种注入方式,这里的注入方式是在TestController的构造函数中注入,也就是说在创建TestController对象时要将TestService传进来:

public class TestController {
    private TestService testService;

    public TestController(TestService testService){
        this.testService = testService;
    }

    public void test() {
        testService.test();
    }

}

public class TestService {
    public void test(){
        System.out.println("测试方法");
    }
}

spring的xml文件:

<bean id="testController" class="com.test.day.TestController">
    <constructor-arg  name="testService" index="0" ref="testService"></constructor-arg>
</bean>

<bean id="testService" class="com.test.day.TestService">

三、静态工厂注入

静态工厂顾名思义,就是通过调用静态工厂的方法来获取自己需要的对象,为了让spring管理所有对象,我们不能直接通过"工程类.静态方法()"来获取对象,而是依然通过spring注入的形式获取:

静态工厂类:

public class StaticFactory {
    //静态工厂
    public static final TestService getTestService(){
        return new TestService();
    }
}
public class TestController {
    private TestService testService;

    //注入testService
    public void setTestService(TestService testService) {
        this.testService = testService;
    }

    public void test() {
        testService.test();
    }

}
public class TestService {
    public void test(){
        System.out.println("测试方法");
    }
}

spring的xml文件:

<bean id="testController" class="com.test.day.TestController">
    <property name="testService" ref="testService"></property>
</bean>

<!--注意这里注入的是静态工厂类-->
<bean id="testService" class="com.test.day.StaticFactory" factory-method="getTestService">

</bean>

四、实例工厂注入

实例工厂的意思是获取对象实例的方法不是静态的,所以你需要首先new工厂类,再调用普通的实例方法:

实例工厂类:

public class ExamplesFactory {
    //实例工厂
    public TestService getTestService(){
        return new TestService();
    }
}

public class TestController {
    private TestService testService;

    //注入testService
    public void setTestService(TestService testService) {
        this.testService = testService;
    }

    public void test() {
        testService.test();
    }

}
public class TestService {
    public void test(){
        System.out.println("测试方法");
    }
}

spring的xml配置文件:

<bean id="testController" class="com.test.day.TestController">
    <property name="testService" ref="testService"></property>
</bean>

<bean id="testService" factory-bean="examplesFactory" factory-method="getTestService">

</bean>

<bean id="examplesFactory" class="com.test.day.ExamplesFactory">

</bean>

五、注解注入 目前使用最广泛的 @Autowired:自动装配,基于@Autowired的自动装配,默认是根据类型注入

public class TestController {
    @Autowired
    private TestService testService;

    public String test() {
        testService.test();
        return null;
    }

}

public class TestService {
    public void test(){
        System.out.println("测试方法");
    }
}

spring的xml配置文件:

    <context:component-scan base-package="com.test.day">
        <context:include-filter type="regex" expression="com.test.day.*"/>
    </context:component-scan>

提供一个测试方法:

public class Test {
    @org.junit.Test
    public void test(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring/spring-context.xml");
        TestController controller = ac.getBean(TestController.class);
        controller.test();

    }
}

3.1.5、Spring Beans 自动装配

学会如何使用元素来声明 bean 和通过使用 XML 配置文件中的和元素来注入 。

Spring 容器可以在不使用和 元素的情况下自动装配相互协作的 bean 之间的关系,这有助于减少编写一个大的基于 Spring 的应用程序的 XML 配置的数量。

自动装配模式

下列自动装配模式,它们可用于指示 Spring 容器为来使用自动装配进行依赖注入。你可以使用元素的 autowire 属性为一个 bean 定义指定自动装配模式。

模式 描述
no 这是默认的设置,它意味着没有自动装配,你应该使用显式的bean引用来连线。你不用为了连线做特殊的事。在依赖注入章节你已经看到这个了。
byName 由属性名自动装配。Spring 容器看到在 XML 配置文件中 bean 的自动装配的属性设置为 byName。然后尝试匹配,并且将它的属性与在配置文件中被定义为相同名称的 beans 的属性进行连接。
byType 由属性数据类型自动装配。Spring 容器看到在 XML 配置文件中 bean 的自动装配的属性设置为 byType。然后如果它的类型匹配配置文件中的一个确切的 bean 名称,它将尝试匹配和连接属性的类型。如果存在不止一个这样的 bean,则一个致命的异常将会被抛出。
constructor 类似于 byType,但该类型适用于构造函数参数类型。如果在容器中没有一个构造函数参数类型的 bean,则一个致命错误将会发生。
autodetect Spring首先尝试通过 constructor 使用自动装配来连接,如果它不执行,Spring 尝试通过 byType 来自动装配。

一、Spring 自动装配 byName

这种模式由属性名称指定自动装配。Spring 容器看作 beans,在 XML 配置文件中 beans 的 auto-wire 属性设置为 byName。然后,它尝试将它的属性与配置文件中定义为相同名称的 beans 进行匹配和连接。如果找到匹配项,它将注入这些 beans,否则,它将抛出异常。

直接修改之前的setter注入的代码,只需要修改一下配置文件即可

<bean id="testController111" class="com.test.day.TestController" autowire="byName">
    <!--        <property name="testService" ref="testService"></property>-->
</bean>

<bean id="testService" class="com.test.day.TestService">

</bean>

二、Spring 自动装配 byType

这种模式由属性类型指定自动装配。Spring 容器看作 beans,在 XML 配置文件中 beans 的 autowire 属性设置为 byType。然后,如果它的 type 恰好与配置文件中 beans 名称中的一个相匹配,它将尝试匹配和连接它的属性。如果找到匹配项,它将注入这些 beans,否则,它将抛出异常。

和上面的例子没有什么区别,除了 XML 配置文件改成“byType”。

<bean id="testController" class="com.test.day.TestController"  autowire="byType">
    <!--<property name="testService"  ref="testService"></property>-->
</bean>

<bean id="testService" class="com.test.day.TestService">

</bean>

三、Spring 构造函数自动装配

这种模式与 byType 非常相似,但它应用于构造器参数。Spring 容器看作 beans,在 XML 配置文件中 beans 的 autowire 属性设置为 constructor。然后,它尝试把它的构造函数的参数与配置文件中 beans 名称中的一个进行匹配和连线。如果找到匹配项,它会注入这些 bean,否则,它会抛出异常。

修改一下Controller,使用构造器的方式

public class TestController {
    private TestService testService;

    public TestController(TestService testService){
        this.testService = testService;
    }

    public void test() {
        testService.test();
    }

}

配置文件

<bean id="testController" class="com.test.day.TestController"  autowire="constructor">
    <!--<property name="testService"  ref="testService"></property>-->
</bean>

<bean id="testService" class="com.test.day.TestService">

</bean>

3.2、Spring AOP

Spring 框架的一个关键组件是面向方面的编程(AOP)框架。面向方面的编程需要把程序逻辑分解成不同的部分称为所谓的关注点。跨一个应用程序的多个点的功能被称为横切关注点,这些横切关注点在概念上独立于应用程序的业务逻辑。有各种各样的常见的很好的方面的例子,如日志记录、审计、声明式事务、安全性和缓存等。

AOP 术语

描述
Aspect 一个模块具有一组提供横切需求的 APIs。例如,一个日志模块为了记录日志将被 AOP 方面调用。应用程序可以拥有任意数量的方面,这取决于需求。
Join point 在你的应用程序中它代表一个点,你可以在插件 AOP 方面。你也能说,它是在实际的应用程序中,其中一个操作将使用 Spring AOP 框架。
Advice 这是实际行动之前或之后执行的方法。这是在程序执行期间通过 Spring AOP 框架实际被调用的代码。
Pointcut 这是一组一个或多个连接点,通知应该被执行。你可以使用表达式或模式指定切入点正如我们将在 AOP 的例子中看到的。
Introduction 引用允许你添加新方法或属性到现有的类中。
Target object 被一个或者多个方面所通知的对象,这个对象永远是一个被代理对象。也称为被通知对象。
Weaving Weaving 把方面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时,类加载时和运行时完成。

通知的类型

通知 描述
前置通知 在一个方法执行之前,执行通知。
后置通知 在一个方法执行之后,不考虑其结果,执行通知。
返回后通知 在一个方法执行之后,只有在方法成功完成时,才能执行通知。
抛出异常后通知 在一个方法执行之后,只有在方法退出抛出异常时,才能执行通知。
环绕通知 在建议方法调用之前和之后,执行通知。

一、基于XML

二、基于注解

搞个简单的例子

为了测试异常,改一下service

public class TestService {
    public void test(){
        System.out.println("测试方法");
        int i = 1 / 0;
    }
}

添加一个切面类

@Aspect
@Component
public class LogAsp {
    private final static Logger logger = LoggerFactory.getLogger(LogAsp.class);

    // 声明一个切入点
    @Pointcut("execution(public * com.test.day.TestService.*(..))")
    public void aspect() {
    }

    @Before("aspect()")
    public void beforeAdvice(){
        logger.info("前置通知。。。");
    }

    @After("aspect()")
    public void afterAdvice(){
        logger.info("后置通知。。。");
    }

    @AfterReturning(pointcut = "aspect()", returning="retVal")
    public void afterReturningAdvice(Object retVal){
        logger.info("返回后通知。。。" );
    }

    @AfterThrowing(pointcut = "aspect()", throwing = "ex")
    public void AfterThrowingAdvice(Exception ex){
        logger.info("抛出异常后通知...");
    }
    
    // @Around("aspect()")
    // public Object aroundAdvice(ProceedingJoinPoint pjp){
    //     Object obj = null;
    //     try {
    //         Object[] args = pjp.getArgs();// 得到方法所需的参数
    //         logger.info("环绕通知:前置...");
    //         //明确调用业务层方法
    //         obj = pjp.proceed(args);
    //         logger.info("环绕通知:后置...");
    //         return obj;
    //     } catch (Throwable throwable) {
    //         logger.info("环绕通知:异常...");
    //         throw new RuntimeException(throwable);
    //     }finally {
    //         logger.info("环绕通知:最终...");
    //     }
    // }
}

修改一下spring配置文件

<context:component-scan base-package="com.test.day">
    <context:include-filter type="regex" expression="com.test.day.*"/>
</context:component-scan>

<!--通过配置织入@Aspectj切面-->
<aop:aspectj-autoproxy expose-proxy="true"></aop:aspectj-autoproxy>

注意:aop:aspectj-autoproxy 正常是需要配置在 spring-mvc.xml 中的,我是为了测试只有一个配置文件就放在一起了。

启动测试类,查看打印结果,么得问题

3.3、Spring 事务管理

一个数据库事务是一个被视为单一的工作单元的操作序列。这些操作应该要么完整地执行,要么完全不执行。事务管理是一个重要组成部分,RDBMS 面向企业应用程序,以确保数据完整性和一致性。事务的概念可以描述为具有以下四个关键属性说成是 ACID:

  • 原子性:事务应该当作一个单独单元的操作,这意味着整个序列操作要么是成功,要么是失败的。

  • 一致性:这表示数据库的引用完整性的一致性,表中唯一的主键等。

  • 隔离性:可能同时处理很多有相同的数据集的事务,每个事务应该与其他事务隔离,以防止数据损坏。

  • 持久性:一个事务一旦完成全部操作后,这个事务的结果必须是永久性的,不能因系统故障而从数据库中删除。

局部事物 vs. 全局事务

  • 局部事务是特定于一个单一的事务资源,如一个 JDBC 连接,而全局事务可以跨多个事务资源事务,如在一个分布式系统中的事务。

  • 局部事务管理在一个集中的计算环境中是有用的,该计算环境中应用程序组件和资源位于一个单位点,而事务管理只涉及到一个运行在一个单一机器中的本地数据管理器。局部事务更容易实现。

  • 全局事务管理需要在分布式计算环境中,所有的资源都分布在多个系统中。在这种情况下事务管理需要同时在局部和全局范围内进行。分布式或全局事务跨多个系统执行,它的执行需要全局事务管理系统和所有相关系统的局部数据管理人员之间的协调。

编程式 vs. 声明式

Spring 支持两种类型的事务管理:

  • 编程式事务管理 :这意味着你在编程的帮助下有管理事务。这给了你极大的灵活性,但却很难维护。

  • 声明式事务管理 :这意味着你从业务代码中分离事务管理。你仅仅使用注释或 XML 配置来管理事务。

3.4、Spring Web MVC 框架

MVC 框架提供了模型-视图-控制的体系结构和可以用来开发灵活、松散耦合的 web 应用程序的组件。MVC 模式导致了应用程序的不同方面(输入逻辑、业务逻辑和 UI 逻辑)的分离,同时提供了在这些元素之间的松散耦合。

  • 模型封装了应用程序数据,并且通常它们由 POJO 组成。

  • 视图主要用于呈现模型数据,并且通常它生成客户端的浏览器可以解释的 HTML 输出。

  • 控制器主要用于处理用户请求,并且构建合适的模型并将其传递到视图呈现。

DispatcherServlet

Spring Web 模型-视图-控制(MVC)框架是围绕 DispatcherServlet 设计的,DispatcherServlet 用来处理所有的 HTTP 请求和响应。Spring Web MVC DispatcherServlet 的请求处理的工作流程如下图所示:

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

上面所提到的所有组件,即 HandlerMapping、Controller 和 ViewResolver 是 WebApplicationContext 的一部分,而 WebApplicationContext 是带有一些对 web 应用程序必要的额外特性的 ApplicationContext 的扩展。

4、常见问题分析

4.1、BeanFactory和ApplicationContext有什么区别?

  • 1、BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能(国际化、访问资源、载入多个(有继承关系)上下文等)

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

  • 3、ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。它还可以为Bean配置lazy-init=true来让Bean延迟实例化。

  • 4、相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。

4.2、Spring BeanFactory 与 FactoryBean 的区别?

  • BeanFactory是一个factory,是spring的IOC的工场,而FactoryBean是个bean,它们两个只是名字很相似。

  • BeanFactory是一个IOC工场,用于管理和创建Bean,它是IOC最基本的接口,为其他的IOC工场提供规范,很多其他的spring容器都实现了它,如ApplicationContext、XMLBeanFactory等。它提供了通过bean的名字获取实例、判断bean是否在工场中、判断是否为单例等方法。

  • 对FactoryBean而言,这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似

4.3、Spring如何处理线程并发问题?

  • 在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。

  • ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。

  • ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

4.4、Spring 框架中都用到了哪些设计模式?

  • 工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;

  • 单例模式:Bean默认为单例模式。

  • 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;

  • 模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。

  • 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现--ApplicationListener。

4.5、Spring 同一个aspect,不同advice的执行顺序?

没有异常情况下的执行顺序:

  • around before advice(环绕通知:前置)
  • before advice(前置通知)
  • target method execution (方法执行)
  • around after advice(环绕通知:后置)
  • after advice(后置通知)
  • afterReturning(返回后通知)

有异常情况下的执行顺序:

  • around before advice(环绕通知:前置)
  • before advice(前置通知)
  • target method execution(方法执行)
  • after advice(后置通知)
  • afterThrowing(抛出异常后通知)
  • throw exception(抛出异常)

持续更新中。。。