admin 管理员组

文章数量: 887021


2024年1月16日发(作者:java计时器代码)

一、理论知识

1.依赖注入、控制反转

依赖注入:在运行期,由外部容器动态地将依赖对象注入到组件中

控制反转:应用本身不负责依赖对象的创建及维护,依赖对象的创建及维护是由外部窗口负

责得。这样控制权就由应用转移到了外部容器,控制权的转移就是所谓的反转。

的主要特性。

(1)降低组件之间的耦合度,实现软件各层之间的解耦。

(2)可以使用容器提供的众多服务,如:事务管理服务、消息服务、JMS 服务、持久化服

务等等。

(3)容器提供单例模式支持,开发人员不再需要自己编写实现代码。

(4)容器提供了AOP 技术,利用它很容易实现如权限拦截,运行期监控等功能。

(5)容器提供的众多辅作类,使用这些类能够加快应用的开发,如:JdbcTemplate、

HibernateTemplate.

(6)对主流的应用框架提供了集成支持。

3.常用技术

控制反转/依赖注入---面向切入编程---与主流框架的整合、管理---

二、基本实例

1.准备搭建环境

如果使用了切面编程,还需下列jar 文件:

如果使用了jsr-250 中的注解,还需要下列jar 文件:

2.搭建并测试环境

建立名为spring_01_base项目,根据需求导入jar包。建立一个Junit测试单元

SpringEnvTest,测试代码如下:

@Test

public void testEnv() {

ApplicationContext ctx = new

ClassPathXmlApplicationContext("");

}

配置文件在此省略(见下)。运行此测试如无错,则说明环境搭建成功。

说明: 可以在类路径下进行配置,也可以在具体的目录下配置。可以是一个配置文

件,也可以是多个配置文件组成String 数组传入。

3.实例

作如下准备工作:(1)建立UseDao接口,代码如下:

;

public interface UserDao {

voidsave();

}

(2)建立UserDao接口的实现类,UserDaoImpl

;

o;

public class UserDaoImpl implements UserDao{

public void save() {

n("执行save方法...");

}

}

(3)在src目录下配置此,它的配置如下:

xmlns:xsi="/2001/XMLSchema-instance"

xsi:schemaLocation="/schema/beans

/schema/beans/">

说明:bean 代表一个实质的java 类,通过它的id 可以获取一个此类的一个对象。

补充:让xml 配置文件在编译时提示

[windows][preferences][myeclipse][files and editors][xml][xml catalog] 点add,

在出现窗口的location 中选“file system”,然后在spring 解压目录的dist/resources

目录中选择“”,并将key Type 值改为“Schema Location”,key 值

为:/schema/beans/

(4)Junit 测试单元SpringEnvTest 中增加如下代码测试:

@Test

public void base() {

ApplicationContext ctx = new

ClassPathXmlApplicationContext("");

UserDao userDao = (UserDao) n("userDaoImpl");

();

}

以上的代码就是通过配置文件 获取所需要的实例对象。

4.三种bean 的生成方式

除了上面使用的类直接生成方式,还有bean 静态工厂及bean 实例工厂。

bean 静态工厂的配置如下:

factory-method="getUserDaoImpl"/>

相应的工厂类代码如下:

;

public class UserDaoImplFactory {

public static UserDaoImpl getUserDaoImpl(){

return new UserDaoImpl();

}

}

bean实例工厂的配置如下:

factory-method="getUserDaoImpl"/>

相应的工厂类的代码如下:

;

public class UserDaoImplFactory2 {

publicUserDaoImpl getUserDaoImpl() {

return new UserDaoImpl();

}

}

的作用域

singleton:返回bean 的同一个实例,也是默认的作用域(无状态bean使用此作用域)

prototype:每次请求都会创建一个实例(有状态bean 使用此作用域)

request、session、global session 这三个作用域主要用在web 应用中

的生命周期

(1)什么时候初始化bean 实例

当scope=singleton,即默认情况,会在装载配置文件时实例化。如果希望在调用getBean

时才初始化,可以使用lazy-init="true" 补充:如果希望希望该配置文件中的所有bean

都延迟初始化,则应在beans根结点中使用lazy-init="true"。

当scope=prototype 时,在调用getBean()方法时才会初始化。

(2)生命周期:

构造器、init 方法、获取bean 后的操作、destroy 方法(、注意如果bean 的scope

设为prototype 时,当 时,destroy 方法不会被调用)

7.属性注入Setter 方式

(1)简单属性(如String):

(2)对象属性-外部bean 注入:在上面的中增加如下配置:

对象属性-内部bean注入:在上面的中增加如下配置:

(3)集合属性注入:

List值一

List值二

List值三

Set值二

Set值一

Set值三

first

second

third

注意:在相应的字段上一定要有setter 方法,才能注入。

补充:使用继承。在中的配置如下:

相当于在XXX bean 实例中也有username 属性设置。

8.属性注入构造器方式

List值一

List值二

List值三

UserServiceBean对应的构造方法代码如下:

publicUserServiceBean(String username, UserDao userDao, Set set)

{

me=username;

o=userDao;

=set;

}

注意:此方法会覆盖掉默认的构造方法,导致要依赖默认构造方法的配置不可用,因此我们还应

为此类提供一个默认的构造器。

三、使用注解方式注入

1.准备

注解方式的注入主要针对对象属性的注入。

使用注解功能要引用注解包,另的配置模板如下:

xmlns:xsi="/2001/XMLSchema-instance"

xmlns:context="/schema/context"

xsi:schemaLocation="/schema/beans

/schema/beans/

/schema/context

/schema/context/"

>

ce 注解实例

拷贝上一个项目为spring_02_annotation 项目,修改UserServiceBean 为如下形式:

e;

public class UserServiceBean {

@Resource(name = "userDaoImpl")

privateUserDao userDao;

privateUserDao userDao2;

@Resource

public void setUserDao2(UserDao userDao2) {

o2 = userDao2;

}

public void test() {

();

();

}

}

然后在 中的配置如下:

简要说明:Resouce注解可以在字段上标记,也可以在对应的setter方法上标记。此注解可

