写点什么

Java 必看的 Spring 知识汇总!

  • 2021 年 12 月 28 日
  • 本文字数:7541 字

    阅读完需:约 25 分钟

Spring 框架是由于软件开发的复杂性而创建的。Spring 使用的是基本的 JavaBean 来完成以前只可能由 EJB 完成的事情。然而,Spring 的用途不仅仅限于服务器端的开发。从简单性、可测试性和松耦合性的角度而言,绝大部分 Java 应用都可以从 Spring 中受益。


Spring 优点:


低侵入式设计,代码的污染极低;


独立于各种应用服务器,基于 Spring 框架的应用,可以真正实现 Write Once,Run Anywhere 的承诺;


Spring 的 IoC 容器降低了业务对象替换的复杂性,提高了组件之间的解耦


Spring 的 AOP 支持允许将一些通用任务如安全、事务、日志等进行集中式管理,从而提供了更好的复用;


Spring 的 ORM 和 DAO 提供了与第三方持久层框架的良好整合,并简化了底层的数据库访问;


pring 的高度开放性,并不强制应用完全依赖于 Spring,开发者可自由选用 Spring 框架的部分或全部。


Spring 框架的组成结构图:


Spring 的核心机制


管理 Bean


程序主要是通过 Spring 容器来访问容器中的 Bean,ApplicationContext 是 Spring 容器最常用的接口,该接口有如下两个实现类:


ClassPathXmlApplicationContext: 从类加载路径下搜索配置文件,并根据配置文件来创建 Spring 容器;


FileSystemXmlApplicationContext: 从文件系统的相对路径或绝对路径下去搜索配置文件,并根据配置文件来创建 Spring 容器


publicclassBeanTest{publicstaticvoidmain(String args) throws Exception{ ApplicationContext ctx =newClassPathXmlApplicationContext(“beans.xml”); Person p = ctx.getBean(“person”, Person.class); p.say; } }


Eclipse 使用 Spring


在 Eclipse 等 IDE 工具中,用户可以自建 User Library,然后把 Spring 的 Jar 包都放入其中,当然也可以将 Jar 包直接放在项目的/WEB-INF/lib 目录下,但是如果使用 User Library,在项目发布时,需要将用户库所引用的 Jar 文件随应用一起发布,就是将 User Library 所使用的 Jar 复制到/WEB-INF/lib 目录下,这是因为对于一个 Web 应用,Eclipse 部署 Web 应用时不会将用户库的 Jar 文件复制到/WEB-INF/lib 下,需要手动复制。


依赖注入


Spring 框架的核心功能有两个:


Spring 容器作为超级大工厂,负责创建、管理所有的 Java 对象,这些 Java 对象被称为 Bean;


Spring 容器管理容器中 Bean 之间的依赖关系,Spring 使用一种被称为“依赖注入”的方式来管理 Bean 之间的依赖关系。


使用依赖注入,不仅可以为 Bean 注入普通的属性值,还可以注入其他 Bean 的引用。依赖注入是一种优秀的解耦方式,其可以让 Bean 以配置文件组织在一起,而不是以硬编码的方式耦合在一起。


理解依赖注入


Rod Johnson 是第一个高度重视以配置文件来管理 Java 实例的协作关系的人,他给这种方式起了一个名字:控制反转(Inverse of Control,IoC)。后来 Martine Fowler 为这种方式起了另一个名称:依赖注入(Dependency Injection),因此不管是依赖注入,还是控制反转,其含义完全相同。当某个 Java 对象(调用者)需要调用另一个 Java 对象(被依赖对象)的方法时,在传统模式下通常有两种做法:


原始做法: 调用者主动创建被依赖对象,然后再调用被依赖对象的方法;


简单工厂模式: 调用者先找到被依赖对象的工厂,然后主动通过工厂去获取被依赖对象,最后再调用被依赖对象的方法。


注意上面的主动二字,这必然会导致调用者与被依赖对象实现类的硬编码耦合,非常不利于项目升级的维护。使用 Spring 框架之后,调用者无需主动获取被依赖对象,调用者只要被动接受 Spring 容器为调用者的成员变量赋值即可,由此可见,使用 Spring 后,调用者获取被依赖对象的方式由原来的主动获取,变成了被动接受——所以 Rod Johnson 称之为控制反转。


另外从 Spring 容器的角度来看,Spring 容器负责将被依赖对象赋值给调用者的成员变量——相当于为调用者注入它依赖的实例,因此 Martine Fowler 称之为依赖注入。


