目 录
2.6案例:Spring框架的使用以及2.1节-2.5节整合测试 3
3.5案例:不用JDBC访问数据库,而是采用Hibernate访问 6
5.4案例:AOP的使用,模拟某些组件需要记录日志的功能 11
5.7案例:环绕通知,修改5.4案例使之动态显示所执行的操作 12
5.8案例:利用AOP实现异常处理,将异常信息写入文件 13
8.6 Spring框架如何使用Hibernate技术 22
8.7 Spring+Hibernate如何使用Session、Query等对象 25
九、 整合开发包struts-spring-plugin.jar 31
9.2 struts-spring-pligin.jar创建对象的方式 31
9.3 struts-spring-plugin.jar的内部实现 31
10.2编程式事务管理(基于Java编程实现事务控制),不推荐用! 34
11.4案例:修改11.3案例(基于注解配置,推荐使用) 37
一、Spring概述
我们学习Spring框架的最终目的是用它整合Struts2、Hibernate框架(SSH)。
1.1 Spring框架的作用
Spring框架主要负责技术整合(可以整合很多技术),该框架提供IoC和AOP机制,基于这些特性整合,可以降低系统组件之间的耦合度,便于系统组件的维护、扩展和替换。
1.2 Spring框架的优点
其实与Spring框架的作用相同:
在SSH中,主要是利用Spring容器管理我们程序中的Action、DAO等组件,通过容器的IoC机制,可以降低Action、DAO之间的耦合度(关联度),利用AOP进行事务管理等共通部分的处理。
在SSH中,Struts2主要是利用它的控制器,而不是标签、表达式;Hibernate主要利用它的数据库访问;Spring主要是利用它的整合。
1.3 Spring框架的容器
Spring框架的核心是提供了一个容器(是我们抽象出来的,代指后面的类型)。该容器类型是BeanFactory或ApplicationContext(建议用这个类型,它是BeanFactory的子类,功能更多)。
该容器具有以下功能:
1)容器可以创建和销毁组件对象,等价于原来“工厂”类的作用。
2)容器可以采用不同的模式创建对象,如单例模式创建对象。
3)容器具有IoC机制实现。
4)容器具有AOP机制实现。
二、Spring容器的基本应用
2.1如何将一个Bean组件交给Spring容器
1)Bean组件其实就是个普通的Java类!
2)方法:在applicationContext.xml中添加以下定义,见2.6案例中step4。
<bean id="标识符" class="Bean组件类型"></bean>
2.2如何获取Spring容器对象和Bean对象
1)实例化容器:
ApplicationContext ac=new ClassPathXmlApplicationContext("/applicationContext.xml");
//FileSystemXmlApplicationContext("");//去指定的磁盘目录找,上面的为去Class路径找
2)利用getBean("标识符")方法获取容器中的Bean对象。见2.6案例中step5。
2.3如何控制对象创建的模式
Spring支持singleton(单例)和prototype(原型,非单例)两种模式。
默认是singleton模式,可以通过<bean>的scope属性修改为prototype模式。以后在Web程序中,通过扩展可以使用request、session等值。见2.6案例中step4、step7。
例如:<bean id="标识符" scope="prototype" class="Bean组件类型"></bean>
u 注意事项:对于NetCTOSS项目,一个请求创建一个Action,所以用Spring时必须指明prototype,否则默认使用singleton会出问题。而DAO则可用singleton模式。
2.4 Bean对象创建的时机
1)singleton模式的Bean组件是在容器实例化时创建。
2)prototype模式是在调用getBean()方法时创建。
3)singleton模式可以使用<bean>元素的lazy-init="true"属性将对象的创建时机推迟到调用getBean()方法。也可以在<beans>(根元素)中使用default-lazy-init="false"推迟所有单例Bean组件的创建时机。见2.6案例中step3、step4。
例如:<bean id="标识符" scope="singleton" lazy-init="true" class="Bean组件类型"></bean>
<beans ...... default-lazy-init="false"></beans>
2.5为Bean对象执行初始化和销毁方法
1)初始化:①可以利用<bean>元素的init-method="方法名"属性指定初始化方法。
②指定的初始化方法是在构造方法调用后自动执行。若非单例模式,则每创建一个对象,则执行一次初始化方法(单例、非单例模式都可)。见2.6案例中step3、step4。
u 注意事项:
v 初始化的三种方式:写构造方法中;或写{ }中(代码块);Spring框架中<bean>元素写init-method="方法名"属性。
v 初始化不能用static{ },它是类加载调用,比创建对象要早。
2)销毁:①可以利用<bean>元素的destroy-method="方法名"属性执行销毁方法。
②指定的销毁方法是在容器关闭时触发,而且只适用于singleton模式的组件(只能为单例模式)。见2.6案例中step3、step4、step6。
2.6案例:Spring框架的使用以及2.1节-2.5节整合测试
step1:导入Spring开发包:spring.jar、commons-logging.jar和配置文件:applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd">
</beans>
step2:在org.tarena.dao包下,创建接口CostDAO,添加两个方法
public void delete(); public void save();
step3:在org.tarena.dao包下,创建JdbcCostDAO类,并实现CostDAO接口
public JdbcCostDAO(){ System.out.println("创建CostDAO对象"); }
public void myinit(){ System.out.println("初始化CostDAO对象"); }
public void mydestroy(){ System.out.println("销毁CostDAO对象"); }
public void delete() { System.out.println("利用JDBC技术实现删除资费记录"); }
public void save() { System.out.println("利用JDBC技术实现保存资费记录"); }
step4:在applicationContext.xml配置文件中,将Bean组件(Java类)交给Spring容器
<bean id="jdbcCostDAO" scope="singleton" lazy-init="true" init-method="myinit"
destroy-method="mydestroy" class="org.tarena.dao.JdbcCostDAO">
</bean>
step5:在org.tarena.test包下,创建TestApplicationContext类,获取Spring容器对象,并测试
@Test
public void test1(){ String conf="/applicationContext.xml";
ApplicationContext ac=new ClassPathXmlApplicationContext(conf);//实例化容器
CostDAO costDAO=(CostDAO)ac.getBean("jdbcCostDAO");//获取Bean对象
costDAO.save(); costDAO.delete(); }
step6:在TestApplicationContext类中添加方法,测试销毁对象
@Test
/**关闭容器才会触发销毁,但关闭容器方法封装在AbstractApplicationContext类中 */
public void test2(){ String conf="/applicationContext.xml";
ApplicationContext ac=new ClassPathXmlApplicationContext(conf);
AbstractApplicationContext ac=new ClassPathXmlApplicationContext(conf);
CostDAO costDAO=(CostDAO)ac.getBean("jdbcCostDAO"); ac.close(); }
step7:在TestApplicationContext类中添加方法,测试单例
@Test
public void test3(){ String conf="/applicationContext.xml";
ApplicationContext ac= new ClassPathXmlApplicationContext(conf);
CostDAO costDAO1=(CostDAO)ac.getBean("jdbcCostDAO");
CostDAO costDAO2=(CostDAO)ac.getBean("jdbcCostDAO");
System.out.println(costDAO1==costDAO2);//true,所以Spring默认为单例模式 }
……………………
………………
…………
五、AOP概念
5.1什么是AOP
Aspect Oriented Programming,被称为面向方面编程。对单个对象(一对一)的解耦用IOC,而当有个共通组件,它对应多个其他组件(一对多),则解耦用AOP。如,拦截器。这也是为何在程序中大量的用IoC,而AOP却用的很少,因为程序中不可能有很多的共通部分。
5.2 AOP和OOP的区别
OOP是面向对象编程,AOP是以OOP为基础的。
OOP主要关注的是对象,如何抽象和封装对象。
AOP主要关注的是方面,方面组件可以以低耦合的方式切入到(作用到)其他某一批目标对象方法中(类似于Struts2中的拦截器)。
AOP主要解决共通处理和目标组件之间解耦。
5.3 AOP相关术语
1)方面(Aspect):指的是封装了共通处理的功能组件。该组件可以作用到某一批目标组件的方法上。
2)连接点(JoinPoint):指的是方面组件和具体的哪一个目标组件的方法有关系。
3)切入点(Pointcut):用于指定目标组件的表达式。指的是方面组件和哪一批目标组件方法有关系。多个连接点组成的集合就是切入点。如:a、b为切入点,1、2为连接点。
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 代理类();//代理类去实现了原目标接口,所以没有原来类的方法
5.4案例: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机制
<bean id="loggerBean" class="org.tarena.aop.LoggerBean"></bean>
<aop:config>
<!--定义切入点,指定目标组件和方法。id:可任意起个名字。expression:指定哪些组件是目标,并作用在这些目标的方法上。下面表示所有Action中的所有方法为切入点-->
<aop:pointcut id="actionPointcut" expression="within(org.tarena.action.*)" />
<!--定义方面,将loggerBean对象指定为方面组件,loggerBean从普通Bean组件升级为了方面组件-->
<aop:aspect id="loggerAspect" ref="loggerBean">
<!-- aop:before在操作前执行 aop:after操作后执行 -->
<!-- 定义通知,aop:before:指定先执行方面组件的logger方法,再执行切入点指定的目标方法。aop:after:与aop:before相反 -->
<aop:before pointcut-ref="actionPointcut" method="logger"/>
</aop:aspect>
</aop:config>
step4:执行3.3案例step3,则发现添加操作已有了记录日志功能
创建CostDAO对象 初始化CostDAO对象 记录了用户的操作日志
处理资费添加操作 利用JDBC技术实现保存资费记录
step5:执行3.4案例step3,则发现删除操作已有了记录日志功能,记得加无参构造方法!
记录了用户的操作日志 处理资费删除操作
利用Hibernate技术实现删除资费记录
u 注意事项:DeleteAction用的是构造注入,所以此处要把无参构造器再加上!因为AOP底层调用了DeleteAction的无参构造方法。不加则报错:Superclass has no null constructors but no arguments were given
5.5通知类型
通知决定方面组件和目标组件作用的关系。主要有以下几种类型通知:
1)前置通知:方面组件在目标方法之前执行。
2)后置通知:方面组件在目标方法之后执行,目标方法没有抛出异常才执行方面组件。
3)最终通知:方面组件在目标方法之后执行,目标方法有没有异常都会执行方面组件。
4)异常通知:方面组件在目标方法抛出异常后才执行。
5)环绕通知:方面组件在目标方法之前和之后执行。
try{ //前置通知执行时机<aop:before>
//执行目标方法
//后置通知执行时机<aop:after-returning>
}catch(Exception e){//异常通知执行时机<aop:after-throwing>
}finally{ //最终通知执行时机<aop:after>
}//环绕通知等价于前置+后置<aop:around>
5.6切入点
切入点用于指定目标组件和方法,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所有方法
u 注意事项:within(com.xyz.service..*.*),为错误的,就到方法名!
3)Bean名称限定:按<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)都能匹配。
u 注意事项:上述表达式可以使用&&、| | 运算符连接使用。
5.7案例:环绕通知,修改5.4案例使之动态显示所执行的操作
………………
…………
……
…