以不使用name属性,它会自动去查找匹配的类型(先以字段名称为name的值查找,如找不到

会再根据依赖对象的类型查找)。但只要使用了name属性,就应确保name的值在xml中有相应

的bean Id对应。它是属于java本身的注解,Resource默认按属性名称装配

red注解实例

@Autowired(required=false)

@Qualifier("userDaoImplXXX")

privateUserDao userDao3;

说明:Autowired 默认是按照类型来查找对应的bean 实例注入,如果想注入指定名称的bean

实例,可以使用Qualifier 注解来指定名字。Required 属性设为true 时,如果不能成功注

入则会报告异常,如果为设为false 而不能成功注入,则会将userDao3 设为null。同样地,

它也实用于setter 方法。它属于spring 特有的注解,Autowired 默认按类型装配。

4.自动装配

自动装配(了解,不建议使用):除了要设置字段的setter方法外,还应在配置文

件中设置如下内容:

class="rviceBean2" autowire="byType"/>

说明:除了byType外,autowire的可选属性如下:

byName:根据类中的字段名来查找对应的bean,如不能成功注入,则字段设为null.

byType:根据类型装配,如果发现多个类型都能够匹配,则抛出异常。

Consturctor:也byType相似,不同之处在于它应用于构造器的参数,如果容器中没有找到

与构造器参数类型一致的bean,则抛出异常。

Autodetect:通过bean类的自省机制来决定是使用consturctor还是byType方式进行自动装

配。如果发现默认的构造器,那么将使用byType方式。

四、自动扫描管理bean

1.准备工作

在前面使用注解的时候,除了结点配置增加了名称空间说明,另还增加了

配置,它的作用就是注册一个处理器。通常情况下,我

们要使用某个bean实例,总会配置相关内容。Spring最新版本可以简化这一操作,即

是说我们只要在配置文件作如下设置:

便可以自动管理指定包名及子包

下标住了@service (业务层组件)、@controller(控制层组件)、@repository(数据访问

组件)或@component(泛指组件)的类,并把它们作一个实例bean,相当于在中配

置了元素。需要说明的是,使用了此配置同时意味着还注册了注解配置的处理器,所

以在这些类中用到了注解配置时并不需要再配置

为什么提出自动扫描管理:在一些比较大的项目中,涉及到的bean实例会有很多,如果依次

对每个bean实例进行配置,不但配置内容繁琐,而且配置文件也会显得杂乱。因此spring

提出了自动扫描bean,它依据在xml文件中指定的包名和类中标记的component系列注解。

2.实例

建立spring_03_autoscan项目,内容基本和前面两个项目一样,只是在要纳入spring管理

的类前增加了component这样的注解。的配置如下:

xmlns:xsi="/2001/XMLSchema-instance"

xmlns:context="/schema/context"

xsi:schemaLocation="/schema/beans

/schema/beans/

/schema/context

/schema/context/"

>

说明:以上的配置会自动管理service 包和impl 包及它们子包下的带有component 标记的

类,上面两行配置代码等价于

下面以UserServiceBean 为例进行说明,代码如下:

e;

@Service("usb")@Scope("singleton")

public class UserServiceBean {

@Resource(name = "userDaoImpl")

privateUserDao userDao;

privateUserDao userDao2;

@Autowired(required = true)

@Qualifier("userDaoImpl")

privateUserDao userDao3;

@Resource

public void setUserDao2(UserDao userDao2) {

o2 = userDao2;

}

publicUserServiceBean() {

}

@PostConstruct

public void init() {

n("init method is called");

}

public void test() {

n("********************************");

();

();

n(userDao3);

// ();

n("********************************");

}

}

说明:如果使用Service这些注解时不指定名称,这些实例bean的名称就是类名(但首字母

小写),也可以指定实例bean的名字,比如这里指定其名字为“usb”,scope注解配置了bean

的作用范围,PostConstruct注解指定了bean的init方法。关于其它的一些注解配置参文

档(3.11-3.12)。

它的junit测试代码如下:

public class AutoScanTest {

@Test

public void base() {

ApplicationContext ctx = new

ClassPathXmlApplicationContext("");

UserServiceBean udb = (UserServiceBean) n("usb");

();

}

}

小结使用自动扫描管理的核心:配置扫描的包、类前的component标记、了解常用注解。

五、AOP 技术

1.引出问题

建立spring_04_aop项目,在该项目下有一个UserDao接口,代码如下:

;

public interface UserDao {

voidsave();

voidupdate();

}

该接口的实现类UseDaoImp,代码如下:

;

o;

public class UserDaoImp implements UserDao {

privateString username;

publicUserDaoImp() {

}

publicUserDaoImp(String username) {

me = username;

}

publicString getUsername() {

returnusername;

}

@Override

public void save() {

n("save method is called:" + username);

}

@Override

public void update() {

n("update method is called" + username);

}

}

需求如下:如果实现类的username!=null,才可以调用save 与update 方法,为null 则不

能调用。当然要解决此问题,可以在save 与update 方法内部进行判断,但是如果在方法内

部进行判断,代码则失去了灵活性,如果以后的需求改变,比如变成 时,

则又要在update/save 方法中重新进行一次判断。如果save/update 这样的方法很多,这样

就会很麻烦。其实要解决此问题,可以通过动态代理技术实现。这里的需求其实就是根据要

求来拦截一些业务方法,这种编程问题称之为横切性关注点。

代理的目标对象必须实现了一个接口---横切关注点

2.动态代理实现[JDK]

建立代理工厂ProxyFactory,代码如下:

y;

public class ProxyFactory implements InvocationHandler {

privateObject target;

publicObject createUserDaoImp(Object target) {

= target;

return

xyInstance(ss().getClassLoader(),

ss().getInterfaces(), this);

}

@Override

publicObject invoke(Object proxy, Method method, Object[] args)

throwsThrowable {

UserDaoImp udi = (UserDaoImp) target;

Object result = null;

if(rname() != null) {

result = (target, args);

}

returnresult;

}

}

简析动态代理:此类根据传递的target 对象来生成此目标对象的代理对象,需要强调的是

动态代理技术所要代理的对象必须实现一个接口。newProxyInstance 参数说明:第一个

