使用AOP

(1)AOP是Aspect Oriented Programming,即面向切面编程。我们先回顾一下OOP:Object Oriented Programming,OOP作为面向对象编程的模式,获得了巨大的成功,OOP的主要功能时数据封装、继承和多态。而AOP是一种新的编程模式,它与OOP不同,OOP把系统看做多个对象的交互,AOP把系统分解为不同的关注点,或者称之为切面(Aspect)。
(2)要理解AOP的概念,我们先用OOP举例,比如一个业务组件BookService,它有几个业务方法,对于每个业务方法,除了业务逻辑,还需要安全检查、日志记录和事务处理。对于安全检查、日志、事务等代码,它们会重复地出现在每个业务方法中、使用OOP,我们很难将这些四散的代码模块化。考察业务模型可以发现,Service关心的是自身的核心逻辑,但整个系统还要求关注安全检查、日志、事务等功能,这些功能实际上横跨多个业务方法,为了实现这些功能,不得不在每个业务方法上重复编写代码。
(3)一种可行的方式是使用Proxy模式,将某个功能,例如,权限检查,放入Proxy中,这种方式的缺点是比较麻烦,必须先抽取接口,然后针对每个方法实现Proxy。
另一种方法是,既然SecurityCheckBookService的代码都是标准的Proxy样板代码,不如把权限检查视作一种切面(Aspect),把日志、事务也视为切面,然后,以某种自动化的方式,把切面植入到核心逻辑中,实现Proxy模式。如果我们以AOP的视角来编写上述业务,可以依次实现:
一、核心逻辑,即BookService
二、切面逻辑,即:权限检查的Aspect,日志的Aspect,事务的Aspect
然后以某种方式,让框架把上述三个Aspect以Proxy的方式植入到BookService中,这样一来,就不必编写复杂而冗长的Proxy模式。

AOP原理

(1)如何把切面植入到核心逻辑中,这正是AOP要解决的问题,换句话说,如果客户端获得了BookService的引用,当调用bookService.createBook()时,如何对调用方法进行拦截,并且在拦截前后进行安全检查、日志、事务等逻辑,就相当于完成了所有业务功能。
(2)在Java平台上,对于AOP的植入,有三种方式:
一、编译器:在编译时,由编译器把切面调用编译进字节码,这种方式需要定义新的关键字并扩展编译器,AspectJ就扩展了Java编译器,使用关键字aspect来实现植入。
二、类加载器:在目标类被装载到JVM时,通过一个特殊的类加载器,对目标类的字节码重新增强。
三、运行期:目标对象和切面都是普通java类,通过JVM的动态代理功能或者第三方库实现运行期动态植入。
其中最简单的方式是第三种,Spring的AOP实现就是基于JVM的动态代理。由于JVM的动态代理要求必须实现接口,若一个普通类没有业务接口,就需要通过CGLIB或者Javassis这些第三方库。
(4)AOP技术看上去比较神秘,实际上,它本质就是一个动态代理,让我们把一些常用功能如权限检查、日志、事务等,从各个业务方法中剥离出来。需要特别指出的是,AOP对于解决特定的问题,例如书屋管理非常有用,这是因为分散在各处的事务代码几乎是完全相同的,并且它们需要的参数(JSBC的Connetion)也是固定的。另一些特定问题,如日志,就不那么容易实现,因为日志虽然简单,但打印日志的时候,经常需要捕获局部变量,如果使用AOP实现日志,我们只能输出固定格式的日志,因此,使用AOP时,必须适合特定的场景。

装配AOP

