Spring复习

Spring 概述

我们学习 Spring 框架的最终目的是用它整合 Struts2、Hibernate 框架(SSH)。

Spring 框架的作用

Spring 框架主要负责技术整合,该框架提供 IOC 和 AOP 机制,可以降低系统组件之间的耦合度,便于系统组件的维护、扩展和替换。

Spring 框架的优点

其实与 Spring 框架的作用相同:
在 SSH 中,主要是利用 Spring 容器管理我们程序中的 Action、DAO 等 Bean 组件,通过容器的 IOC 机制,可以降低 Action、DAO 之间调用的耦合度(关联度),利用 AOP可以降低共通组件和一批目标组件的耦合度,进行事务管理等共通部分的处理。
在 SSH 中,Struts2 主要是利用它的控制器,而不是标签、表达式;Hibernate 主要利用 它的数据库访问;Spring 主要是利用它的整合。

Spring 框架的容器

Spring 框架的核心是提供了一个容器,该容器类 型是ApplicationContext(它是 BeanFactory 的子类,建议用这个类型因为功能更多)。
该容器具有以下功能:
1)容器可以创建和销毁 Bean 组件对象,等价于原来“工厂”类的作用。
2)容器可以采用不同的模式创建对象,如单例模式创建对象。
3)容器具有 IOC 机制实现。
4)容器具有AOP 机制实现。

Spring 容器的基本应用

如何将一个 Bean 组件交给 Spring 容器

1)Bean 组件其实就是个普通的Java 类!
2)方法:在applicationContext.xml 中添加以下组件定义,见 2.6 案例中 step4。

如何获取 Spring 容器对象和 Bean 对象

1)实例化容器:
ApplicationContext ac=new ClassPathXmlApplicationContext("/applicationContext.xml");
//FileSystemXmlApplicationContext("");//去指定的磁盘目录找,上面的为去 Class 路径找
2)利用 getBean("标识符")方法获取容器中的 Bean 对象。见 2.6 案例中 step5。

如何控制对象创建的模式

Spring 支持 singleton(单例)和 prototype(原型,非单例)两种模式。
默认是 singleton 模式,可以通过的 scope 属性修改为 prototype 模式。
以后在 Web程序中,通过扩展可以使用 request、session 等值。见 2.6 案例中 step4、step7。
例如:
注意事项:对于 NetCTOSS 项目,一个请求创建一个 Action,所以用 Spring 时必须 指明 prototype,否则默认使用 singleton 会出问题。而 DAO 则可用 singleton 模式。

Bean 对象创建的时机

1)singleton 模式的 Bean 组件是在容器实例化时创建。
2) 模式是在调用 getBean()方法时创建。
3)singleton 模式可以使用元素的 lazy-init="true"属性将对象的创建时机推迟到调 用 getBean()方法。也可以在(根元素)中使用 default-lazy-init="false"推迟所有单例 Bean 组件的创建时机。见 2.6 案例中 step3、step4。

Spring 框架 IoC 特性

IoC 概念

1)Inverse of Controller 被称为控制反转,其实真正体现的是“控制转移”。
2)所谓的控制指的是负责对象关系的指定、对象创建、初始化和销毁等逻辑。
3)IoC 指的是将控制逻辑交给第三方框架或容器负责(即把 Action 中的控制逻辑提出来, 交给第三方责),当两个组件关系发生改变时,只需要修改框架或容器的配置即可。
4)IoC 主要解决的是两个组件对象调用问题,可以以低耦合方式建立使用关系。

DI 概念

1)Dependency Injection 依赖注入:在容器实例化的时候依赖对象将依赖关系主动注入到对象中.
2)Spring 框架采用 DI 技术实现了 IoC 控制思想。
3)Spring 提供了两种形式的注入方法:

  • setter 方式注入(常用):依靠 set 方法,将组件对象传入(可注入多个对象)。 A.首先添加属性变量和 set 方法。
    B.在该组件的定义中采用下面的描述方式:

     注意事项:例如 CostAction 中有 costDAO 属性,而它的标准 set 方法名为 setCostDAO,那么配置文件中的 name 就应该写 costDAO(去掉 set,首字 母小写)。如果 set 方法名为 setCost,那么 name 就应该写 cost(去掉 set, 首字母小写)!确切的说,name 不是看定义的属性名,而是 set 方法名。
  • 构造方式注入(用的少):依靠构造方法,将组件对象传入。
    A.在需要注入的组件中,添加带参数的构造方法。 B.在该组件的定义中,使用下面格式描述:

AOP 概念

什么是 AOP