参数是目标对象的类装载器,第二个参数是目标对象的接口,第三个参数是回调对象,生成

的代理对象要执行方法时就是依靠这个回调对象的invoke 方法来进行目标对象的方法回

调。关于动态代理的其它细节不在此讨论。

建立junit 测试代码,内容如下:

public class AopProxyTest {

@Test //用户名为空,不执行方法

public void testProxy(){

ProxyFactory pf=new ProxyFactory();

UserDao ud=(UserDao) UserDaoImp(new UserDaoImp());

();

}

@Test //用户名为不为空,才执行save方法

public void testProxy2(){

ProxyFactory pf=new ProxyFactory();

UserDao ud=(UserDao) UserDaoImp(new UserDaoImp("张某"));

();

}

}

实现代理

JDK的Proxy实现代理要求被代理的目标对象必须实现一个接口,而如果目标对象没有实现接

口则不能使用Proxy来代理。其实也可以借助cglib来实现代理。操作步骤如下

步骤一、建立UserDaoImp2类,它与UserDaoImp的唯一区别就是没有实现任何接口。

步骤二、导入包,创建cglib代理工厂,代码如下:

y;

public class CglibFactory implements MethodInterceptor {

privateObject target;

publicObject createUserDaoImp2(Object target) {

= target;

Enhancer enhancer = new Enhancer();

erclass(ss());

// cglib创建的代理对象,其实就是继承了要代理的目标类,然后对目标类中所有非final方

法进行覆盖,但在覆盖方法时会添加一些拦截代码。

lback(this); //注册回调器

();

}

@Override

publicObject intercept(Object proxy, Method method, Object[] args,

MethodProxy methodProxy)

throwsThrowable {

UserDaoImp2 udi = (UserDaoImp2) target;

Object result = null;

if(rname() != null) {

// 前置通知

try{

result = (target, args);

// 后置通知

} catch (Exception e) {

tackTrace();

// 例外通知

} finally {

// 最终通知

}

}

returnresult;

}

}

说明:注意注释的通知,通知就是拦截到方法后执行的一些代码,比如前置通知,就是说在

回调目标方法时执行的一些操作。

步骤三、建立测试代码(省略,和五.2 的测试代码相似)

理论知识

横切性关注点:对哪些方法拦截,拦截后怎么处理,这些关注就称之为横切性关注点

切面(aspect):类是对物体特征的抽象,而切面是指对横切性关注点的抽象。

连接点(joinpoint):被拦截到的点,因为spring 只支持方法类型的连接点,所以在spring

中连接点指的就是被拦截到的方法。实际上连接点还可以是字段或构造器。

切入点(pointcut):对连接点进行拦截的定义。

通知(advice):所谓通知就是指拦截到连接点之后的要执行的代码。通知分为前置、后置、

异常、最终。环绕通知五类。

目标对象:代理的目标对象。

织入(weave):将切面应用到目标对象并导致代理对象创建的过程