(1)在AOP编程中,我们经常会遇到下面的概念:
Aspect:切面,即一个横跨多个核心逻辑的功能,或者称之为系统关注点;
Joinpoint:连接点,即定义在应用程序流程的何处插入切面的执行;
Pointcut:切入点,即一组连接点的集合;
Advice:增强,指特定连接点上执行的动作。
Introduction:指为一个已有的Java对象动态地增加新的接口。
Weaving:织入,指将切面整合到程序的执行流程中;
Interceptor:拦截器,是一种实现增强的方式。
Target Object:目标对象,即真正执行业务的核心逻辑对象;
AOP Proxy: AOP代理,是客户端持有的增强后的对象引用。
(2)其实,我们不用关心AOP创造的术语,只需要理解AOP本质上只是一种代理模式的实现方式,在Spring的容器中实现AOP非常方便。Spring对接口类型使用JDK动态代理,对普通类使用CGLIB创建子类,如果一个Bean(组件)的class是final,Spring将无法为其创建子类。
(3)可见,虽然Spring的容器内部实现AOP的逻辑十分复杂(需要使用ASpectJ解析注解,并通过CGLIB实现代理类),但我们使用AOP非常简单,一共需要三步:
一、定义执行方法,并在方法上通过Aspect的注解告诉Spring应该在何处调用此方法。
二、标记@Compnent和@Aspect;
三、在@Configration类上标注@EnableAspectAutoProxy。
(4)拦截器类型
一、@Before:这种拦截器先执行拦截代码,再执行目标代码。如果拦截器抛出异常,那么目标代码就不执行了。
二、@After:这种拦截器先执行目标代码,再执行拦截器代码。无论目标代码是否排除异常,拦截器代码都会执行。
三、@AfterReturning:和@After不同的是,只有当目标代码正常返回时,才执行拦截器代码。
四、@AfterThrowing:和@After不同的是,,只有当目标代码抛出异常后,才会执行拦截器代码。
五、@Around:能完全控制目标代码是否执行,并可以再执行前后、抛异常后任意拦截代码,可以说是包含了上面所有的功能。

使用注解装配AOP

(1)我们在使用AOP时,要注意到虽然Spring容器可以把指定的方法通过AOP规则装配到指定的Bean的指定方法前后,但是,如果自动装配时,因为不恰当的范围,容易导致意想不到的结果,即很多不需要AOP代理的Bean也被自动代理了,并且,后续新增的Bean,如果不清楚现有的AOP装配规则,容易被强迫分配。
(2)使用AOP时,被装配的Bean最好能自己清清楚楚地知道自己被安排了,例如,Spring提供的@Transactional就是一个非常好的例子,如果我们自己写的Bean希望在一个数据库事务中被调用,就标注上@Transactional。通过@Transactional,某个方法是否启用了事务就一清二楚了,因此,装配AOP的时候,使用注解是最好的方式。

AOP避坑指南

(1)无论是使用AspectJ语法,还是配合Annotation,使用AOP,实际上就是让Spring自动为我们创建一个Proxy,使得调用方能无感知地调用指定方法,但运行期间却动态地植入了其他逻辑,因此,AOP本质上就是一个代理模式。
(2)正确地使用Bean时,我们需要一个避坑指南:
访问被注入的Bean时,总是调用方法而非直接使用字段;
编写Bean时,如果可能会被代理,就不要编写public final方法。
这样才能保证有没有AOP,代码都能正常工作。

使用JDBC

(1)Java程序使用JDBC接口访问关系数据库的时候,需要一下几步:
创建全局DataSource实例,表示数据库连接池;
在需要读写数据库的方法内部,按照如下步骤访问数据库:
从全局DataSource实例获取Connection实例;
通过Connection实例创建PreparedStatement实例;
执行SQL语句,如果是查询,则通过ResultSet读取结果集,如果是修改,则获得int结果。

正确编写JDBC代码的关键是使用try…finally释放资源,涉及到事务的代码需要正确提交或者回滚事务。

(2)在Spring使用JDBC,首先我们通过IOC容器创建并管理一个DataSource实例,然后,Spring提供了一个JdbcTemplate,可以方便地让我们操作JDBC,因此,通常情况下我们会jdbcTemplate。
(3)Spring提供的JdbcTemplate采用Template模式,提供了一系列以回调为特点的工具方法,目的是避免繁琐的try…catch语句。JdbcTemplate只是对JDBC操作的一个简单封装,它的目的是尽量减少手动编写try(resource){…}的代码,对于查询,主要通过RowMapper实现了JDBC结果到Java对象的转换。
(4)总结JdbcTemplate的用法,就是:
针对简单查询,优选query()和queryForObject(),因为只需要提供SQL语句、参数和RowMapper;
针对更新操作,优选update(),因为只需要提供SQL语句和参数。
任何复杂的操作,也可以通过Execute(ConnectionCallback)实现,因为只要拿到Connection就可以做任何JDBC操作。实际上我们使用最多的仍然是各种查询,如果在设计表结构的时候,能够和JavaBean的属性一一对应,那么直接使用BeanPropertyRowMapper就很方便,如果表结构和JavaBean不一致时,稍微修改一下查询,使结果集的结构和JavaBean保持一致。