第1章 Spring之旅
两大核心
- 依赖注入(Dependency injection,DI)
- 面向切面编程(aspect-oriented-programming,AOP)
发展历程:
- 创建Spring的主要目的是用来替代更加重量级的企业级Java技术,尤其是EJB。相对于EJB说,Spring提供了更加轻量级和简单的编程模型。它增强了简单老式Java对象(Plain Old Java object,POJO)的功能,使其具备了之前只有EJB和其他企业级Java规范才具有的功能。
- 随着时间的推移,EJB以及Java 2企业版(Java 2 Enterprise Edition,J2EE)在不断演化。EJB自身也提供了面向简单POJO的编程模型。现在,EJB也采用了依赖注入(Dependency Injection,DI)和面向切面编程(Aspect-Oriented Programming,AOP)的理念,这毫无疑问是受到Spring成功的启发。
- 尽管J2EE(现在称之为JEE)能够赶上Spring的步伐,但Spring也没有停止前进。Spring继续在其他领域发展,而JEE则刚刚开始涉及这些领域,或者还完全没有开始在这些领域的创新。移动开发、社交API集成、NoSQL数据库、云计算以及大数据都是Spring正在涉足和创新的领域。Spring的前景依然会很美好。
1.1 简化Java开发
为了降低Java开发的复杂性,Spring采取了下面四种关键性策略
- 基于POJO的轻量级和最小侵入性编程;
- 通过依赖注入和面向接口实现松耦合;
- 基于切面和惯例进行声明式编程;
- 通过切面和模板减少样板式代码。
POJO:plain old java object,简单老实Java对象。使用POJO名称是为了避免和EJB混淆起来, 而且简称比较直接. 其中有一些属性及其getter setter方法的类,没有业务逻辑,有时可以作为VO(value -object)或dto(Data Transform Object)来使用.当然,如果你有一个简单的运算属性也是可以的,但不允许有业务方法,也不能携带有connection之类的方法。
1.1.1 基于POJO的轻量级和最小侵入性编程
侵入式框架:框架通过强迫应用继承它们的类或实现它们的接口从而导致应用与框架绑死。例如:struts框架。
非侵入式框架:不会强迫的让你继承框架提供的类或者接口,你一样可以使用,简而言之,拿来即用,不用去修改你原来的代码。例如:Hibernate框架,Spring框架等等。
1.1.2 通过依赖注入和面向接口实现松耦合
Spring通过应用上下文(Application Context)装载bean的定义并把它们组装起来。Spring应用上下文全权负责对象的创建和组装。Spring自带了多种应用上下文的实现,它们之间主要的区别仅仅在于如何加载配置。
1.1.3 基于切面和惯例进行声明式编程(应用切面)
面向切面编程(aspect-oriented programming,AOP)允许你把遍布应用各处的功能分离出来形成可重用的组件。
面向切面编程往往被定义为促使软件系统实现关注点的分离一项技术。系统由许多不同的组件组成,每一个组件各负责一块特定功能。除了实现自身核心的功能之外,这些组件还经常承担着额外的职责。诸如日志、事务管理和安全这样的系统服务经常融入到自身具有核心业务逻辑的组件中去,这些系统服务通常被称为横切关注点,因为它
们会跨越系统的多个组件。
横切关注点 :这些系统服务通常被称为横切关注点,因为他们会跨越系统的多个组件
如果将这些关注点分散到多个组件中去,你的代码将会带来双重的复杂性。
- 实现系统关注点功能的代码将会重复出现在多个组件中。这意味着如果你要改变这些关注点的逻辑,必须修改各个模块中的相关实现。即使你把这些关注点抽象为一个独立的模块,其他模块只是调用它的方法,但方法的调用还是会重复出现在各个模块中。组件会因为那些与自身核心业务无关的代码而变得混乱。一个向地址簿增加地址条目的方法应该只关注如何添加地址,而不应该关注它是不是安全的或者是否需要支持事务。
使用AOP的好处:
借助AOP,可以使用各种功能层去包裹核心业务层。这些层以声明的方式灵活地应用到系统中,你的核心应用甚至根本不知道它们的存在。这是一个非常强大的理念,可以将安全、事务和日志关注点与核心业务逻辑相分离。
1.1.4 使用模板消除样板式代码
Spring旨在通过模板封装来消除样板式代码。例如:Spring的JdbcTemplate使得执行数据库操作时,避免传统的JDBC样板代码成为了可能。(例如:在使用JDBC操作数据库的时候,需要关闭数据库连接,关闭Statement,关闭ResultSet等等这些都是重复性的代码,使用模板将这些重复性的代码封装到了模板中)
1.2 Spring容器
简单来说就是装JavaBean的容器,统一管理,按需装配(使用依赖注入的方式)。
它是Spring框架的核心。Spring容器使用DI管理构成应用的组件,它会创建相互协作的组件之间的关联。毫无疑问,这些对象更简单干净,更易于理解,更易于重用并且更易于进行单元测试。
Spring容器分为两种类型:
- Bean工厂 实现
org.springframework. beans.factory.BeanFactory接口,是最简单的容器,提供基本的DI支持. - 应用上下文 (ApplicationContext) 实现
org.springframework.context.ApplicationContext接口,它是基于Bean工厂实现的,并提供应用框架级别的服务,例如从属性文件解析文本信息以及发布应用事件给感兴趣的事件监听者。
Bean工厂功能态单一,所以大多数选用ApplicationContext
1.2.1 使用应用上下文
常用的应用上下文:
- AnnotationConfigApplicationContext:从一个或多个基于Java的配置类中加载Spring应用上下文。
- AnnotationConfigWebApplicationContext:从一个或多个基于Java的配置类中加载Spring Web应用上下文。
- ClassPathXmlApplicationContext:从类路径下的一个或多个XML配置文件中加载上下文定义,把应用上下文的定义文件作为类资源。
- FileSystemXmlapplicationcontext:从文件系统下的一个或多个XML配置文件中加载上下文定义。
- XmlWebApplicationContext:从Web应用下的一个或多个XML配置文件中加载上下文定义。
使用FileSystemXmlApplicationContext和使用ClassPathXmlApp-licationContext的区别在于:FileSystemXmlApplicationContext在指定的文件系统路径下查找bean.xml文件;而ClassPathXmlApplicationContext是在所有的类路径(包含JAR文件)下查找 bean.xml文件。
这里需要注意的是:使用 “classpath*:”和”classpath:”的区别
1.2.2 Bean的生命周期
读书笔记\Bean的生命周期.png)
- 1.Spring对bean进行实例化;
- 2.Spring将值和bean的引用注入到bean对应的属性中;
- 3.如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBean-Name()方法;
- 4.如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入;
- 5.如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来;
- 6.如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessBeforeInitialization()方法;
- 7.如果bean实现了InitializingBean接口,Spring将调用它们的after-PropertiesSet()方法。类似地,如果bean使用initmethod声明了初始化方法,该方法也会被调用;
- 8.如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessAfterInitialization()方法;
- 9.此时,bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;
- 10.如果bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法。同样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用。
1.3 Spring模块
1.4 Spring新方向
- Spring Boot
1.5 小节
Spring致力于简化开发,方便维护,其核心是依赖注入和面向切面编程
依赖注入,也叫作控制反转。正常情况下类中需要用到的组件都是需要使用自己去new,相当于控制正转。但是它的耦合性太高,不利于维护和测试。而控制反转是我们需要什么组件外界使用我们的时候自己传进来,例如可以通过set方法或者构造器传入。这样,依赖的对象可以有使用者自己去扩展,实现了松耦合。而依赖注入正是控制反转的一种实现。另一方面,使用Spring的依赖注入核心功能使得对象的管理更加清晰。
面向切面编程(AOP),是将散落的逻辑,例如日志(在Spring中称作关注点),事务等等,这些功能也不得不使用,将这些逻辑汇聚在一起,形成一个面,也就是一个独立的模块,这样讲这些与核心业务逻辑类分离。一方面能够减少业务逻辑类中的代码量,使其专注自己的逻辑。
AOP可以帮助应用将散落在各处的逻辑汇集于一处——切面。当Spring装配bean的时候,这些切面能够在运行期编织起来,这样就能非常有效地赋予bean新的行为。
第2章 装配Bean(Bean的配置与获取)
在Spring中,对象无需自己查找或创建与其所关联的其他对象。相反,容器负责把需要相互协作的对象引用赋予各个对象。例如,一个订单管理组件需要信用卡认证组件,但它不需要自己创建信用卡认证组件。订单管理组件只需要表明自己两手空空,容器就会主动赋予它一个信用卡认证组件。
创建应用对象之间协作关系的行为通常称为装配(wiring),这也是依赖注入(DI)的本质。
2.1 Spring容器的配置方式
- 在XML中进行显式配置。
- 在Java中进行显式配置。
- 隐式的bean发现机制和自动装配。
关于三种配置方式的选择问题:
选择自己喜欢的或者项目适合的配置方式。
搭配配置也是可以的
我的建议是尽可能地使用自动配置的机制。显式配置越少越好。当你必须要显式配置bean的时候(比如,有些源码不是由你来维护的,而当你需要为这些代码配置bean的时候),我推荐使用类型安全并且比XML更加强大的JavaConfig。最后,只有当你想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使用XML。
未完待续…
第4章 面向切面的Spring(Spring中的AOP)
软件系统中的一些功能就像我们家里的电表一样。这些功能需要用到应用程序的多个地方,但是我们又不想在每个点都明确调用它们。日志、安全和事务管理的确都很重要,但它们是否为应用对象主动参与的行为呢?如果让应用对象只关注于自己所针对的业务领域问题,而其他方面的问题由其他应用对象来处理,这会不会更好呢?
在软件开发中,散布于应用中多处的功能被称为横切关注点(crosscuttingconcern)。通常来讲,这些横切关注点从概念上是与应用的业务逻辑相分离的(但是往往会直接嵌入到应用的业务逻辑之中)。把这些横切关注点与业务逻辑相分离正是面向切面编程(AOP)所要解决的问题。
理解:各司其职,例如日志功能呢,虽然我们希望日志系统能够很好的帮我们记日志,但是日志系统的主要工作范畴。他就像财务,是替我们记账的。横切关注点串联起来,可以形成一个完整的面,该面相当于一个分布于应用中的微型系统。例如,日志,错误处理,事务管理等等。
DI有助于应用对象之间的解耦,而AOP可以实现横切关注点与它们所影响的对象之间的解耦。
4.1 什么是面向切面编程
Spring切面的实现原理
切面提供了取代继承和委托的另一种可选方案,而且在很多场景下更清晰简洁。在使用面向切面编程时,我们仍然在一个地方定义通用功能,但是可以通过声明的方式定义这个功能要以何种方式在何处应用,而无需修改受影响的类。**横切关注点可以被模块化为特殊的类,这些类被称为切面(aspect)。
这样做有两个好处:首先,现在每个关注点都集中于一个地方,而不是分散到多处代码中;其次,服务模块更简洁,因为它们只包含主要关注点(或核心功能)的代码,而次要关注点的代码被转移到切面中了。
4.1.1 AOP相关术语
通知(Advice)–也叫增强
通知定义了切面是什么以及何时使用。除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题。它应该应用在某个方法被调用之前?之后?之前和之后都调用?还是只在方法抛出异常时调用?
Spring切面可以应用5种类型的通知:
- 前置通知(Before):在目标方法被调用之前调用通知功能;
- 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
连接点(Join point)
我们的应用可能也有数以千计的时机应用通知。这些时机被称为连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
简单理解就是:所有可以被增强的方法
切点(Poincut)
如果说通知定义了切面的“什么”和“何时”的话,那么切点就定义了“何处”。切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。有些AOP框架允许我们创建动态的切点,可以根据运行时的决策(比如方法的参数值)来决定是否应用通知。
简单理解:可以被增强的方法有很多个,但是并不是所有的方法都需要被增强,从连接点中选取一部分必要的方法来增强.切点是连接点的子集。
切面(Aspect)
切面是通知和切点的结合。通知和切点共同定义了切面的全部内容——它是什么,在何时和何处完成其功能。
简单理解:切面就是通知(增强)和切点编织在一起。他知道在哪里应用增强以及增强的内容。例如:在管理员往数据库中插入一条记录的时候就将插入记录的相关细节记录到日志中去。
引入(Introduction)
引入允许我们向现有的类添加新方法或属性。例如,我们可以创建一个Auditable通知类,该类记录了对象最后一次修改时的状态。这很简单,只需一个方法,setLastModified(Date),和一个实例变量来保存这个状态。然后,这个新方法和实例变量就可以被引入到现有的类中,从而可以在无需修改这些现有的类的情况下,让它们具有新的行为和状态。
织入(Weaving)
织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以进行织入:
- 编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。
- 类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ 5的加载时织入(load-timeweaving,LTW)就支持以这种方式织入切面。
- 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。Spring AOP就是以这种方式织入切面的。
简单理解:创建切面的过程
4.1.2 Spring对AOP的支持
Spring提供了4种类型的AOP支持:
- 基于代理的经典Spring AOP;
- 纯POJO切面;
- @AspectJ注解驱动的切面;
- 注入式AspectJ切面(适用于Spring各版本)。
前三种都是Spring AOP实现的变体,Spring AOP构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法拦截。
Spring中AOP的特点:
Spring在运行时,也就是说当真正的需要被代理的对象时,才创建代理对象.即运行时织入.
Spring只支持方法级别的连接点.不支持字段和构造器接入点.
但是方法拦截可以满足绝大部分的需求。如果需要方法拦截之外的连接点拦截功能,那么我们可以利用Aspect来补充Spring AOP的功能。
在Spring AOP中,要使用AspectJ的切点表达式语言来定义切点。
关于Spring AOP的AspectJ切点,最重要的一点就是Spring仅支持AspectJ切点指示器(pointcut designator)的一个子集
4.2 编写切点
例如: execution(* com.lee.aop.LeeDao.*(..))
说明:
- 第一个*代表返回任意类型
- 第二个*代表匹配LeeDao类中的所有的方法
- (..)代表任意的参数
除了execution之外还有以下这些AspectJ指示器
arg()限制连接点匹配参数为指定类型的执行方法@args()限制连接点匹配参数由指定注解标注的执行方法例如:
execution(* com.lee.aop.LeeDao.findById(int)) && args(num)表示当调用findById的时候传入的参数同时会传入到通知方法中去,此时通知方法可以在方法参数列表中指定一个参数用来接收这个参数,必须要保证通知方法参数列表中的参数名称和num名称一致execution()用于匹配是连接点的执行方法this()限制连接点匹配AOP代理的bean引用为指定类型的类target限制连接点匹配目标对象为指定类型的类@target()限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解within()限制连接点匹配指定的类型@within()限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义在由指定的注解所标注的类里)@annotation限定匹配带有指定注解的连接点Spring还引入了一个新的bean()指示器,它允许我们在切点表达式中使用bean的ID来标识bean。bean()使用bean ID或bean名称作为参数来限制切点只匹配特定的bean。
例如:
execution(* com.lee.aop.LeeDao. *(..)) and bean("leeDao1")表示限定的bean的ID为leeDao1
使用示例:
execution(* com.lee.aop.LeeDao. *(..)) && within(com.lee.*)
解释:&& 表示逻辑与.还有!,表示非.||表示或.响应的都可以使用and,not,or来代替
within表示限制切点仅仅匹配com.lee包下的
4.3 使用注解创建切面
在类上进行标注:
@Aspect: 代表该类不仅是一个POJO类,还是一个切面
在方法上进行标注:代表他们是通知(增强)
@After通知方法会在目标方法返回或抛出异常后调用@AfterReturning通知方法会在目标方法返回后调用@AfterThrowing通知方法会在目标方法抛出异常后调用@Around通知方法会将目标方法封装起来@Before通知方法会在目标方法调用之前执行@Pointcut定义一个公用的切点
需要注意的是@Around环绕通知的使用方法
关于这个新的通知方法,你首先注意到的可能是它接受ProceedingJoinPoint作为参数。这个对象是必须要有的,因为你要在通知中通过它来调用被通知的方法。通知方法中可以做任何的事情,当要将控制权交给被通知的方法时,它需要调用ProceedingJoinPoint的proceed()方法。
**
通过注解引入新的功能:
一些编程语言,例如Ruby和Groovy,有开放类的理念。它们可以不用直接修改对象或类的定义就能够为对象或类增加新的方法。不过,Java并不是动态语言。一旦类编译完成了,我们就很难再为该类添加新的功了。
实现方法
1 |
|
在xml中配置
1 | <bean id="myApple" class="com.lee.aop.aopjoinnewfunction.Apple"/> |
使用:
1 | public void test1() { |
4.4 在xml中生命切面
略
4.5 注入AspectJ切面
上面的切面是Spring AOP的变体.
虽然Spring AOP能够满足许多应用的切面需求,但是与AspectJ相比,Spring AOP 是一个功能比较弱的AOP解决方案。AspectJ提供了SpringAOP所不能支持的许多类型的切点。
例如,当我们需要在创建对象时应用通知,构造器切点就非常方便。不像某些其他面向对象语言中的构造器,Java构造器不同于其他的正常方法。这使得Spring基于代理的AOP无法把通知应用于对象的创建过程。
对于大部分功能来讲,AspectJ切面与Spring是相互独立的。虽然它们可以织入到任意的Java应用中,这也包括了Spring应用,但是在应用AspectJ切面时几乎不会涉及到Spring。
第10章 通过Spring和JDBC征服数据库
SQLException的问题在于捕获到它的时候该如何处理。事实上,能够触发SQLException的问题通常是不能在catch代码块中解决的。大多数抛出SQLException的情况表明发生了致命性错误。如果应用程序不能连接到数据库,这通常意味着应用不能继续使用了。类似地,如果查询时出现了错误,那在运行时基本上也是无能为力。
SQLException并查看其属性才能获知问题根源的更多信息。这是因为SQLException被视为处理数据访问所有问题的通用异常。对于所有的数据访问问题都会抛出SQLException,而不是对每种可能的问题都会有不同的异常类型。
一方面,JDBC的异常体系过于简单了——实际上,它算不上一个体系。另一方面,Hibernate的异常体系是其本身所独有的。我们需要的数据访问异常要具有描述性而且又与特定的持久化框架无关。
Spring所提供的平台无关的持久化异常
Spring JDBC提供的数据访问异常体系解决了以上的两个问题。不同于JDBC,Spring提供了多个数据访问异常,分别描述了它们抛出时所对应的问题。表10.1对比了Spring的部分数据访问异常以及JDBC所提供的异常。
10.2 访问数据源
10.2.1 使用JNDI配置数据源
(略)
10.2.2 使用数据源连接池
常见的有DBCP,c3p0等,配置上大体相同
10.2.5 使用profile选择数据源
实际上,我们很可能面临这样一种需求,那就是在某种环境下需要其中一种数据源,而在另外的环境中需要不同的数据源。
例如,对于开发期来说,jdbc:embedded-database元素是很合适的,而在QA环境中,你可能希望使用DBCP的BasicDataSource,在生产部署环境下,可能需要使用jee:jndi-lookup。
借助Spring的profile特性能够在运行时选择数据源
10.3 在Spring中使用JDBC
JDBC不要求我们掌握其他框架的查询语言。它是建立在SQL之上的,而SQL本身就是数据访问语言。此外,与其他的技术相比,使用JDBC能够更好地对数据访问的性能进行调优。JDBC允许你使用数据库的所有特性,而这是其他框架不鼓励甚至禁止的。
再者,相对于持久层框架,JDBC能够让我们在更低的层次上处理数据,我们可以完全控制应用程序如何读取和管理数据,包括访问和管理数据库中单独的列。这种细粒度的数据访问方式在很多应用程序中是很方便的。例如在报表应用中,如果将数据组织为对象,而接下来唯一要做的就是将其解包为原始数据,那就没有太大意义了。
理解:很多时候我们先将基本的功能实现了,再考虑优化问题,提高性能等.而使用持久层框架,它们帮我们做了很多封装,会隐藏很多细节,这样有针对性的优化就会付出很多成本.
首先使代码正确的运行,然后再提高代码的速度.
摘自《Java并发编程实战》