设值注入


设值注入是指 IoC 容器通过成员变量的 setter 方法来注入被依赖对象。这种注入方式简单、直观,因而在 Spring 的依赖注入里大量使用。


构造注入


利用构造器来设置依赖关系的方式,被称为构造注入。通俗来说,就是驱动 Spring 在底层以反射方式执行带指定参数的构造器,当执行带参数的构造器时,就可利用构造器参数对成员变量执行初始化——这就是构造注入的本质。


两种注入方式的对比:


设值注入有如下优点:


与传统的 JavaBean 的写法更相似,程序开发人员更容易理解、接受。通过 setter 方法设定依赖关系显得更加直观、自然;


对于复杂的依赖关系,如果采用构造注入,会导致构造器过于臃肿,难以阅读。Spring 在创建 Bean 实例时,需要同时实例化其依赖的全部实例,因而导致性能下降。而使用设值注入,则能避免这些问题。


尤其在某些成员变量可选的情况下,多参数的构造器更加笨重。


构造注入优势如下:


构造注入可以在构造器中决定依赖关系的注入顺序,优先依赖的优先注入;


对于依赖关系无需变化的 Bean,构造注入更有用处。因为没有 setter 方法,所有的依赖关系全部在构造器内设定,无须担心后续的代码对依赖关系产生破坏;


依赖关系只能在构造器中设定,则只有组件的创建者才能改变组件的依赖关系,对组件的调用者而言,组件内部的依赖关系完全透明,更符合高内聚的原则。


Notes 建议采用设值注入为主,构造注入为辅的注入策略。对于依赖关系无须变化的注入,尽量采用构造注入;而其他依赖关系的注入,则考虑采用设值注入。


Spring 容器中的 Bean


对于开发者来说,开发者使用 Spring 框架主要是做两件事:①开发 Bean;②配置 Bean。对于 Spring 框架来说,它要做的就是根据配置文件来创建 Bean 实例,并调用 Bean 实例的方法完成“依赖注入”——这就是所谓 IoC 的本质。


容器中 Bean 的作用域


当通过 Spring 容器创建一个 Bean 实例时,不仅可以完成 Bean 实例的实例化,还可以为 Bean 指定特定的作用域。Spring 支持如下五种作用域:


singleton: 单例模式,在整个 Spring IoC 容器中,singleton 作用域的 Bean 将只生成一个实例;


prototype: 每次通过容器的 getBean 方法获取 prototype 作用域的 Bean 时,都将产生一个新的 Bean 实例;


request: 对于一次 HTTP 请求,request 作用域的 Bean 将只生成一个实例,这意味着,在同一次 HTTP 请求内,程序每次请求该 Bean,得到的总是同一个实例。只有在 Web 应用中使用 Spring 时,该作用域才真正有效;


对于一次 HTTP 会话,session 作用域的 Bean 将只生成一个实例,这意味着,在同一次 HTTP 会话内,程序每次请求该 Bean,得到的总是同一个实例。只有在 Web 应用中使用 Spring 时,该作用域才真正有效;


global session: 每个全局的 HTTP Session 对应一个 Bean 实例。在典型的情况下,仅在使用 portlet context 的时候有效,同样只在 Web 应用中有效。


如果不指定 Bean 的作用域,Spring 默认使用 singleton 作用域。prototype 作用域的 Bean 的创建、销毁代价比较大。而 singleton 作用域的 Bean 实例一旦创建成果,就可以重复使用。因此,应该尽量避免将 Bean 设置成 prototype 作用域。


使用自动装配注入合作者 Bean


Spring 能自动装配 Bean 与 Bean 之间的依赖关系,即无须使用 ref 显式指定依赖 Bean,而是由 Spring 容器检查 XML 配置文件内容,根据某种规则,为调用者 Bean 注入被依赖的 Bean。Spring 自动装配可通过元素的 default-autowire 属性指定,该属性对配置文件中所有的 Bean 起作用;也可通过对元素的 autowire 属性指定,该属性只对该 Bean 起作用。


autowire 和 default-autowire 可以接受如下值:


no: 不使用自动装配。Bean 依赖必须通过 ref 元素定义。这是默认配置,在较大的部署环境中不鼓励改变这个配置,显式配置合作者能够得到更清晰的依赖关系;