Aspect Oriented Programming,被称为面向切面编程。对单个对象(一对一)的解耦用 IOC, 而当有个共通组件,它对应多个其他组件(一对多),则解耦用 AOP。AOP是对OOP的一种补充,而AOP是面向横向的编程。
AOP,可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。
如,拦截器。这也是为何在程序中大量的用 IoC,而AOP却用的很少,因为程序中不可能有很多的共通部分。

AOP 和 OOP 的区别

OOP 是面向对象编程,AOP 是以 OOP 为基础的。
OOP 主要关注的是对象,如何抽象和封装对象。
AOP 主要关注的是方面,方面组件可以以低耦合的方式切入到(作用到)其他某一批目 标对象方法中(类似于 Struts2 中的拦截器)。
AOP 主要解决共通处理和目标组件之间解耦。

AOP 相关术语

1)方面(Aspect):指的是封装了共通处理的功能组件。该组件可以作用到某一批目标 组件的方法上。
2)连接点(JoinPoint):指的是方面组件和具体的哪一个目标组件的方法有关系。
3)切入点(Pointcut):用于指定目标组件的表达式。指的是方面组件和哪一批目标组件 方法有关系。多个连接点组成的集合就是切入点。如:a、b 为切入点,1、2 为连接点。
a方面组件
b
1HibernateCostDAO
2
1JdbcCostDAO
2save() delete()
save()
delete()
4)通知(Advice):用于指定方面组件和目标组件方法之间的作用时机。例如:先执行 方面组件再执行目标方法;或先执行目标方法再执行方面组件。
5)目标(Target):利用切入点指定的组件和方法。
6)动态代理(AutoProxy):Spring 同样采用了动态代理技术实现了 AOP 机制。当使用 AOP 之后,从容器 getBean()获取的目标组件,返回的是一个动态生成的代理类。然后通过代 理类执行业务方法,代理类负责调用方面组件功能和原目标组件功能。

Spring 提供了下面两种动态代理技术实现:

1)采用 CGLIB 技术实现(目标组件没有接口采用此方法)
例如:public class 代理类 extends 原目标类型 { }
CostAction action=new 代理类();//代理类中有原来类的方法
2)采用 JDK Proxy API 实现(目标组件有接口采用此方法,即实现了某个接口)
例如:Public class 代理类 implements 原目标接口 { }
CostDAO costDAO=new 代理类();//代理类去实现了原目标接口,所以没
有原来类的方法

案例:AOP 的使用,模拟某些组件需要记录日志的功能

接 3.3、3.4 案例,想让所有的操作进行日志记录,那么按以前的方式就需要给所有 Action 或 DAO 中添加记录日志的代码,如果 Action 或 DAO 很多,那么不便于维护。而使用 AOP 机制,则可以很方便的实现上述功能:
step1:导入 AOP 需要的包:aopalliance.jar、aspectjrt.jar、aspectjweaver.jar、cglib-nodep-2.1_3.jar
step2:在 org.tarena.aop 包下新建 LoggerBean 类,并添加 logger 方法用于模拟记录日志功能
public void logger(){
System.out.println("记录了用户的操作日志"); }
step3:在 applicationContext.xml 配置文件中,添加 AOP 机制







step4:执行 3.3 案例 step3,则发现添加操作已有了记录日志功能
创建 CostDAO 对象 初始化 CostDAO 对象 记录了用户的操作日志 处理资费添加操作 利用 JDBC 技术实现保存资费记录
step5:执行 3.4 案例 step3,则发现删除操作已有了记录日志功能,记得加无参构造方法!
记录了用户的操作日志 处理资费删除操作 利用 Hibernate 技术实现删除资费记录
注意事项:DeleteAction 用的是构造注入,所以此处要把无参构造器再加上!因为 AOP底层调用了 DeleteAction的无参构造方法。不加则报错:Superclass has no null constructors but no arguments were given

通知类型 通知决定方面组件和目标组件作用的关系。

主要有以下几种类型通知:
1)前置通知:方面组件在目标方法之前执行。
2)后置通知:方面组件在目标方法之后执行,目标方法没有抛出异常才执行方面组件。
3)最终通知:方面组件在目标方法之后执行,目标方法有没有异常都会执行方面组件。
4)异常通知:方面组件在目标方法抛出异常后才执行。
5)环绕通知:方面组件在目标方法之前和之后执行。
try{ //前置通知执行时机
//执行目标方法
//后置通知执行时机
}catch(Exception e){//异常通知执行时机

}finally{ //最终通知执行时机
}//环绕通知等价于前置+后置

切入点