引入(introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方

法或字段。

5.基于spring 的AOP 实现

步骤一、导入spring 开发的基本包(包括切面及注解包)

步骤二、编写切面类TheInterceptor,代码如下:

y;

@Aspect

public class TheInterceptor {

@Pointcut("execution (* oImp.*(..))")

// 声明一个切入点(第一个*后要留一个空格)

private void anyMethod() {

}

@Before("anyMethod()")// 前置通知

public void before() {

n("前置通知");

}

@AfterReturning("anyMethod()")//后置通知

public void afterReturning(){

n("后置通知");

}

}

简析:Aspect 注解声明此类为一个切面类,Pointcut 注解用来声明一个切入点,括号中的

参数是切入点的表达式,这里的表达式的意思是对UserDaoImp 类的所有方法进行拦截。关

于切入点表达式后面会有详细的说明。anyMethod 是为切入点起一个名字,后面的“通知”

都要依赖这个名字。

步骤三、 配置文件的内容如下:

xmlns:xsi="/2001/XMLSchema-instance"

xmlns:context="/schema/context"

xmlns:aop="/schema/aop"

xsi:schemaLocation="/schema/beans

/schema/beans/

/schema/context

/schema/context/

/schema/aop

/schema/aop/">

class="erceptor" />

说明:要想切面类起作用,首先要把切面类纳入spring容器管理。

步骤四、编写junit测试单元

@Test

public void base() {

ApplicationContext ctx = new

ClassPathXmlApplicationContext("");

// UserDaoImp udii=(UserDaoImp) n("userDaoImp");

UserDao ud = (UserDao) n("userDaoImp");

n(ss().getName());

();

}

说明:由于开启了切面编程功能,所以当我们获取一个被切面类监控管理的bean对象

—UserDaoImp时,它实际上获取的是此对象的一个代理对象,而在spring中对代理对象的处

理有如下原则:(1)如果要代理的对象实现了接口,则会按照Proxy的方式来产生代理对象,

这即是说产生的代理对象只能是接口类型,比如起用上面注掉的代码就会报错,而且通过下面的

打印语句我们也可以看出产生的是一个代理对象。(2)要代理的对象未实现接口,则按cglib

方式来产生代理对象。另还要注意:要想spring的切面技术起作用,被管理的bean对象只能

是通过spring容器获取的对象。比如这里如果直接new出UseDaoImp对象,则new出的对象是

不能被spring的切面类监控管理。

补充:测试被代理对象未实现接口时,spring切面技术的应用。

步骤一、修改切面类TheInterceptor切入点为如下内容:

@Pointcut("execution (* .*.*(..))")

说明:拦截impl包下的所有类所有方法

步骤二、在中增加如下内容:

步骤三、junit测试代码如下:

public void base2() {

ApplicationContext ctx = new

ClassPathXmlApplicationContext("");

UserDaoImp2 udi2 = (UserDaoImp2) n("userDaoImp2");

n(ss().getName());

n(ss().getSuperclass().getName());

();

}

说明:UseDaoImp2 未实现任何接口,因此在spring 中利用切面技术来管理此类使用的动态

代理技术实质是cglib 的动态代理方式,所以产生的代理对象实质是被代理对象的一个子类,

通过上面的控制台打印语句可以看出。

小结:(1)声明aspect 的切面类要纳入spring 容器管理才能起作用。(2)被管理的bean

实例要通过容器的getBeans 方法获取。(3)依据被管理的bean 是否实现接口,spring

采取两种方式来产生代理对象。(4)在xml 文件中启用

6.通知应用实例(基于注解)

在前一节,我们应用了前置通知和后置通知,除了这两个通知外,下面接着演示其它通知的

应用。

(1)最终通知

在切面类TheInterceptor中增加如下代码即可:略去测试。

@After("anyMethod()")// 最终通知

public void after() {

n("最终通知");

}

(2)异常通知

为了演示此实例,我们在UseDaoImp 中增加如下代码以抛出异常:

int i=1/0;在然后在切面类TheInterceptor中增加如下代码:

@AfterThrowing("anyMethod()") // 例外通知

public void AfterThrowing() {

n("例外通知");

}

当获取代理对象并调用save 方法时会抛出异常,例外通知便会得以执行。

(3)环绕通知

@Around("anyMethod()") //环绕通知

publicObject around(ProceedingJoinPoint pjp) throws Throwable {

n("进入环绕");

//if(){ // 进行一些判断,再执行环绕

Object result = d();

//}

n("退出环绕");

returnresult;

}

注意的是方法的参数及抛出异常类型的固定写法(方法名可以是任意得),另在该方法中必须执行d()才能让环绕通知中的两处打印代码得以执行。即是说要想环绕通知的拦截处理代码起作用必须调用d 方法。补充:环绕通知通常可以用来测试方法的执行时间,在d 前获取一个时间,在d 方法后再获取一个时间。最后两

个时间相减即可得方法执行时间。

(4)传递参数给通知

首先在UseDao接口中增加如下代码:

String add(String name);

然后再在UserDaoImp中实现此方法,代码如下:

publicString add(String name) {

n("add method is called [ " + name+" ]");

return "添加成功";

}

需求:获取调用add方法传递的参数。操作步骤如下:在切面类增加如下代码:

@Before("anyMethod() && args(name)")// 前置通知,只针对UseDaoImp的add方法

public void beforeAdd(String name) {

n("前置通知:" + name);

}

说明:在前置通知的方法中有一个参数,然后再把此参数作为拦截条件(即是说拦截带有一个

String类型参数的方法)。args的名字和beforeAdd方法参数名字相同。

测试代码:

public void advieeTest() {

ApplicationContext ctx = new

ClassPathXmlApplicationContext("");

UserDao ud=(UserDao) n("userDaoImp");

("xxx");

}

(5)获取方法的返回值

我们知道add方法有一个返回值,我们对此方法进行拦截并获取返回值,在切面类中增加如

下代码:

@AfterReturning(pointcut = "anyMethod()", returning = "result")

// 后置通知,监听返回结果,针对UserDaoImp的getUsername()方法

public void afterReturningRes(String result) {

n("后置通知,返回结果:" + result);

}

说明:afterReturningRes 方法的参数就是要返回的参数类型,returning 标记的就是的

结果,它的取值与该方法参数名相同。测试代码同(4)。

(6)获取抛出的异常

切面类的增加如下代码:

@AfterThrowing(pointcut="anyMethod",throwing="e")

public void catchException(Exception e){

n("获取抛出的异常:"+e);

}

throwing 的取值和方法的参数名相同,测试代码省略。

7. 通知应用实例(基于XML)

步骤一、复制TheInterceptorX 类为TheInterceptorXML,并去掉所有注解。

步骤二、建立配置文件,内容如下:

class="erceptorXML" />

--声明一个切面类

expression="execution(* oImp.*(..))" />

method="afterReturningRes" returning="result" />

method="catchException" throwing="e"/>

测试代码如下:

public void advieeTest() {

ApplicationContext ctx = new

ClassPathXmlApplicationContext("");

UserDao ud=(UserDao) n("userDaoImp");

("xxx");

}

未解决问题:不能成功传参给前置通知。

8.解析切入点表达式

1.格式:execution(返回值空格方法选择)。两部分组成,中间一定要有空格。

返回值:可以是*,说明拦截任何方法。(全名),拦截返回值为String

类型的方法。常用的实例如下:

方法选择:包名[类名].*()。设定要拦截的方法签名。

表达式(省略execution)说明

( 方法选择略) 拦截返回值为String 类型的方法

(!void方法选择略) 拦截返回值非空的方法

(* ..*.*(..)) 拦截 包及子包下每个类的全部方法

(* .*.*(..)) 拦截 包下每个类的全部方法

(* .*(..)) 拦截asm 包下User 类的所有方法

(* .*

(,..))

拦截User 类中第一个参数为String,后面参

数任一的方法

待增加

待增加

9.总结

面向切面的常见应用(如权限拦截)、spring 的aop 依赖两种方式实现代理(依被代理的对

象是否实现接口而定)、通知概念、基于注解与基于XML 两种方式来配置切面、基本步骤(依

要拦截的方法来设定切入点,依据业务需求实现拦截通知代码,切面纳入spring 容器管理,

要被监控的类只能是通过Spring 容器获取)、切入点的格式。

六、与JDBC 集成

1.搭建环境

建立spring_05_integrationJdbc 项目,此项目使用dbcp 作为数据源来整合JDBC 技术。

因此除了spring 所需的jar 包,还应导入dbcp 的jar 包: 及此jar

所依赖的两个apache 开源jar 包:、。由

于涉及到数据库操作,还应导入数据库驱动包,这里选择的是mySQL 驱动。

2.基于注解的事务管理

步骤一、配置事务管理器。

建立文件,它的内容如下:

xmlns:xsi="/2001/XMLSchema-instance"

xmlns:aop="/schema/aop"

xmlns:tx="/schema/tx"

xmlns:context="/schema/context"

xsi:schemaLocation="

/schema/beans

/schema/beans/

/schema/tx

/schema/tx/

/schema/aop

/schema/aop/

/schema/context

/schema/context/"

>

class="ataSource">

class="urceTransactionM

anager">

说明:首先是配置了一个数据源,以供数据源事务管理器配置引用。接着配置了数据源事务管

理器,随后开启了基于@Transactional注解的事务管理器,开启后,只要是被spring管理的

bean且打有@Transactional注解的bean都会受配置的事务管理。关于这里的配置可参看

spring文档9.5.6

步骤二、准备使用环境。

建立UserDao接口,代码如下:

;

public interface UserDao {

voidsave(User user);

voiddelete(User user);

voidupdate(User user);

User get(int id);

ListgetUsers();

}

建立UserDaoImp类实现UseDao接口,代码如下:

;

@Transactional

public class UserDaoImp implements UserDao {

privateJdbcTemplate jdbcTemplate;

public void setDatasouce(DataSource datasource) {

jdbcTemplate= new JdbcTemplate(datasource);

}

public void delete(User user) {

("delete from user where id=?", new Object[]

{ () },

new int[] { R });

}

publicUser get(int id) {

return(User) orObject("select * from user

where id=?",

newObject[] { id }, new int[] { R },

newRowMapper() {

publicObject mapRow(ResultSet rs, int arg1) throws

SQLException {

User user = new User();

(("id"));

e(ing("name"));

returnuser;

}

});

}

@SuppressWarnings("unchecked")

publicList getUsers() {

return(List) ("select * from user", new

RowMapper() {

publicObject mapRow(ResultSet rs, int arg1) throws

SQLException {

User user = new User();

(("id"));

e(ing("name"));

returnuser;

}

});

}

public void save(User user) {

("insert into user(name) values(?)", new

Object[] { e() },

new int[] { R });

}

public void update(User user) {

("update user set name=? where id=?", new

Object[] { e(),

() }, new int[] { R,

R });

}

}

步骤三、把UserDaoImp纳入spring容器管理。

在中增加对应的配置内容如下:

结合配置文件解析UserDaoImp实现类:(1)此类作为一个bean实例纳入spring容器管理,

使用setter注入方式完成对datasource的注入,实质是完成的JdbcTemplate对象的初始

化。(2)该类CRUD方法都使用了Spring容器提供的JdbcTemplate对象来简化了CRUD操作,

在spring文档的11.2.1.节对JdbcTemplate类作了较详细的介绍。(3)此类打上了

@Transactional注解,表示此类中的业务方法都会受配置的

管理

步骤四、编写测试类

;

public class TestSJ {

privateUserDao ud = (UserDao) new

ClassPathXmlApplicationContext("").getBean("userDaoImp");

public static void main(String[] args) {

TestSJ sj = new TestSJ();

();

();

}

public void save() {

User user = new User();

e("张某某");

(user);

}

public void delete() {

User user = new User();

(1);

(user);

}

//其它测试方法省略...

}

说明:注意这里通过getBean获取的是UserDao接口对象,而非UserDao接口的实现类

UserDaoImp对象,因为spring的事务管理也是利用了aop技术,所以必须要面向接口,如果

想通过getBean获取它的实现类对象将会报错。

步骤五、感知事务管理。

在UserDaoImp的delete方法中增加如下代码:

("delete from user where id=?", new Object[]

{ () }, new int[] { R });

inti=5/0;

("delete from user where id=2");

spring 默认的事务管理方式:运行期异常进行事务回滚,非运行期异常不进行事务回滚。

因此增加上面的代码后,会出现ArithmeticException 运行期异常。所以当出现此异常时,

delete 方法中的数据库操作都会进行回滚,因而id=1 和2 这两条记录都不会被删除。如

果把UseDaoImp 类前标记@Transactional 的注解去掉,id=1 的记录会被删除,因为失去

了spring 容器的事务管理。

小结spring 事务:

(1)在 中配置事务管理器,可以是依赖于数据源的事务管理器,也可以其它的

事务管理器(比如和JPA 集成的事务管理器等),这些事务管理器类都继承自

AbstractPlatformTransactionManager 抽象类。(2)事务管理器配置后,要想让配置的事

务管理器对某些类的事务管理起作用,可以有两种方式配置:一种是声明式配置(两种:基

于注解或基于XML),一种是编程是配置。(3)上面的一些操作,都是基于注解的声明式

事务配置:关键两点:开启基于事务的注解支持(

transaction-manager="txManager" />);被管理的类打上事务注解标记。(4)除了

可以在类前声明事务标记,也可以在类的方法中具体声明详细的事务标记。(5)事务标记

具有多个可选属性,具体可参文档9.5.6.1。

3.简析事务注解属性

@Transactional 注解具有大属性:

(1)传播属性propagation:

可选属性说明

REQUIRED 业务方法需要在一个事务中运行,如果方法运行时,已经处在一个事务中,

那么加入到该事务,否则自己创建一个新的事务。

REQUIRESNEW 不管是否存在事务,业务方法总会为自己发起一个新的事务,如果方法已

经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,直到方

法执行结束,新事务才算结束,原先的事务才恢复执行。

SUPPORTS 如果业务方法在某个事务范围内被调用,则业务方法成为该事务的一部分。

如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行

NOT_SUPPORTED 声明业务方法不需要事务,如果方法没有关联到一个事务,容器不会为它

开启事务,如果方法在一个事务中被调用,则该事务会被挂起,在该方法

调用结束后,原先的事务恢复执行

NEVER 指定业务方法绝对不能在事务范围中执行,如果业务方法在某个事务中执

行,容器会抛出例外;只有业务方法没有关联到任何事务,才能正常执行。

MANDATORY 指定业务方法只能在一个已经存在的事务中执行,业务方法不能发起自己

的事务。如果业务方法在没有事务的环境下调用,容器就会抛出例外

NESTED 如果一个活动的事务存在,它会自己产生一个单独的而且拥有多个可以回

滚的保存点的事务,然后嵌套运行在活动的事务中,实质它可以看成是一

个内部事务且具有回滚保存点,所以内部自身的事务回滚并不会引起外部

活动事务的回滚,它只是回滚到内部事务的保存点。如果没有活动事务,

则按REQIIRED 属性执行。需要注意的是此配置只对

DataSourceTransactionManager 事务管理器生效。

说明:上面多次提到业务方法,它实质就是UserDaoImp 中save 等这样的方法。但是这些方

法前会有一个设定了详细属性的@Transactional 注解。比如:

@Transactional(propagation="",isolation="",noRollbackFor="")

(2)隔离级别属性isolation:这个是依赖数据库系统而言,数据库系统提供四种事务隔

离级别。(具体的事务隔离级别分析在些略过,可以参看网络资源及相关文档)

(3)只读属性readOnly:false--读写性、true--只读性事务.

(4)超时属性timeout:int 型,以秒为单位。

(5)回滚属性:根据抛出的异常决定是否回滚事务,它包括四种可选属性。参文档9.5.6.1

的表9.3

4.扩展:抽取dpcp 配置文件。

如果希望把dbcp的配置单独放到一个properties配置文件中去,可以使用如下做法。操作步

骤:在src的路径下建立ties文件,内容如下:

driverClassName=

url=jdbc:mysql://localhost:3306/sjdbc

username=root

password=123456

initialSize=2

maxActive=100

maxIdle=2

minIdle=1

然后把此配置读取数据源bean中去,形式如下:

class="ataSource">

...省略部分属性的获取,形式如下:

...

注意的是使用${}来获取值,必须把properties 文件引入到spring 的容器管理,因此要想

成功通过${}来获取相应的值,还应 中增加如下内容:

而要想使用还应在 增加名称空间:

xmlns:context="/schema/context"

和schema 信息:/schema/context

/schema/context/

关于这里的名称空间及schema 信息可参A2.8 节。

5.基于XML 的事务管理

步骤一、拷贝UserDaoImp 为UserDaoImp2,然后把内面所有关于事务的注解去掉

步骤二、建立文件,并配置事务管理器(和六.2.步骤一完全相同)。

步骤三、配置事务

expression="execution(* oImp2.*(..)) " />

-->拦截get开头的方法,并设置只读属性

-->拦截所有方法

解析:根据txManager 事务管理器来配置一个事务通知器:txAdvice。而中的

name 属性指定方法名(可以使用通配符),其它属性就好比注解的传播属性配置(可参

六.3(1))。然后再使用切面配置一个切入点,再对这个切入点引入事务通知器。

步骤四、把UserDaoImp2 纳入spring 容器管理。即在 做如下配置:

步骤五、编写写测试类,测试事务管理器是否起作用,省略...

5.总结与JDBC 的集成

(1)首先是配置好一个事务管理器(2)基于注解:一是涉及到事务的类打上事务注解标

记,二是在xml 配置文件中开启事务注解功能;基于配置:事务通知器加进apo 配置中。(3)

涉及到事务的类必须纳入spring 容器管理,事务才能起作用。(4)强调:不论是基于注解

还是基于xml 配置实现事务,都要依赖于动态代理技术。以UserDaoImp 说明,它实现了接

口,所以它的代理对象实质是一个接口对象,因而通过getBean 获取的UseDaoImp 实质上是

一个接口对象,所以特别要注意类型转换(参六.2.步骤四.说明)。(5)为了简化CRUD 操作,

我们通常会使用spring 提供的JdbcTemplate 类。

七、SSH 集成实例

1.分析实例及准备环境

建立spring_06_SSH 项目,此项目主要演示struts1.x、hibernate、spring 的集成。集成

前的一个比较关键的因素是搭建好环境,这一步要求我们对集成的框架所用到的jar 包有比

较清楚的认识。下面列表展示了三个框架所用到的jar 包。

hibernate3.3 所用到的jar 包:

开源语法分析生成器(librequired)

Commons 集合类库,与连接池有关(librequired)

xml 解析类库(librequired)

分析,编辑和创建java 字节码类库(librequired)

事务处理api (librequired)

日志处理(librequired)-->用log4j 实现

核心

二级缓存(liboptionalehcache)

Spring2.5 安装包所用的jar 包:

libcglibcgligb-nodep-2.1_

、、

供srping 与hibernate 使用的日志记录jar 包

libslf4j 日志转换jar 包,实现把 包适配到slf4j

标准。

struts1.10 所用到的jar 包

导入lib 目录下的所有jar 包。但为了避免jar 包冲突,不要导入(hibernate

已经导入)

jar 包说明:绿色字样是框架所需的一些基本包。hibernate 的开发jar 包主要集中在

required 目录和核心jar 包,还需要特别说明的是它的日志包问题:hibernate 使用了slf4j

来记录日志,但是slf4j 只是一个日志记录标准,需要具体的实现才可以进行日志记录,我

们可以使用它本身的实现(如),也可以使用其它的实现,如log4j

来记录日志,但是使用log4j 需要导入 来进行适配。在这里为了

和spring 的日志记录结合,我们使用了log4j 的实现。而要使用log4j 日志记录,可以在

spring 的lib 子目录中找到。Spring 框架中除了常用的jar 包外,还增加了dbcp 连接池相

关的包,需要说明的是连接池的基本包为:dbcp、pool、collections(在hibernate 中已

导入)。为了与strus 相结合,还增加了一个 包。

经验:spring 的作用是来集成其它的框架,所以对于许多集成所要用到的jar 包,在spring

中都能找到,因而对于项目中出现的类装载相关的错误,应首先查看jar 包是否冲突,是否

导入了需要的包,建议集成时,其它框架只需导入它们自己所需的基本包,而对于集成所用

的包都可以从spring 提供的lib 中查询。

2.集成spring+hibernate

对于框架的集成最好的方式是分布集成,比如这里先集成spring+hibernate 并测试,测试

通过后再来集成struts。

步骤一、建立接口UserServeice,代码如下:

e;

public interface UserService {

public abstract void save(User user);

public abstract void delete(Integer id);

public abstract void update(User user);

public abstract User getUser(Integer id);

@SuppressWarnings("unchecked")

public abstract List getUsers();

}

步骤二、对应的实现类UserServiceBean,代码如下:

;

public class UserServiceBean implements UserService {

@Resource

privateSessionFactory sf;

@Transactional

public void save(User user) {

rentSession().persist(user);

}

...其它实现方法省略

}

注意事项:(1)在每个方法上都要加上事务注解,特别是save和update方法一定要加上事务注

解,因为把sessionFactory对象是通过spring注入(注解方式注入),而spring中配置的

sessionFactory对象又纳入了spring容器的事务管理,所以要加上事务标记。(2)一定要

通过getCurrentSession方法得到session对象,因为使用openSession方式得到的

Session对象不受spring容器管理。

实体类User及相应的配置在此省略。

步骤三、在中进行配置,内容如下:

class="ataSource">

...数据源配置,省略其它,可参看“与jdbc的集成”。

class="ateTransactionManager

">

class="essionFactoryBean">

com/asm/entity/

t=ialect

=update

_sql=true

_sql=true

如果想就把此bean对应的属性配置放到中去配置,可以在

中配好后,再使用下面的配置加载配置文件:

-->

class="rviceBean" />

说明:这里配置的sessionFactory 实质就是对hibernate 的sessionFactory 进行了一

次包装,它的配置依赖于一个数据源实例bean,最后再把sessionFactory 纳入spring

器的事务管理,但是要注意的是这时使用的是事务管理器类为

HibernateTransactionManager。

配置简图:

步骤四、建立junit单元进行测试,测试代码如下:

;

public class UserServiceTest {

private static UserService us;

@BeforeClass

public static void setUpBeforeClass() throws Exception {

ApplicationContext ctx=new

ClassPathXmlApplicationContext("");

us=(UserService) n("userServiceBean");

}

@Test

数据源SessionFactory –被包装

事务管理器

UserServiceBean 的sf 属性

注入

被管理

public void testSave() {

(new User("李某某"));

}

...其它测试方法省略。

}

测试通过,完成了MVC的M层业务逻辑层的开发。

3.集成struts 框架

首先准备好struts框架的基本配置环境:(1)在中配置struts的核心控制器

Actionservlet(配置代码省略)。(2)在中建立配

置文件,它的配置内容如下:

"-//Apache Software Foundation//DTD Struts Configuration 1.3//EN"

"/dtds/struts-config_1_">

path="/WEB-INF/page/">

(3)上面所用到的UserAction的代码如下:

;

public class UserAction extends Action {

@Override

publicActionForward execute(ActionMapping mapping, ActionForm form,

HttpServletRequest request, HttpServletResponse response)

throwsException {

WebApplicationContext ctx = WebApplicationContextUtils

.getWebApplicationContext(vletContext());

UserService us = (UserService) n("userServiceBean");

List users = rs();

ribute("users",users);

rward("success");

}

}

说明:要想此实例真正可用,还需在 中增加一个监听器的配置,内容如下:

contextConfigLocation

classpath:

tLoaderListener

此监听器的作用就是在启动时把spring 容器进行实例化,并把spring 容器实例

WebApplicationContext 对象放到application 作用域中,大致进行类似这样的操作:

ribute(_WEB_APPLICATION_C

ONTEXT_ATTRIBUTE,spring 容器实例WebApplicationContext 对象)。因此我们也可以通

过保存的名字来获取WebApplicationContext 对象,但是为了方便,spring 提供一个工

具类WebApplicationContextUtils 来获取此对象,关于这个工具类的使用,在

UserAction 就是使用了此工具类来获取spring 容器实例。保存一个List对象后,

在 遍历List 对象,代码如下:

${ } -- ${ }

4.改进集成struts 框架

上面的代码其实并非整合struts的最佳方式,整合struts的优雅方式应该是把Action也纳入

spring容器管理。具体操作如下:

步骤一、修改UserAction类,修改后的代码如下:

@Resource

privateUserService userService;

@Override

publicActionForward execute(ActionMapping mapping, ActionForm form,

HttpServletRequest request, HttpServletResponse response) throws

Exception {

ribute("users", rs());

rward("success");

}

说明:上面的userService属性通过spring的注解方式注入,这样可以避免获取UserService

对象的繁琐过程。

步骤二、在配置UserAction,纳入spring容器管理,配置内容如下:

说明:此实例bean的配置使用了name属性,而未使用id属性,因为id不支持“/”这样的特殊

字符。还需要说明的是这里的name之所以取值为“/list”,是因为纳入spring容器管理的

Action的取名必须保证与action的path属性值相同(为什么如此做,后面有解释)。

步骤三、添加一个ActionServet的处理器类。Struts1.x中的ActionServlet最终其实是

把请求派发给RequestProcessor类来处理,而如果想自己编写处理请求类,可以在

中配置一个来处理这些请求。Spring提供了一个

DelegatingRequestProcessor类,它继承自RequestProcessor类,它除了具备父类的

功能,还主要完成一件重要的工作:把ActionServlet派发过来的请求交给在spring中配置

的Action bean实例来处理(由于最终它是把请求交给了spring中配置的action bean

例处理,所以在中关于此action的type属性可以不写)。因此我们还

应的中增加如下配置代码:

value="tingRequestProcessor"

/>

关于这个处理器也可如下配置:

"tingRequestProcessor"/>

5.使用二级缓存

步骤一、在使用hibernate框架时,可以为hibenate配置一个第三方缓存。通常的做法是在

hibernate的主配置文件中开启缓存相关的功能。在此项目中,由于是集成了spring框架,

所以我们可以在spring容器的中的sessionFactory bean实例的

hibernateProperties属性中增加如下配置:

_second_level_cache=true -->开启二级缓存

_query_cache=false -->不使用查询缓存

er_class=eProvider

-->第三方缓存实现类

步骤二、第三方缓存EhCache的配置,在src目录下编写配置文件,内容如下:

ActionServlet

DelegatingRequestProcessor

根据 的请求,分

析出在 中存

在一个名为“/list”的

bean 实例,然后把请求

交给这个bean 实例处

理。

Spring 容器中存在名为“/list”

bean 实例Action:UserAction

说明了为什么要在 配

置的UserAction 的名字为“/list”

overflowToDisk="true"

timeToIdleSeconds="120"

timeToLiveSeconds="180"

diskPersistent="false"

diskExpiryThreadIntervalSeconds="60"/>

eternal="false"

overflowToDisk="true" timeToIdleSeconds="300"

timeToLiveSeconds="600" diskPersistent="false"/>

步骤三、在中的目录下增加如下配置:

说明:这里的region的值和配置下的中的name属性值相同。结合

作如下说明:通常情况下,实体类的二级缓存配置通常使用默认的

这个配置,但是如果想使用特有的缓存配置,可以用实体类的

regin>来和中的关联。

步骤四、测试二级缓存。在UserServiceTest中增加如下测试代码:

public void testGetUser() {

n(r(1).getName());

n("关闭数据库服务");

try{

(1000*40);

} catch (InterruptedException e) {

tackTrace();

}

n("关闭数据库准备从缓存中获取");

n(r(1).getName());

}

说明:当执行到控制台显示“关闭数据库服务”时,我们手工停掉数据库服务,休眠40秒

后,仍能获取数据,证明是从缓存中取得的数据。

小结使用缓存步骤:开启支持缓存的配置、对第三方缓存类进行配置、在实体类配置文件中

标记使用二级缓存。

6.乱码问题

当前台传递的数据包含中文时,获取这些中文数据时会出现乱码。大致原因如下:在struts

框架中,我们把配置的*.do 的请求会被ActionServlet 拦截到,而访问ActionServlet 时

用到的默认编码为ISO8859-1 ,即是说设置了racterEncoding

(“ISO8859-1”)。这时ActionServlet 在获取参数时就会得到乱码,ActionServlet 填

充ActionForm 时就把乱码填充进ActionForm。当填充完毕后会把*.do 的请求派发给相应的

Action,而Action 从ActionForm 中获取数据时自然就出现了乱码。解决此问题很简单,只

需把spring 提供的一个过滤器类配置到 中,即增加如下代码:

encodingFilter

terEncodingFilter

encoding

UTF-8

encodingFilter

/*

在以前我们是自己编写此filter 类,而spring 为我们提供了CharacterEncodingFilter

类,我们就自需配置即可。另要注意在配置时一定要指定一个encoding 参数名及值. 补充:

关于这里的乱码问题可参“struts1.x 深入学习笔记-四、2.步骤五.问题”。

Spring 对于OpenSessionInView 也提供一个filter 类来解决此问题:

ssionInViewFilter

八、SSJ 集成实例

1.搭建环境

hibernate 核心包

hibernate 注解包

Hibernate 针对JPA 的实现包

上面列举的jar 包(基于hibernate 实现的JPA 包)+SSH 集成时的所有jar 包

2.集成spring+JPA

建立spring_07_SSJ 项目并导入相关ja 包,此项目基本参照上一个项目,只是把JPA 的实

现加了进去。

步骤一、搭建JPA 开发环境

在src下建立META-INF/文件,此文件的主要内容如下:

xmlns:xsi="/2001/XMLSchema-instance"

xsi:schemaLocation="/xml/ns/persistence

/xml/ns/persistence/persistence_1_"

version="1.0">

transaction-type="RESOURCE_LOCAL">

atePersistence

value="5Dialect" />

value="" />

value="jdbc:mysql://localhost:3306/ssj" />

对应的持久化User 的代码如下:

;

@Entity

public class User {

@Id

@GeneratedValue

privateInteger id;

@Column(name="u_name")

privateString name;

publicUser() {

}

publicUser(String name) {

= name;

}

...省略get/set方法,特别注意要在实体在打上实体相关的标记:@Entity @Id

@GeneratedValue @Column

}

步骤二、把JPA纳入spring容器管理

在中的配置如下:

class="ntityManagerFactoryBean">

class="nsactionManager">

说明:localEMF 实例bean 的使用是把JPA 的 配置的jpaDemo 持久化单元

加进JPA 实体工厂管理(LocalEntityManagerFactoryBean 包装了JPA 所使用的

EntityManager 对象的工厂类EntityManagerFactoryBean)。再把工厂类加进事务管

理器。

步骤三、UserServiceBean 的代码如下:

;

public class UserServiceBean implements UserService {

@PersistenceContext

privateEntityManager em;

@Transactional

public void save(User user) {

t(user);

}

...省略其它的实现方法

}

说明:em属性依赖于spring容器注入。只要把此类交给spring容器管理,spring容器会根据

@PersistenceContext注解,来用配置的产生EntityManager对象的JPA工厂类来完成此

对象的注入。强调的是在save、update方法上一定要打上事务注解标记。

步骤四、测试UserServiceBean 的业务方法

测试代码同上一个项目的测试代码

3.集成struts:参上一个项目

问题

在集成hibernate 时,OSIV 指的是sesion 关闭后的的解决方案。而在使用JPA 时,取代

session 的是EntityManager,所以要解决因EntityManager 关闭的OSIV 问题,应使用

spring 提供的tityManagerInViewFilter

九、SSH2 集成实例

1.搭建环境并集成hibenate

基本环境SSH 集成相同,只是要导入开发struts2 的开发包(在2.1.8 需要导入6 个基本包),

与hibenate 的集成可以前面的项目说明。

2.集成strust2

步骤一、建立UserAction,代码如下:

;

public class UserAction extends ActionSupport {

@Resource

privateUserService userService;

privateString username;

@Override

publicString execute() throws Exception {

User user=new User(username);

(user);

return"success";

}

...省略username 的get/set方法

}

说明:userService 属性通过spring 注入。在 配置UserAction 如下:

配置此action,在src建立文件,配置如下:

"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"

"/dtds/">

/

步骤二、配置一个监听器,实例化spring 容器,和集成struts1 相同。即在 中增

加如下代码:

contextConfigLocation

classpath:

tLoaderListener

步骤三、前台 页面,代码如下:

用户名:

步骤四、发布测试。

十、整合总结:

十一、错误总结:

1.使用new ClassPathXmlApplicationContext 对象时未传递 配置文件出错:

BeanFactory not initialized or already closed

另外如果ctx 对象关闭后,再刷新也可以重新使用ctx 对象来获取bean 实例

OpenSession 得到的session 不受Spring 容器管理

JPA 实体一定要有entity 注解标志—涉及到保存或更新操作时,一定要打上事务标记__


本文标签: 方法 配置 对象 事务 使用