byName: 根据 setter 方法名进行自动装配。Spring 容器查找容器中全部 Bean,找出其 id 与 setter 方法名去掉 set 前缀,并小写首字母后同名的 Bean 来完成注入。如果没有找到匹配的 Bean 实例,则 Spring 不会进行任何注入;


byType: 根据 setter 方法的形参类型来自动装配。Spring 容器查找容器中的全部 Bean,如果正好有一个 Bean 类型与 setter 方法的形参类型匹配,就自动注入这个 Bean;如果找到多个这样的 Bean,就抛出一个异常;如果没有找到这样的 Bean,则什么都不会发生,setter 方法不会被调用;


constructor: 与 byType 类似,区别是用于自动匹配构造器的参数。如果容器不能恰好找到一个与构造器参数类型匹配的 Bean,则会抛出一个异常;


autodetect: Spring 容器根据 Bean 内部结构,自行决定使用 constructor 或 byType 策略。如果找到一个默认的构造函数,那么就会应用 byType 策略。


当一个 Bean 既使用自动装配依赖,又使用 ref 显式指定依赖时,则显式指定的依赖覆盖自动装配依赖;对于大型的应用,不鼓励使用自动装配。虽然使用自动装配可减少配置文件的工作量,但大大将死了依赖关系的清晰性和透明性。依赖关系的装配依赖于源文件的属性名和属性类型,导致 Bean 与 Bean 之间的耦合降低到代码层次,不利于高层次解耦;


<beanid=""autowire-candidate="false"/>


<beansdefault-autowire-candidates="*abc"/>创建 Bean 的 3 种方式:


使用构造器创建 Bean 实例


使用构造器来创建 Bean 实例是最常见的情况,如果不采用构造注入,Spring 底层会调用 Bean 类的无参数构造器来创建实例,因此要求该 Bean 类提供无参数的构造器。


采用默认的构造器创建 Bean 实例,Spring 对 Bean 实例的所有属性执行默认初始化,即所有的基本类型的值初始化为 0 或 false;所有的引用类型的值初始化为 null。


使用静态工厂方法创建 Bean


使用静态工厂方法创建 Bean 实例时,class 属性也必须指定,但此时 class 属性并不是指定 Bean 实例的实现类,而是静态工厂类,Spring 通过该属性知道由哪个工厂类来创建 Bean 实例。


除此之外,还需要使用 factory-method 属性来指定静态工厂方法,Spring 将调用静态工厂方法返回一个 Bean 实例,一旦获得了指定 Bean 实例,Spring 后面的处理步骤与采用普通方法创建 Bean 实例完全一样。如果静态工厂方法需要参数,则使用< constructor-arg…/ >元素指定静态工厂方法的参数。


调用实例工厂方法创建 Bean


实例工厂方法与静态工厂方法只有一个不同:调用静态工厂方法只需使用工厂类即可,而调用实例工厂方法则需要工厂实例。使用实例工厂方法时,配置 Bean 实例的< bean…/ >元素无须 class 属性,配置实例工厂方法使用 factory-bean 指定工厂实例。采用实例工厂方法创建 Bean 的< bean…/ >元素时需要指定如下两个属性:


factory-bean: 该属性的值为工厂 Bean 的 id


factory-method: 该属性指定实例工厂的工厂方法


若调用实例工厂方法时需要传入参数,则使用< constructor-arg…/ >元素确定参数值。


协调作用域不同步的 Bean


当 singleton 作用域的 Bean 依赖于 prototype 作用域的 Bean 时,会产生不同步的现象,原因是因为当 Spring 容器初始化时,容器会预初始化容器中所有的 singleton Bean,由于 singleton Bean 依赖于 prototype Bean,因此 Spring 在初始化 singleton Bean 之前,会先创建 prototypeBean——然后才创建 singleton Bean,接下里将 prototype Bean 注入 singleton Bean。解决不同步的方法有两种:


放弃依赖注入: singleton 作用域的 Bean 每次需要 prototype 作用域的 Bean 时,主动向容器请求新的 Bean 实例,即可保证每次注入的 prototype Bean 实例都是最新的实例;


利用方法注入: 方法注入通常使用 lookup 方法注入,使用 lookup 方法注入可以让 Spring 容器重写容器中 Bean 的抽象或具体方法,返回查找容器中其他 Bean 的结果,被查找的 Bean 通常是一个 non-singleton Bean。Spring 通过使用 JDK 动态代理或 cglib 库修改客户端的二进制码,从而实现上述要求。


建议采用第二种方法,使用方法注入。为了使用 lookup 方法注入,大致需要如下两步:


将调用者 Bean 的实现类定义为抽象类,并定义一个抽象方法来获取被依赖的 Bean2.在< bean…/ >元素中添加< lookup-method…/ >子元素让 Spring 为调用者 Bean 的实现类实现指定的抽象方法 Notes;


Spring 会采用运行时动态增强的方式来实现<lookup-method…/>元素所指定的抽象方法,如果目标抽象类实现过接口,Spring 会采用 JDK 动态代理来实现该抽象类,并为之实现抽象方法;如果目标抽象类没有实现过接口,Spring 会采用 cglib 实现该抽象类,并为之实现抽象方法。Spring4.0 的 spring-core-xxx.jar 包中已经集成了 cglib 类库。


两种后处理器:


Spring 提供了两种常用的后处理器:


Bean 后处理器: 这种后处理器会对容器中 Bean 进行后处理,对 Bean 进行额外加强;


容器后处理器: 这种后处理器会对 IoC 容器进行后处理,用于增强容器功能。


Bean 后处理器


Bean 后处理器是一种特殊的 Bean,这种特殊的 Bean 并不对外提供服务,它甚至可以无须 id 属性,它主要负责对容器中的其他 Bean 执行后处理,例如为容器中的目标 Bean 生成代理等,这种 Bean 称为 Bean 后处理器。Bean 后处理器会在 Bean 实例创建成功之后,对 Bean 实例进行进一步的增强处理。Bean 后处理器必须实现 BeanPostProcessor 接口,同时必须实现该接口的两个方法。


1.Object postProcessBeforeInitialization(Object bean, String name) throws BeansException: 该方法的第一个参数是系统即将进行后处理的 Bean 实例,第二个参数是该 Bean 的配置 id2.Object postProcessAfterinitialization(Object bean, String name) throws BeansException: 该方法的第一个参数是系统即将进行后处理的 Bean 实例,第二个参数是该 Bean 的配置 id。


容器中一旦注册了 Bean 后处理器,Bean 后处理器就会自动启动,在容器中每个 Bean 创建时自动工作,Bean 后处理器两个方法的回调时机如下图


注意一点,如果使用 BeanFactory 作为 Spring 容器,则必须手动注册 Bean 后处理器,程序必须获取 Bean 后处理器实例,然后手动注册。


BeanPostProcessor bp = (BeanPostProcessor)beanFactory.getBean("bp");beanFactory.addBeanPostProcessor(bp);Person p = (Person)beanFactory.getBean("person");容器后处理器


Bean 后处理器负责处理容器中的所有 Bean 实例,而容器后处理器则负责处理容器本身。容器后处理器必须实现 BeanFactoryPostProcessor 接口,并实现该接口的一个方法 postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)实现该方法的方法体就是对 Spring 容器进行的处理,这种处理可以对 Spring 容器进行自定义扩展,当然也可以对 Spring 容器不进行任何处理。


类似于 BeanPostProcessor,ApplicationContext 可自动检测到容器中的容器后处理器,并且自动注册容器后处理器。但若使用 BeanFactory 作为 Spring 容器,则必须手动调用该容器后处理器来处理 BeanFactory 容器。


Spring 的“零配置”支持


搜索 Bean 类:


Spring 提供如下几个 Annotation 来标注 Spring Bean


@Component: 标注一个普通的 Spring Bean 类


@Controller: 标注一个控制器组件类


@Service: 标注一个业务逻辑组件类


@Repository: 标注一个 DAO 组件类


在 Spring 配置文件中做如下配置,指定自动扫描的包


<context:component-scan base-package=”edu.shu.spring.domain”/>


使用 @Resource 配置依赖


@Resource 位于 javax.annotation 包下,是来自 JavaEE 规范的一个 Annotation,Spring 直接借鉴了该 Annotation,通过使用该 Annotation 为目标 Bean 指定协作者 Bean。使用 @Resource 与< property…/ >元素的 ref 属性有相同的效果。@Resource 不仅可以修饰 setter 方法,也可以直接修饰实例变量,如果使用 @Resource 修饰实例变量将会更加简单,此时 Spring 将会直接使用 JavaEE 规范的 Field 注入,此时连 setter 方法都可以不要。


使用 @PostConstruct 和 @PreDestroy 定制生命周期行为


