前言
一年多前,我开始了Java Web学习之旅。这个旅程是断断续续的,因为它与我的工作领域没有太多重合。最近看了很多Java和Spring的资料,然而离感觉自己学会了,仍有一点距离。因此,就继续之前的Servlet和JSP博客系列吧。
本系列将会覆盖到Spring的基础以及Spring MVC, 当然还有Spring Boot. 除了开始介绍 Spring及其配置的文章,后面将都使用 Spring Boot。
同时,本系列将不是照本宣科的学习,还会有一些思考和讨论。否则,很容易陷入一堆配置的泥潭,越学越糊涂。做好准备,我们开始吧!
Spring简介
本段内容引自, 很棒的博客!
Spring框架由Rod Johnson开发,第一版于2004年发布。Spring是一个从实际开发中抽取出来的框架,因此它完成了大量开发中的通用步骤,留给开发者的仅仅是与特定应用相关的部分,从而大大提高了企业应用的开发效率。
Spring总结起来优点如下:
- 低侵入式设计,代码的污染极低。
- 独立于各种应用服务器,基于Spring框架的应用,可以真正实现Write Once,Run Anywhere的承诺。
- Spring的IoC容器降低了业务对象替换的复杂性,提高了组件之间的解耦。
- Spring的AOP支持允许将一些通用任务如安全、事务、日志等进行集中式管理,从而提供了更好的复用。
- Spring的ORM和DAO提供了与第三方持久层框架的良好整合,并简化了底层的数据库访问。
- Spring的高度开放性,并不强制应用完全依赖于Spring,开发者可自由选用Spring框架的部分或全部。
- 蓬勃的社区,Spring全家桶,你懂的。
依赖注入
Spring的历史不多谈,但提到Spring,一定会提到它的核心容器。你可能还听过另一个词:IoC,控制反转。这个词显然更加不知所云。
IoC是更早提出的概念,它是OO设计模式的一种。而在IoC之前,还有个设计原则,叫依赖倒置原则(DIP)。依赖倒置原则:高层模块不应该依赖低层模块,而两个都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
关于它们的前世今生,讲的非常好。
总结起来就是:
- DIP强调要面向接口编程,因此使用者最好只持有被使用者的接口的引用。
- IoC进一步指出创建所依赖的对象这个动作也不应该由使用者负责,那就要交给第三方处理。
- DI (依赖注入)是IoC的一种实现。常见方式是构造函数注入和属性注入。
- IoC 容器是功能更加强大的依赖注入框架,可以实现通过配置文件配置依赖,以及对象的生命周期管理等等。
使用容器是必须的吗?
显然,对于Java Web来说,使用Spring容器别无选择。以至于会让一些人认为这是天经地义的。然而放眼其他语言,几乎都有各自的容器框架,却很少有像Spring这样得到了普遍的应用。甚至,在我们公司的.NET开发规范中,明确指出了Unity这种容器框架过重,不推荐使用。以下是一些讨论(在讨论之前,我们先明确一点 —— IoC 不等于 IoC容器):
- “使用容器可以方便单元测试”。并不是,只要面向接口编程,多提供几个构造函数,就可以很方便的使用Mock做单元测试。
- “创建依赖对象有时过于复杂,交给容器即可”。创建对象可以交给工厂模式解决。
- "容器可以集中配置,而且只修改XML,不编译就可以切换应用策略"。额,这些功能使用工厂+配置文件+反射也可以做。况且,使用XML配置依赖真的好吗?那为什么Spring 4.0之后改为推荐使用代码配置呢?
- “使用容器可以方便地管理对象生命周期,方便实现单例模式”。 额,算是一个小理由。
- "使用容器可以方便实现AOP,否则很难实现一个动态代理"。这算是一个比较好的理由。
而使用容器的缺点也显而易见,如今到处都是Annotation,根本不是集中配置。需要记住的约定一大堆,出了问题还要排查是不是Spring注入的错。在代码中定位依赖也同样很不直观。
以下是网上的一些讨论,并非为了撕,只是提供更全面的认识。
最后,既来之则安之,下面我们正式开始。
Bean与容器
JavaBean、EJB、POJO这些东西又有很多历史可以扯。具体参见。结论就是,在Spring容器里,Bean就是POJO。也就是说:
- Spring容器是一个超级大工厂(和仓库),负责创建、管理所有的Java对象,这些Java对象被称为Bean。
- Spring容器管理容器中Bean之间的依赖关系,Spring使用"依赖注入"来管理Bean之间的依赖关系。
Spring容器的入口
Spring通过应用上下文(Application Context)来行使容器的工作。Spring自带了多种类型的应用上下文实现,每种都以具体的类表示。下面是常用的几个:
-
AnnotationConfigApplicationContext
:从Java配置类中加载Spring应用上下文。 -
AnnotationConfigWebApplicationContext
:从Java配置类中加载Spring Web应用上下文。 -
ClassPathXmlApplicationContext
:从ClassPath下的XML配置文件中加载Spring应用上下文。 -
FileSystemXmlApplicationContext
:从文件系统的XML配置文件中加载Spring应用上下文。 -
XmlWebApplicationContext
:从Web应用中的XML配置文件中加载Spring Web应用上下文。
既然是入口类,使用它们只有一种方式:直接或间接地new出一个实例。如下是一个简单的例子:
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");User u = (User)context.getBean("user");System.out.println(u);
Spring配置的三种方案
Spring的配置方案随着历史的演进,支持以下三种:
- 在XML中显式配置
- 隐式的bean自动扫描和自动装配
- 在Java中显式配置
XML配置是最老的技术;使用Java类配置是最新的推荐。而自动扫描和自动装配使用XML和Java都可以指定。
使用Java显式配置
- 使用
@Configuration
注解创建配置类。 - 使用
@Bean
注解声明Bean。
隐式的bean自动扫描和自动装配
所谓装配(wiring)就是指创建所依赖的对象的行为,即依赖注入。自动装配就是容器推断出所依赖的具体对象,创建它并设置好使用类。
组件扫描则是指Spring可以扫描应用中有特殊注解的类,为其创建Bean。- 通过
@Component
注解标记可被发现的类。当介绍到Spring Web部分时,我们还会看到其他几个相似的注解。 - 通过
@ComponentScan
注解启用组件扫描。 - 通过
@Autowired
注解开启自动装配。另外Spring也重用了JDK中的@Resource
注解。两者有微小的差异。
使用XML显式配置
使用Java Config能做到的在XML中也能做到。例如:
-
<bean class="xxx"/>
声明bean。 -
<context:component-scan base-package="yyy"/>
启用组件扫描。 -
<bean autowire="true"/>
开启自动装配。
Bean的作用域
当通过Spring容器创建一个Bean实例时,不仅可以完成Bean实例的实例化,还可以为Bean指定特定的作用域。Spring支持如下五种作用域:
- Singleton: 单例模式,在整个Spring容器中,singleton作用域的Bean将只生成一个实例。
- Prototype: 每次通过容器的
getBean()
方法获取prototype作用域的Bean时,都将产生一个新的Bean实例。 - Request: 对于一次HTTP请求,request作用域的Bean将只生成一个实例,这意味着,在同一次HTTP请求内,程序每次请求该Bean,得到的总是同一个实例。只有在Web应用中使用Spring时,该作用域才真正有效。
- Session: 对于一次HTTP会话,session作用域的Bean将只生成一个实例,这意味着,在同一次HTTP会话内,程序每次请求该Bean,得到的总是同一个实例。只有在Web应用中使用Spring时,该作用域才真正有效。
- Global session: 每个全局的HTTP Session对应一个Bean实例。在典型的情况下,仅在使用portlet context的时候有效,同样只在Web应用中有效。
如果不指定Bean的作用域,Spring默认使用Singleton作用域。Prototype作用域的Bean的创建、销毁代价比较大。而Singleton作用域的Bean实例一旦创建成功,就可以重复使用。因此,应该尽量避免将Bean设置成Prototype作用域。