切入点用于指定目标组件和方法,Spring 提供了多种表达式写法:
1)方法限定表达式:指定哪些方法启用方面组件。
①形式:execution(修饰符? 返回类型 方法名(参数列表) throws 异常?)
②示例:
execution(public * * (..)),匹配容器中,所有修饰符是 public(不写则是无要求 的),返回类型、方法名都没要求,参数列表也不要求的方法。
execution(* set(..)),匹配容器中,方法以 set 开头的所有方法。 execution( org.tarena.CostDAO.(..)),匹配 CostDAO 类中的所有方法。 execution( org.tarena.dao..(..)),匹配 dao 包下所有类所有方法。
execution(* org.tarena.dao...(..)),匹配 dao 包及其子包中所有类所有方法。
2)类型限定表达式:指定哪些类型的组件的所有方法启用方面组件(默认就是所有方法 都启用,且知道类型,不到方法)。
①形式:within(类型) ②示例:
within(com.xyz.service.),匹配 service 包下的所有类所有方法 within(com.xyz.service..),匹配 service 包及其子包中的所有类所有方法 within(org.tarena.dao.CostDAO),匹配 CostDAO 所有方法
 注意事项:within(com.xyz.service...),为错误的,就到方法名!
3)Bean 名称限定:按元素的 id 值进行匹配。
①形式:Bean(id 值) ②示例:
bean(costDAO),匹配 id=costDAO 的 bean 对象。
bean(*DAO),匹配所有 id 值以 DAO 结尾的 bean 对象。
4)args 参数限定表达式:按方法参数类型限定匹配。
①形式:args(类型) ②示例:
args(java.io.Serializable),匹配方法只有一个参数,并且类型符合 Serializable 的 方法,public void f1(String s)、public void f2(int i)都能匹配。
 注意事项:上述表达式可以使用&&、| | 运算符连接使用。

案例:环绕通知,修改 5.4 案例使之动态显示所执行的操作

step1:新建 opt.properties 文件,自定义格式:包名.类名.方法名=操作名。在高版本 MyEclipse 中,切换到 Properties 界面,点击 Add 直接输入键和值,则中文会自动转为 ASCII 码。低版 本的则需要使用 JDK 自带的转换工具:native2ascii.exe

第一个为资费添加,第二个为资费删除 org.tarena.action.CostAction.execute=\u8D44\u8D39\u6DFB\u52A0

org.tarena.action.DeleteAction=\u8D44\u8D39\u5220\u9664 step2:新建 PropertiesUtil 工具类,用于解析.properties 文件
private static Properties props = new Properties();
static{ try{ props.load(PropertiesUtil.class.getClassLoader()
.getResourceAsStream("opt.properties"));
}catch(Exception ex){ ex.printStackTrace(); } } public static String getValue(String key){

String value = props.getProperty(key);
if(value == null){ return ""; }else{ return value; } } step3:使用环绕通知,将 5.4 案例 step3 中的标签换为
step4:修改 5.4 案例 step2 中的 LoggerBean 类
public Object logger(ProceedingJoinPoint pjp) throws Throwable{//采用环绕通知,加参数
//前置逻辑
String className=pjp.getTarget().getClass().getName();//获取要执行的目标组件类名 String methodName=pjp.getSignature().getName();//获取要执行的方法名
//根据类名和方法名,给用户提示具体操作信息
String key=className+"."+methodName; System.out.println(key);
//解析 opt.properties,根据 key 获取 value String value=PropertiesUtil.getValue(key);
//XXX 用户名可以通过 ActionContext.getSession 获取
System.out.println("XXX 执行了"+value+"操作!操作时间:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(
new Date(System.currentTimeMillis()))); Object obj=pjp.proceed();//执行目标方法
//后置逻辑
return obj; }
step5:分别执行 3.3 案例 step3 和 3.4 案例 step3,执行结果动态显示所执行的操作及时间 XXX 执行了资费添加操作!操作时间:2013-08-19 20:14:47
XXX 执行了资费删除操作!操作时间:2013-08-19 20:15:45

案例:利用 AOP 实现异常处理,将异常信息写入文件

1)分析:方面:将异常写入文件。切入点:作用到所有 Action 业务方法上
within(org.tarena.action..)。通知:异常通知。 2)实现:step1:在 org.tarena.aop 包中创建 ExceptionBean 类
public class ExceptionBean {//模拟,将异常信息写入文件
public void exec(Exception ex){//ex 代表目标方法抛出的异常
System.out.println("将异常记录文件"+ex); //记录异常信息 } } step2:在 applicationContext.xml 配置文件中进行配置





step3:在 DeleteAction 的 execute 方法中添加异常
String str=null; str.length();
step4:执行 3.3 案例 step3 则添加操作执行正常;执行 3.4 案例 step3 则删除操作报 空指针异常!显示结果:将异常记录文件 java.lang.NullPointerException

你可能感兴趣的:(Spring复习)