@PostConstruct 和 @PreDestroy 同样位于 javax.annotation 包下,也是来自 JavaEE 规范的两个 Annotation,Spring 直接借鉴了它们,用于定制 Spring 容器中 Bean 的生命周期行为。它们都用于修饰方法,无须任何属性。其中前者修饰的方法时 Bean 的初始化方法;而后者修饰的方法时 Bean 销毁之前的方法。


Spring4.0 增强的自动装配和精确装配


Spring 提供了 @Autowired 注解来指定自动装配,@Autowired 可以修饰 setter 方法、普通方法、实例变量和构造器等。当使用 @Autowired 标注 setter 方法时,默认采用 byType 自动装配策略。在这种策略下,符合自动装配类型的候选 Bean 实例常常有多个,这个时候就可能引起异常,为了实现精确的自动装配,Spring 提供了 @Qualifier 注解,通过使用 @Qualifier,允许根据 Bean 的 id 来执行自动装配。


Spring 的 AOP


为什么需要 AOP?


AOP(Aspect Orient Programming)也就是面向切面编程,作为面向对象编程的一种补充,已经成为一种比较成熟的编程方式。其实 AOP 问世的时间并不太长,AOP 和 OOP 互为补充,面向切面编程将程序运行过程分解成各个切面。


AOP 专门用于处理系统中分布于各个模块(不同方法)中的交叉关注点的问题,在 JavaEE 应用中,常常通过 AOP 来处理一些具有横切性质的系统级服务,如事务管理、安全检查、缓存、对象池管理等,AOP 已经成为一种非常常用的解决方案。


使用 AspectJ 实现 AOP


AspectJ 是一个基于 Java 语言的 AOP 框架,提供了强大的 AOP 功能,其他很多 AOP 框架都借鉴或采纳其中的一些思想。其主要包括两个部分:一个部分定义了如何表达、定义 AOP 编程中的语法规范,通过这套语法规范,可以方便地用 AOP 来解决 Java 语言中存在的交叉关注点的问题;另一个部分是工具部分,包括编译、调试工具等。


AOP 实现可分为两类


1.静态 AOP 实现: AOP 框架在编译阶段对程序进行修改,即实现对目标类的增强,生成静态的 AOP 代理类,以 AspectJ 为代表 2.动态 AOP 实现: AOP 框架在运行阶段动态生成 AOP 代理,以实现对目标对象的增强,以 Spring AOP 为代表


一般来说,静态 AOP 实现具有较好的性能,但需要使用特殊的编译器。动态 AOP 实现是纯 Java 实现,因此无须特殊的编译器,但是通常性能略差。


AOP 的基本概念


关于面向切面编程的一些术语


切面(Aspect): 切面用于组织多个 Advice,Advice 放在切面中定义;


连接点(Joinpoint): 程序执行过程中明确的点,如方法的调用,或者异常的抛出。在 Spring AOP 中,连接点总是方法的调用;


增强处理(Advice): AOP 框架在特定的切入点执行的增强处理。处理有“around”、“before”和“after”等类型;


切入点(Pointcut): 可以插入增强处理的连接点。简而言之,当某个连接点满足指定要求时,该连接点将被添加增强处理,该连接点也就变成了切入点 Spring 的 AOP 支持;


Spring 中的 AOP 代理由 Spring 的 IoC 容器负责生成、管理,其依赖关系也由 IoC 容器负责管理。为了在应用中使用 @AspectJ 支持,Spring 需要添加三个库:


aspectjweaver.jar


aspectjrt.jar


aopalliance.jar


并在 Spring 配置文件中做如下配置:


aop:aspectj-autoproxy/


context:component-scanbase-package="cn.javastack.service"<context:include-filter type="annotation"expression="org.aspectj.lang.annotation.Aspect"/>


</context:component-scan>


最后如果你觉得此文对你有一丁点帮助,点个赞。或者可以加入我的开发交流群:1025263163 相互学习,我们会有专业的技术答疑解惑


如果你觉得这篇文章对你有点用的话,麻烦请给我们的开源项目点点 star: https://gitee.com/ZhongBangKeJi/crmeb_java不胜感激 !


JAVA 学习手册:https://doc.crmeb.com技术交流论坛:https://q.crmeb.com

用户头像

还未添加个人签名 2021.11.02 加入

CRMEB就是客户关系管理+营销电商系统实现公众号端、微信小程序端、H5端、APP、PC端用户账号同步,能够快速积累客户、会员数据分析、智能转化客户、有效提高销售、会员维护、网络营销的一款企业应用

评论

发布
暂无评论
Java 必看的 Spring 知识汇总!