Hibernate入门笔记

    技术2022-05-19  22

    一、理论知识

    1. 依赖注入、控制反转

    依赖注入:在运行期,由外部容器动态地将依赖对象注入到组件中 控制反转:应用本身不负责依赖对象的创建及维护,依赖对象的创建及维护是由外部窗口负责得。这样控制权就由应用转移到了外部容器,控制权的转移就是所谓的反转。

    2.spring 的主要特性。

    1 )降低组件之间的耦合度,实现软件各层之间的解耦。 2 )可以使用容器提供的众多服务,如:事务管理服务、消息服务、JMS 服务、持久化服务等等。 3 )容器提供单例模式支持,开发人员不再需要自己编写实现代码。 4 )容器提供了AOP 技术,利用它很容易实现如权限拦截,运行期监控等功能。 5 )容器提供的众多辅作类,使用这些类能够加快应用的开发,如:JdbcTemplateHibernateTemplate. 6 )对主流的应用框架提供了集成支持。

    3. 常用技术

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

     

    二、基本实例

    1. 准备搭建环境

    dist/spring.jar

    lib/jakata-commons/commons-loggin.jar

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

    lib/aspectj/aspectjweaver.jar aspectjrt.jar

    lib/cglib/cglib-nodep-2.1.3.jar

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

    lib/j2ee/common-annotations.jar

    2. 搭建并测试环境

    建立名为spring_01_base 项目 ,根据需求导入jar 包。建立一个Junit 测试单元SpringEnvTest ,测试代码如下: @Test

        public   void testEnv() {

    ApplicationContext ctx = new ClassPathXmlApplicationContext( "beans.xml" );

        }

    beans.xml 配置文件在此省略(见下)。运行此测试如无错,则说明环境搭建成功。 说明: beans.xml 可以在类路径下进行配置,也可以在具体的目录下配置。可以是一个配置文件,也可以是多个配置文件组成 String 数组传入。

    3. 实例

    作如下准备工作:(1 )建立UseDao 接口,代码如下: package com.asm.dao;

    public interface UserDao {

        void save();

    }

    2 )建立UserDao 接口的实现类,UserDaoImpl package com.asm.dao.impl;

    import com.asm.dao.UserDao;

    public class UserDaoImpl implements UserDao{

        public void save() {

           System. out .println( " 执行 save 方法 ..." );

        }

    }

    3 )在src 目录下配置此beans.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"

        xsi:schemaLocation = "http://www.springframework.org/schema/beans           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd" >

        < bean id = "userDaoImpl" class = "com.asm.dao.impl.UserDaoImpl" />

    </ beans > 说明:bean 代表一个实质的java 类,通过它的id 可以获取一个此类的一个对象。 补充 :让xml 配置文件在编译时提示

    [windows][preferences][myeclipse][files and editors][xml][xml catalog] add ,在出现窗口的location 中选“file system, 然后在spring 解压目录的dist/resources 目录中选择“spring-beans-2.5.xsd, 并将key Type 值改为“Schema Location ”,key 值为:http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 4Junit 测试单元SpringEnvTest 中增加如下代码测试:

        @Test

    public   void base() {

    ApplicationContext ctx = new ClassPathXmlApplicationContext( "beans.xml" );

           UserDao userDao = (UserDao) ctx.getBean( "userDaoImpl" );

           userDao.save();

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

    4. 三种bean 的生成方式

    除了上面使用的类直接生成方式,还有bean 静态工厂及bean 实例工厂。 bean 静态工厂的配置如下: < bean id = "userDaoImpl2" class = "com.asm.dao.impl.UserDaoImplFactory" factory-method = "getUserDaoImpl" />

    相应的工厂类代码如下: package com.asm.dao.impl;

    public class UserDaoImplFactory {

        public static UserDaoImpl getUserDaoImpl(){

           return new UserDaoImpl();

        }

    } bean 实例工厂的配置如下: < bean id = "factory" class = "com.asm.dao.impl.UserDaoImplFactory2" />

    < bean id = "userDaoImpl3" factory-bean = "factory" factory-method = "getUserDaoImpl" />

    相应的工厂类的代码如下: package com.asm.dao.impl;

    public class UserDaoImplFactory2 {

        public UserDaoImpl getUserDaoImpl() {

           return new UserDaoImpl();

        }

    }

    5.bean 的作用域

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

    prototype: 每次请求都会创建一个实例(有状态bean 使用此作用域) requestsessionglobal session 这三个作用域主要用在web 应用中

    6.bean 的生命周期

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

    scopesingleton ,即默认情况,会在装载配置文件时实例化。如果希望在调用getBean 时才初始化,可以使用lazy-init="true" 补充:如果希望希望该配置文件中的所有bean 都延迟初始化,则应在beans 根结点中使用lazy-init="true"

    scope=prototype ,在调用getBean() 方法时才会初始化。 2 )生命周期:

    构造器、init 方法、获取bean 后的操作、destroy 方法(ctx.close 、注意 如果beanscope 设为prototype 时,当ctx.close 时,destroy 方法不会被调用)

    7. 属性注入Setter 方式

    1 )简单属性(如String ): < bean  id = "userServiceBean" class = "com.asm.service.UserServiceBean" >

           < property name = "id" value = "10" ></ property >

           < property name = "username" value = " 张某某 " ></ property >

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

    < property name = "userDao" ref = "userDaoImpl" /> 对象属性- 内部bean 注入:在上面的<bean> 中增加如下配置: < property name = "userDao" >

           < bean id = "temp" class = "com.asm.dao.impl.UserDaoImpl" />

    </ property >   

    (3) 集合属性注入:     < property name = "list" >

               < list >

                  < value > List 值一 </ value >

                  < value > List 值二 </ value >

                  < value > List 值三 </ value >

               </ list >

           </ property >

     

           < property name = "set" >

               < set >

                  < value > Set 值二 </ value >

                  < value > Set 值一 </ value >

                  < value > Set 值三 </ value >

               </ set >

           </ property >

     

           < property name = "map" >

               < map >

                  < entry key = "one" value = " " />

                  < entry key = "two" value = " " />

                  < entry key = "three" value = " " />

               </ map >

           </ property >

           < property name = "pro" >

               < props >

                  < prop key = "p1" > first </ prop >

                  < prop key = "p2" > second </ prop >

                  < prop key = "p3" > third </ prop >

               </ props >

           </ property >

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

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

    < bean abstract = "true" id = "parent" >

        < property name = "username" value = " 张某某 " ></ property >

    </ bean >

    < bean  id = "XXX"  class = "com.asm.vo.User" parent = "parent" >

        < property name = "password" value = "123456" ></ property >

    </ bean >

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

    8. 属性注入构造器方式

    < bean id = "userServiceBean2" class = "com.asm.service.UserServiceBean" >

           < constructor-arg index = "0" value = " 李某某 " />

           < constructor-arg index = "1" ref = "userDaoImpl" />

           < constructor-arg index = "2" >

               < list >

                  < value > List 值一 </ value >

                  < value > List 值二 </ value >

                  < value > List 值三 </ value >

               </ list >

           </ constructor-arg >

    </ bean >

    UserServiceBean 对应的构造方法代码如下: public UserServiceBean(String username, UserDao userDao, Set<String> set) {

           this . username =username;

           this . userDao =userDao;

           this . set =set;

    } 注意 :此方法会覆盖掉默认的构造方法,导致要依赖默认构造方法的配置不可用,因此我们还应为此类提供一个默认的构造器。

    三、使用注解方式注入

    1. 准备

    注解方式的注入主要针对对象属性的注入。 使用注解功能要引用注解包,另beans.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:context = "http://www.springframework.org/schema/context"

        xsi:schemaLocation = "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" >

        < context:annotation-config /> <!-- 开启注解功能 -->

    </ beans >

    2.Resource 注解实例

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

    package com.asm.service;

    public class UserServiceBean {

        @Resource (name = "userDaoImpl" )

        private UserDao userDao ;

        private UserDao userDao2 ;

        @Resource

        public void setUserDao2(UserDao userDao2) {

           this . userDao2 = userDao2;

        }

        public void test() {

           userDao .save();

           userDao2 .save();

        }

    }

    然后在bean.xml 中的配置如下:

    < bean id = "userDaoImpl" class = "com.asm.dao.impl.UserDaoImpl" />

    < bean id = "userServiceBean" class = "com.asm.service.UserServiceBean" /> 简要说明 Resouce 注解可以在字段上标记,也可以在对应的setter 方法上标记。此注解可以不使用name 属性,它会自动去查找匹配的类型(先以字段名称为name 的值查找,如找不到会再根据依赖对象的类型查找)。但只要使用了name 属性,就应确保name 的值在xml 中有相应的bean Id 对应。它是属于java 本身的注解, Resource 默认按属性名称装配

    3.Autowired 注解实例

    @Autowired (required= false )

    @Qualifier ( "userDaoImplXXX" )

    private UserDao userDao3 ; 说明 Autowired 默认是按照类型来查找对应的bean 实例注入,如果想注入指定名称的bean 实例,可以使用Qualifier 注解来指定名字。Required 属性设为true 时,如果不能成功注入则会报告异常,如果为设为false 而不能成功注入,则会将userDao3 设为null 。同样地,它也实用于setter 方法。它属于spring 特有的注解,Autowired 默认按类型装配。

    4. 自动装配

    自动装配(了解,不建议使用):除了要设置字段的setter 方法外,还应在beans.xml 配置文件中设置如下内容: < bean id = "userServiceBean2"

        class = "com.asm.service.UserServiceBean2" autowire = "byType" /> 说明:除了byType 外,autowire 的可选属性如下:

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

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

    Consturctor :也byType 相似,不同之处在于它应用于构造器的参数,如果容器中没有找到与构造器参数类型一致的bean, 则抛出异常。

    Autodetect :通过bean 类的自省机制来决定是使用consturctor 还是byType 方式进行自动装配。如果发现默认的构造器,那么将使用byType 方式。

    四、自动扫描管理 bean

    1. 准备工作

    在前面使用注解的时候,除了<beans> 结点配置增加了名称空间说明,另还增加了 < context:annotation-config /> 配置,它的作用就是注册一个处理器。通常情况下,我们要使用某个bean 实例,总会配置<bean> 相关内容。Spring 最新版本可以简化这一操作,即是说我们只要在配置文件作如下设置:

    < context:component-scan base-package = " 包名 " /> 便可以自动管理指定包名及子包下标住了@service (业务层组件)、@controller (控制层组件)、@repository (数据访问组件)或@component (泛指组件)的类,并把它们作一个实例bean, 相当于在beans.xml 中配置了<bean> 元素。需要说明的是,使用了此配置同时意味着还注册了注解配置的处理器,所以在这些类中用到了注解配置时并不需要再配置 < context:annotation-config /> 为什么提出自动扫描管理:在一些比较大的项目中,涉及到的bean 实例会有很多,如果依次对每个bean 实例进行配置,不但配置内容繁琐,而且配置文件也会显得杂乱。因此spring 提出了自动扫描bean ,它依据在xml 文件中指定的包名和类中标记的component 系列注解。

    2. 实例

    建立spring_03_autoscan 项目 ,内容基本和前面两个项目一样,只是在要纳入spring 管理的类前增加了component 这样的注解。Beans.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:context = "http://www.springframework.org/schema/context"

        xsi:schemaLocation = "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" >

        < context:component-scan base-package = "com.asm.service" />

        < context:component-scan base-package = "com.asm.dao.impl" />

    </ beans >

    说明:以上的配置会自动管理service 包和impl 包及它们子包下的带有component 标记的类,上面两行配置代码等价于 < context:component-scan base-package = "com.asm" /> 下面以UserServiceBean 为例进行说明,代码如下:

    package com.asm.service;

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

    public class UserServiceBean {

        @Resource (name = "userDaoImpl" )

        private UserDao userDao ;

        private UserDao userDao2 ;

     

        @Autowired (required = true )

        @Qualifier ( "userDaoImpl" )

        private UserDao userDao3 ;

        @Resource

        public void setUserDao2(UserDao userDao2) {

           this . userDao2 = userDao2;

        }

        public UserServiceBean() {

        }

        @PostConstruct

        public void init() {

           System. out .println( "init method is called" );

        }

        public void test() {

           System. out .println( "********************************" );

           userDao .save();

           userDao2 .save();

           System. out .println( userDao3 );

           // userDao3.save();

           System. out .println( "********************************" );

        }

    }

    说明 :如果使用 Service 这些注解时不指定名称,这些实例bean 的名称就是类名(但首字母小写),也可以指定实例bean 的名字,比如这里指定其名字为“usb,scope 注解配置了bean 的作用范围, PostConstruct 注解指定了beaninit 方法。关于其它的一些注解配置参文档(3.11-3.12 )。 它的junit 测试代码如下: public class AutoScanTest {

        @Test

        public void base () {

           ApplicationContext ctx = new ClassPathXmlApplicationContext( "beans.xml" );

           UserServiceBean udb = (UserServiceBean) ctx.getBean( "usb" );

           udb.test();

        }

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

     

    五、 AOP 技术

    1. 引出问题

    建立spring_04_aop 项目 ,在该项目下有一个UserDao 接口,代码如下: package com.asm.dao;

    public interface UserDao {

        void save();

        void update();

    }

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

    package com.asm.dao.impl;

    import com.asm.dao.UserDao;

    public class UserDaoImp implements UserDao {

        private String username ;

        public UserDaoImp() {

        }

        public UserDaoImp(String username) {

           this . username = username;

        }

        public String getUsername() {

           return username ;

        }

        @Override

        public void save() {

           System. out .println( "save method is called " + username );

        }

        @Override

        public void update() {

           System. out .println( "update method is called" + username );

        }

    }

    需求如下:如果实现类的username!=null, 才可以调用saveupdate 方法,为null 则不能调用。当然要解决此问题,可以在saveupdate 方法内部进行判断,但是如果在方法内部进行判断,代码则失去了灵活性,如果以后的需求改变,比如变成username.equals 时,则又要在update/save 方法中重新进行一次判断。如果save/update 这样的方法很多,这样就会很麻烦。其实要解决此问题,可以通过动态代理技术实现。这里的需求其实就是根据要求来拦截一些业务方法,这种编程问题称之为横切性关注点

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

    2. 动态代理实现[JDK]

    建立代理工厂 ProxyFactory ,代码如下: package com.asm.dao.impl.factory;

    public class ProxyFactory implements InvocationHandler {

        private Object target ;

        public Object createUserDaoImp(Object target) {

           this . target = target;

    return Proxy.newProxyInstance ( this . target .getClass().getClassLoader(), this . target .getClass().getInterfaces(), this );

        }

        @Override

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

           UserDaoImp udi = (UserDaoImp) target ;

           Object result = null ;

           if (udi.getUsername() != null ) {

               result = method.invoke( target , args);

           }

           return result;

        }

    }

    简析动态代理:此类根据传递的target 对象来生成此目标对象的代理对象,需要强调的是动态代理技术所要代理的对象必须实现一个接口 newProxyInstance 参数说明:第一个参数是目标对象的类装载器,第二个参数是目标对象的接口,第三个参数是回调对象,生成的代理对象要执行方法时就是依靠这个回调对象的invoke 方法来进行目标对象的方法回调。关于动态代理的其它细节不在此讨论。

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

    public class AopProxyTest {

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

        public void testProxy(){

           ProxyFactory pf= new ProxyFactory();

           UserDao ud=(UserDao) pf.createUserDaoImp( new UserDaoImp());

           ud.save();

        }

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

        public void testProxy2(){

            ProxyFactory pf = new ProxyFactory();

        UserDao ud=(UserDao) pf .createUserDaoImp( new UserDaoImp( " 张某 " ));

            ud.save();

        }

    }

    3.cglib 实现代理

    JDK Proxy 实现代理要求被代理的目标对象必须实现一个接口,而如果目标对象没有实现接口则不能使用Proxy 来代理。其实也可以借助cglib 来实现代理。操作步骤如下 步骤一、建立 UserDaoImp2 类,它与 UserDaoImp 的唯一区别就是没有实现任何接口。 步骤二、导入cglib.jar 包,创建cglib 代理工厂,代码如下: package com.asm.dao.impl.factory;

    public class CglibFactory implements MethodInterceptor {

        private Object target ;

        public Object createUserDaoImp2(Object target) {

           this . target = target;

           Enhancer enhancer = new Enhancer();

           enhancer.setSuperclass( this . target .getClass()); // cglib 创建的代理对象,其实就是继承了要代理的目标类,然后对目标类中所有非 final 方法进行覆盖,但在覆盖方法时会添加一些拦截代码。

           enhancer.setCallback( this ); // 注册回调器

           return enhancer.create();

        }

        @Override

        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)

               throws Throwable {

           UserDaoImp2 udi = (UserDaoImp2) target ;

           Object result = null ;

           if (udi.getUsername() != null ) {

               // 前置通知

               try {

                  result = methodProxy.invoke( target , args);

                  // 后置通知

               } catch (Exception e) {

                  e.printStackTrace();

                  // 例外通知

               } finally {

                  // 最终通知

               }

           }

           return result;

        }

    }

    说明:注意注释的通知,通知就是拦截到方法后执行的一些代码,比如前置通知,就是说在回调目标方法时执行的一些操作。

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

    4.aop 理论知识

    横切性关注点:对哪些方法拦截,拦截后怎么处理,这些关注就称之为横切性关注点 切面(aspect ):类是对物体特征的抽象,而切面是指对横切性关注点的抽象。

    连接点(joinpoint: 被拦截到的点,因为spring 只支持方法类型的连接点,所以在spring 中连接点指的就是被拦截到的方法。实际上连接点还可以是字段或构造器。 切入点(pointcut ):对连接点进行拦截的定义。 通知(advice ):所谓通知就是指拦截到连接点之后的要执行的代码。通知分为前置、后置、异常、最终。环绕通知五类。 目标对象:代理的目标对象。 织入(weave ):将切面应用到目标对象并导致代理对象创建的过程 引入(introduction ):在不修改代码的前提下,引入可以在运行期 为类动态地添加一些方法或字段。

    5. 基于springAOP 实现

    步骤一、导入spring 开发的基本包(包括切面及注解包) 步骤二、编写切面类TheInterceptor ,代码如下:

    package com.asm.dao.impl.factory;

    @Aspect

    public class TheInterceptor {

        @Pointcut ( "execution (* com.asm.dao.impl.UserDaoImp.*(..))" )  

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

        private void anyMethod() {

        }

     

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

        public void before() {

           System. out .println( " 前置通知 " );

        }  

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

        public void afterReturning(){

           System. out .println( " 后置通知 " );

        }  

    }

    简析:Aspect 注解声明此类为一个切面类,Pointcut 注解用来声明一个切入点,括号中的参数是切入点的表达式,这里的表达式的意思是对UserDaoImp 类的所有方法进行拦截。关于切入点表达式后面会有详细的说明。anyMethod 是为切入点起一个名字,后面的“通知”都要依赖这个名字。 步骤三、beans.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:context = "http://www.springframework.org/schema/context"

        xmlns:aop = "http://www.springframework.org/schema/aop"

        xsi:schemaLocation = "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/aop           http://www.springframework.org/schema/aop/spring-aop-2.5.xsd" >

        < aop:aspectj-autoproxy /> <!-- 开启切面编程功能 -->

        < bean id = "userDaoImp" class = "com.asm.dao.impl.UserDaoImp" />   

    < bean id = "theInterceptor"

           class = "com.asm.dao.impl.factory.TheInterceptor" />

    </ beans >

    说明:要想切面类起作用,首先要把切面类纳入spring 容器管理。 步骤四、编写junit 测试单元     @Test

    public void base() {

    ApplicationContext ctx = new ClassPathXmlApplicationContext ( "beans.xml" );   

        // UserDaoImp udii=(UserDaoImp) ctx.getBean("userDaoImp");

        UserDao ud = (UserDao) ctx.getBean( "userDaoImp" );

        System. out .println(ud.getClass().getName());

        ud.save();

    } 说明:由于开启了切面编程功能,所以当我们获取一个被切面类监控管理的 bean 对象 —UserDaoImp 时,它实际上获取的是此对象的一个代理对象,而在 spring 中对代理对象的处理有如下 原则 :( 1 )如果要代理的对象实现了接口,则会按照 Proxy 的方式来产生代理对象,这即是说产生的代理对象只能是接口类型,比如起用上面注掉的代码就会报错,而且通过下面的打印语句我们也可以看出产生的是一个代理对象。( 2 )要代理的对象未实现接口,则按 cglib 方式来产生代理对象。   另还要注意:要想 spring 的切面技术起作用,被管理的 bean 对象只能是通过 spring 容器获取的对象。比如这里如果直接 new UseDaoImp 对象,则 new 出的对象是不能被 spring 的切面类监控管理。 补充 :测试被代理对象未实现接口时,spring 切面技术的应用。 步骤一、修改切面类 TheInterceptor 切入点为如下内容: @Pointcut ( "execution (* com.asm.dao.impl.*.*(..))" 说明:拦截 impl 包下的所有类所有方法 步骤二、在beans.xml 中增加如下内容: < bean id = "userDaoImp2" class = "com.asm.dao.impl.UserDaoImp2" /> 步骤三、junit 测试代码如下: public void base2() {

        ApplicationContext ctx = new ClassPathXmlApplicationContext( "beans.xml" );

        UserDaoImp2 udi2 = (UserDaoImp2) ctx.getBean( "userDaoImp2" );

        System. out .println(udi2.getClass().getName());

        System. out .println(udi2.getClass().getSuperclass ().getName());

        udi2.save();

    } 说明: UseDaoImp2 未实现任何接口,因此在 spring 中利用切面技术来管理此类使用的动态代理技术实质是 cglib 的动态代理方式,所以产生的代理对象实质是被代理对象的一个子类,通过上面的控制台打印语句可以看出。 小结:( 1 )声明 aspect 的切面类要纳入 spring 容器管理才能起作用。( 2 )被管理的 bean 实例要通过容器的 getBeans 方法获取。 3 )依据被管理的 bean 是否实现接口, spring 采取两种方式来产生代理对象。( 4 )在 xml 文件中启用 <aop:aspectj-autoproxy/>

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

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

    1 )最终通知

    在切面类 TheInterceptor 中增加如下代码即可:略去测试。 @After ( "anyMethod()" ) // 最终通知

        public void after() {

        System. out .println( " 最终通知 " );

    }

    2 )异常通知

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

    int i=1/0; 在然后在切面类 TheInterceptor 中增加如下代码: @AfterThrowing ( "anyMethod()" )   // 例外通知

    public void AfterThrowing() {

        System. out .println( " 例外通知 " );

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

    3 )环绕通知

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

    public Object around(ProceedingJoinPoint pjp) throws Throwable {

        System. out .println( " 进入环绕 " );

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

        Object result = pjp.proceed();

        //}

        System. out .println( " 退出环绕 " );

        return result;

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

    4 )传递参数给通知

    首先在UseDao 接口中增加如下代码: String add(String name); 然后再在UserDaoImp 中实现此方法,代码如下: public String add(String name) {

        System. out .println( "add method is called [ " + name+ " ]" );

        return " 添加成功 " ;

    } 需求:获取调用add 方法传递的参数。操作步骤如下:在切面类增加如下代码: @Before ( "anyMethod() && args(name)" ) // 前置通知 , 只针对 UseDaoImp add 方法

    public void beforeAdd(String name) {

        System. out .println( " 前置通知 :" + name);

    } 说明:在前置通知的方法中有一个参数,然后再把此参数作为拦截条件(即是说拦截带有一个 String 类型参数的方法)。 args 的名字和 beforeAdd 方法参数名字相同。 测试代码: public void advieeTest () {

        ApplicationContext ctx = new ClassPathXmlApplicationContext( "beans.xml" );

        UserDao ud=(UserDao) ctx.getBean( "userDaoImp" );

        ud.add( "xxx" );

    }

    5 )获取方法的返回值

    我们知道add 方法有一个返回值,我们对此方法进行拦截并获取返回值,在切面类中增加如下代码: @AfterReturning (pointcut = "anyMethod()" , returning = "result" )

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

    public void afterReturningRes(String result) {

        System. out .println( " 后置通知 , 返回结果 :" + result);

    } 说明: afterReturningRes 方法的参数就是要返回的参数类型, returning 标记的就是的结果,它的取值与该方法参数名相同。   测试代码同( 4 )。

    6 )获取抛出的异常

    切面类的增加如下代码:

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

    public void catchException(Exception e){

        System. out .println( " 获取抛出的异常: " +e); 

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

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

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

    步骤二、建立beansXML.xml 配置文件,内容如下: < aop:aspectj-autoproxy /> <!-- 开启切面编程功能 -->

    < bean id = "userDaoImp" class = "com.asm.dao.impl.UserDaoImp" />

    < bean id = "aspectBean"

        class = "com.asm.dao.impl.factory.TheInterceptorXML" />

    < aop:config >

    < aop:aspect id = "asp" ref = "aspectBean" >     -- 声明一个切面类

           < aop:pointcut id = "thecut"                  -- 声明一个切入点

           expression = "execution(* com.asm.dao.impl.UserDaoImp.*(..))" />

           < aop:after-returning pointcut-ref = "thecut" method = "afterReturningRes" returning = "result" />

           < aop:around pointcut-ref = "thecut" method = "around" />

           < aop:after-throwing pointcut-ref = "thecut" method = "catchException" throwing = "e" />                  

           < aop:after pointcut-ref = "thecut" method = "after" />

           < aop:before pointcut-ref = "thecut" method = "before" />

        </ aop:aspect >

    </ aop:config >

    测试代码如下: public void advieeTest() {

           ApplicationContext ctx = new ClassPathXmlApplicationContext( "beansXML.xml " );

           UserDao ud=(UserDao) ctx.getBean( "userDaoImp" );

           ud.add( "xxx" );      

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

    8. 解析切入点表达式

    1. 格式: execution( 返回值 空格 方法选择) 。两部分组成,中间一定要有空格 返回值:可以是* ,说明拦截任何方法。Java.lang.String( 全名) ,拦截返回值为String 类型的方法。 常用的实例如下:

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

    表达式(省略 execution

    说明

    (java.lang.String 方法选择略 )

    拦截返回值为 String 类型的方法

    (!void  方法选择略 )

    拦截返回值非空的方法

    (* com.asm..*.*(..))

    拦截 com.asm 包及子包下每个类的全部方法

    (* com.asm.*.*(..))

    拦截 com.asm 包下每个类的全部方法

    (* com.asm.User.*(..))

    拦截 asm 包下 User 类的所有方法

    (* com.asm.User.* (java.lang.String,..))

    拦截 User 类中第一个参数为 String, 后面参数任一的方法

    待增加

     

    待增加

     

    9. 总结

    面向切面的常见应用(如权限拦截)、springaop 依赖两种方式实现代理(依被代理的对象是否实现接口而定)、通知概念、基于注解与基于XML 两种方式来配置切面、基本步骤(依要拦截的方法来设定切入点,依据业务需求实现拦截通知代码,切面纳入spring 容器管理,要被监控的类只能是通过Spring 容器获取)、切入点的格式。

    六、与 JDBC 集成

    1. 搭建环境

    建立spring_05_integrationJdbc 项目 ,此项目使用dbcp 作为数据源来整合JDBC 技术。因此除了spring 所需的jar 包,还应导入dbcpjar 包:commons-dbcp-1.2.2.jar 及此 jar 所依赖的两个apache 开源jar 包:commons-collections-3.1.jarcommons-pool.jar 。由于涉及到数据库操作,还应导入数据库驱动包,这里选择的是mySQL 驱动。

    2. 基于注解的事务管理

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

    建立beans.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:aop = "http://www.springframework.org/schema/aop"

        xmlns:tx = "http://www.springframework.org/schema/tx"

        xmlns:context = "http://www.springframework.org/schema/context"

        xsi:schemaLocation = "

           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

           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/context http://www.springframework.org/schema/context/spring-context-2.5.xsd " >

    < bean id = "theDatasource"

           class = "org.apache.commons.dbcp.BasicDataSource" >

        < property name = "driverClassName" value = "com.mysql.jdbc.Driver" />

        < property name = "url" value = "jdbc:mysql://localhost:3306/sjdbc" />

        < property name = "username" value = "root" />

        < property name = "password" value = "123456" />

        < property name = "initialSize" value = "2" />

        < property name = "maxActive" value = "100" />

        < property name = "maxIdle" value = "2" />

        < property name = "minIdle" value = "1" />

    </ bean >

    < bean id = "txManager"     class = "org.springframework.jdbc.datasource.DataSourceTransactionManager" >

        < property name = "dataSource" ref = "theDatasource" />

    </ bean >

    < tx:annotation-driven transaction-manager = "txManager" />

    说明:首先是配置了一个数据源,以供数据源事务管理器 配置引用。接着配置了数据源事务管理器,随后开启了基于 @Transactional 注解的事务管理器,开启后,只要是被 spring 管理的 bean 且打有 @Transactional 注解的 bean 都会受配置的事务管理。关于这里的配置可参看 spring 文档 9.5.6

    步骤二、准备使用环境。

    建立 UserDao 接口,代码如下: package com.asm.dao;

    public interface UserDao {

        void save(User user);

        void delete (User user);

        void update(User user);

        User get( int id);

        List<User> getUsers();

    }

    建立 UserDaoImp 类实现 UseDao 接口,代码如下: package com.asm.dao.impl;

    @Transactional

    public class UserDaoImp implements UserDao {

        private JdbcTemplate jdbcTemplate ;

     

        public void setDatasouce(DataSource datasource) {

           jdbcTemplate = new JdbcTemplate(datasource);

        }

        public void delete(User user) {

           jdbcTemplate .update( "delete from user where id=?" , new Object[] { user.getId() },

                  new int [] { java.sql.Types. INTEGER });

        }

     

        public User get( int id) {

           return (User) jdbcTemplate .queryForObject( "select * from user where id=?" ,

                  new Object[] { id }, new int [] { java.sql.Types. INTEGER }, new RowMapper() {

                      public Object mapRow(ResultSet rs, int arg1) throws SQLException {

                         User user = new User();

                         user.setId(rs.getInt( "id" ));

                         user.setName(rs.getString( "name" ));

                         return user;

                      }

                  });

        }

     

        @SuppressWarnings ( "unchecked"

        public List<User> getUsers() {

           return (List<User>) jdbcTemplate .query( "select * from user" , new RowMapper() {

              

               public Object mapRow(ResultSet rs, int arg1) throws SQLException {

                  User user = new User();

                  user.setId(rs.getInt( "id" ));

                  user.setName(rs.getString( "name" ));

                  return user;

               }

           });

        }

     

        public void save(User user) {

           jdbcTemplate .update( "insert into user(name) values(?)" , new Object[] { user.getName() },

                  new int [] { java.sql.Types. VARCHAR });

        }

     

        public void update(User user) {

           jdbcTemplate .update( "update user set name=? where id=?" , new Object[] { user.getName(),

                  user.getId() }, new int [] { java.sql.Types. VARCHAR , java.sql.Types. INTEGER });

        }

    }

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

    beans.xml 中增加对应的配置内容如下:

    < bean id = "userDaoImp" class = "com.asm.dao.impl.UserDaoImp" >

        < property name = "datasouce" ref = "theDatasource" />

    </ bean > 结合配置文件解析UserDaoImp 实现类:(1 )此类作为一个bean 实例纳入spring 容器管理,使用setter 注入方式完成对datasource 的注入,实质是完成的 JdbcTemplate 对象的初始化。(2 )该类CRUD 方法都使用了Spring 容器提供的 JdbcTemplate 对象来简化了CRUD 操作,在spring 文档的 11.2.1 . JdbcTemplate 类作了较详细的介绍。(3 )此类打上了 @Transactional 注解,表示此类中的业务方法都会受beans.xml 配置的 < tx:annotation-driven transaction-manager = "txManager" /> 管理

    步骤四、编写测试类

    package com.asm.test;

    public class TestSJ {

        private UserDao ud = (UserDao) new ClassPathXmlApplicationContext( "beans.xml" ).getBean( "userDaoImp" );

        public static void main(String[] args) {

           TestSJ sj = new TestSJ();

           sj.save();

    sj.delete();

        }

        public void save() {

           User user = new User();

           user.setName( " 张某某 " );

           ud .save(user);

        }

        public void delete() {

           User user = new User();

           user.setId(1);

           ud .delete(user);

        }

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

    }

    说明 注意这里通过 getBean 获取的是 UserDao 接口对象,而非 UserDao 接口的实现类 UserDaoImp 对象,因为 spring 的事务管理也是利用了 aop 技术,所以必须要面向接口,如果想通过 getBean 获取它的实现类对象将会报错。

    步骤五、感知事务管理。

    UserDaoImpdelete 方法中增加如下代码:

    jdbcTemplate .update( "delete from user where id=?" , new Object[] { user.getId() },   new int [] { java.sql.Types. INTEGER });

    int i=5/0;

    jdbcTemplate .update( "delete from user where id=2" );

    s pring 默认的事务管理方式:运行期异常进行事务回滚,非运行期异常不进行事务回滚 。因此增加上面的代码后,会出现ArithmeticException 运行期 异常。所以当出现此异常时,delete 方法中的数据库操作都会进行回滚,因而id=12 这两条记录都不会被删除。 如果把UseDaoImp 类前标记 @Transactional 的注解去掉,id=1 的记录会被删除,因为失去了spring 容器的事务管理。 小结spring 事务: 1 )在beans.xml 中配置事务管理器,可以是依赖于数据源的事务管理器,也可以其它的事务管理器(比如和JPA 集成的事务管理器等),这些事务管理器类都继承自 AbstractPlatformTransactionManager 抽象类。(2 )事务管理器配置后,要想让配置的事务管理器对某些类的事务管理起作用,可以有两种方式配置:一种是声明式配置(两种:基于注解或基于XML ),一种是编程是配置 。(3 )上面的一些操作,都是基于注解的声明式事务配置:关键两点:开启基于事务的注解支持( < tx:annotation-driven transaction-manager = "txManager" /> );被管理的类打上事务注解标记。(4 )除了可以在类前声明事务标记,也可以在类的方法中具体声明详细的事务标记。(5 )事务标记具有多个可选属性,具体可参文档 9.5.6 .1

    3. 简析事务注解属性

    @Transactional 注解具有大属性:

    1 )传播属性 propagation

    可选属性

    说明

    REQUIRED

    业务方法需要在一个事务中运行,如果方法运行时,已经处在一个事务中,那么加入到该事务,否则自己创建一个新的事务。

    REQUIRESNEW

    不管是否存在事务,业务方法总会为自己发起一个新的事务,如果方法已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,直到方法执行结束,新事务才算结束,原先的事务才恢复执行。

    SUPPORTS

    如果业务方法在某个事务范围内被调用,则业务方法成为该事务的一部分。如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行

    NOT_SUPPORTED

    声明业务方法不需要事务,如果方法没有关联到一个事务,容器不会为它开启事务,如果方法在一个事务中被调用,则该事务会被挂起,在该方法调用结束后,原先的事务恢复执行

    NEVER

    指定业务方法绝对不能在事务范围中执行,如果业务方法在某个事务中执行,容器会抛出例外;只有业务方法没有关联到任何事务,才能正常执行。

    MANDATORY

    指定业务方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果业务方法在没有事务的环境下调用,容器就会抛出例外

    NESTED

    如果一个活动的事务存在,它会自己产生一个单独的而且拥有多个可以回滚的保存点的事务,然后嵌套运行在活动的事务中,实质它可以看成是一个内部事务且具有回滚保存点,所以内部自身的事务回滚并不会引起外部活动事务的回滚,它只是回滚到内部事务的保存点。 如果没有活动事务,则按REQIIRED 属性执行。需要注意的是此配置只对DataSourceTransactionManager 事务管理器生效。

    说明:上面多次提到业务方法,它实质就是UserDaoImpsave 等这样的方法。但是这些方法前会有一个设定了详细属性的 @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 的路径下建立dbcp.properties 文件,内容如下: driverClassName= com.mysql.jdbc.Driver

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

    username= root

    password= 123456

    initialSize= 2

    maxActive= 100

    maxIdle= 2

    minIdle= 1 然后把此配置读取数据源bean 中去,形式如下: < bean id = "theDatasource"

        class = "org.apache.commons.dbcp.BasicDataSource" >

        < property name = "driverClassName" value = "${driverClassName}" />

        < property name = "url" value = "${url}" />

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

    < property name = "xxx" value = "${dbcp.properties 配置文件中的键 }" />

    ...

    </ bean >

    注意的是使用 ${} 来获取值,必须把properties 文件引入到spring 的容器管理,因此要想成功通过 ${} 来获取相应的值,还应beans.xml 中增加如下内容: < context:property-placeholder location = "classpath:dbcp.properties" /> 而要想使用 < context:XXX /> 还应在beans.xml 增加名称空间:xmlns:context="http://www.springframework.org/schema/context"

    schema 信息:http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd 关于这里的名称空间及schema 信息可参A2.8 节。

    5. 基于XML 的事务管理

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

    步骤二、建立beansXML.xml 文件,并配置事务管理器(和.2. 步骤一 完全相同)。 步骤三、配置事务 < aop:config >

    < aop:pointcut id = "txPointcut"

        expression = "execution(* com.asm.dao.impl.UserDaoImp2.*(..)) " />

            < aop:advisor advice-ref = "txAdvice" pointcut-ref = "txPointcut" />

    </ aop:config >

    < tx:advice id = "txAdvice" transaction-manager = "txManager" >

        < tx:attributes >

    < tx:method name = "get*" read-only = "true" />                                         --> 拦截 get 开头的方法,并设置只读属性

           < tx:method name = "*" />          --> 拦截所有方法   

        </ tx:attributes >

    </ tx:advice >

    解析 :根据txManager 事务管理器来配置一个事务通知器:txAdvice 。而<tx:method> 中的name 属性指定方法名(可以使用通配符),其它属性就好比注解的传播属性配置(可参.3(1) )。然后再使用切面配置一个切入点,再对这个切入点引入事务通知器。

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

    < bean id = "userDaoImp2" class = "com.asm.dao.impl.UserDaoImp2" >

           < property name = "datasouce" ref = "theDatasource" />

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

    5. 总结与JDBC 的集成

    1 )首先是配置好一个事务管理器 (2 )基于注解:一是涉及到事务的类打上事务注解标记,二是在xml 配置文件中开启事务注解功能;基于配置:事务通知器加进apo 配置中。(3 )涉及到事务的类必须纳入spring 容器管理,事务才能起作用。(4强调 :不论是基于注解还是基于xml 配置实现事务,都要依赖于动态代理技术。以UserDaoImp 说明,它实现了接口,所以它的代理对象实质是一个接口对象,因而通过getBean 获取的UseDaoImp 实质上是一个接口对象,所以特别要注意类型转换(参.2. 步骤四. 说明 )。(5 )为了简化CRUD 操作,我们通常会使用spring 提供的JdbcTemplate 类。

    七、 SSH 集成实例

    1. 分析实例及准备环境

    建立spring_06_SSH 项目 ,此项目主要演示struts1.xhibernatespring 的集成。集成前的一个比较关键的因素是搭建好环境,这一步要求我们对集成的框架所用到的jar 包有比较清楚的认识。下面列表展示了三个框架所用到的jar 包。 hibernate3.3 所用到的jar

    antlr-2.7.6.jar                   开源语法分析生成器(lib/required)

    commons-collections-3.1.jar       Commons 集合类库,与连接池有关(lib/required)

    dom4j-1.6.1.jar                   xml 解析类库(lib/required)

    javassist-3.9.0.GA.jar             分析,编辑和创建java 字节码类库(lib/required)

    jta-1.1.jar                       事务处理api   (lib/required)       

    slf4j-api-1.5.8.jar               日志处理 (lib/required)-->log4j 实现

    hibernate3.jar                    核心 ehcache-1.2.3.jar                  二级缓存(lib/optional/ehcache

    Spring2.5 安装包所用的jar 包: dist/spring.jar lib/aspectj/aspectjweaver.jar aspectjrt.jar

    lib/cglib/cgligb-nodep-2.1_3.jar lib/j2ee/common-annotations.jar

    lib/jakarta-commons/commons-logging.jar commons-dbcp.jar commons-pool.jar

    dist/modules/spring-webmvc-struts.jar

    lib/log4j/log4j-1.2.15.jar   srpinghibernate 使用的日志记录jar

    lib/slf4j/ slf4j-log4j12-1.5.0.jar  日志转换jar 包,实现把log4j.jar 包适配到slf4j 标准。

    struts1.10 所用到的jar

    导入lib 目录下的所有jar 包。但为了避免jar 包冲突,不要导入antlr.jarhibernate 已经导入) jar 包说明绿色字样是框架所需的一些基本包hibernate 的开发jar 包主要集中在required 目录和核心jar 包,还需要特别说明的是它的日志包问题:hibernate 使用了slf4j 来记录日志,但是slf4j 只是一个日志记录标准,需要具体的实现才可以进行日志记录,我们可以使用它本身的实现(如slf4j-simple-1.5.8.jar ),也可以使用其它的实现,如log4j 来记录日志,但是使用log4j 需要导入slf4j-log4j12-1.5.8.jar 来进行适配。在这里为了和spring 的日志记录结合,我们使用了log4j 的实现。而要使用log4j 日志记录,可以在springlib 子目录中找到。Spring 框架中除了常用的jar 包外,还增加了dbcp 连接池 相关的包,需要说明的是连接池的基本包为:dbcppoolcollections (在hibernate 中已导入)。为了与strus 相结合,还增加了一个spring-webmvc-struts.jar 包。 经验:spring 的作用是来集成其它的框架,所以对于许多集成所要用到的jar 包,在spring 中都能找到,因而对于项目中出现的类装载相关的错误,应首先查看jar 包是否冲突,是否导入了需要的包,建议集成时,其它框架只需导入它们自己所需的基本包,而对于集成所用的包都可以从spring 提供的lib 中查询。

    2. 集成spring+hibernate

    对于框架的集成最好的方式是分布集成,比如这里先集成spring+hibernate 并测试,测试通过后再来集成struts 步骤一、建立接口UserServeice ,代码如下:

    package com.asm.service;

    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<User> getUsers();

    }

    步骤二、对应的实现类 UserServiceBean ,代码如下: package com.asm.service.impl;

    public class UserServiceBean implements UserService {

        @Resource

        private SessionFactory sf ;

        @Transactional

        public void save(User user) {

           sf .getCurrentSession().persist(user);

        }

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

    注意事项 (1) 在每个方法上都要加上事务注解,特别是 save update 方法一定要加上事务注解,因为把 sessionFactory 对象是通过 spring 注入 ( 注解方式注入 ) ,而 spring 中配置的 sessionFactory 对象又纳入了 spring 容器的事务管理,所以要加上事务标记。 (2) 一定要通过 getCurrentSession 方法得到 session 对象,因为使用 openSession 方式得到的 Session 对象不受 spring 容器管理。 实体类 User 及相应的 User.hbm.xml 配置在此省略。

    步骤三、在 beans.xml 中进行配置,内容如下: < bean id = "theDatasource"

           class = "org.apache.commons.dbcp.BasicDataSource" >

        < property name = "driverClassName" value = "com.mysql.jdbc.Driver" />

        < property name = "url" value = "jdbc:mysql://localhost:3306/ssh" />

        < property name = "username" value = "root" />

        < property name = "password" value = "123456" />

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

    </ bean >

     

    < bean id = "txManager"

    class = "org.springframework.orm.hibernate3.HibernateTransactionManager" >

           < property name = "sessionFactory" ref = "sessionFactory" />

    </ bean >

    < tx:annotation-driven transaction-manager = "txManager" />

     

    < bean id = "sessionFactory"

    class = "org.springframework.orm.hibernate3.LocalSessionFactoryBean" >

           < property name = "dataSource" ref = "theDatasource" />

           < property name = "mappingResources" >

               < list >

                  < value > com/asm/entity/User.hbm.xml </ value >

               </ list >

           </ property >

           < property name = "hibernateProperties" >

               < value >

                  hibernate.dialect=org.hibernate.dialect.MySQLDialect

                  hibernate.hbm2ddl.auto=update

                   hibernate.show_sql=true

                  hibernate.format_sql=true             

               </ value >

           </ property >

    <!—

    如果想就把此 bean 对应的属性配置放到 hibernate.cfg.xml 中去配置,可以在 hibernate.cfg.xml 中配好后,再使用下面的配置加载 hibernate.cfg.xml 配置文件:

    <property name="configLocation" value="hibernate.cfg.xml" />

           -->

        </ bean >

       

    < bean id = "userServiceBean" class = "com.asm.service.impl.UserServiceBean" />

    说明:这里配置的 sessionFactory 实质就是对 hibernate sessionFactory 进行了一次包装,它的配置依赖于一个数据源实例 bean ,最后再把 sessionFactory 纳入 spring 容器的事务管理,但是要注意的是这时使用的是事务管理器类为 HibernateTransactionManager 配置简图:

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

    public class UserServiceTest {

        private static UserService us ;

        @BeforeClass

        public static void setUpBeforeClass() throws Exception {

           ApplicationContext ctx= new ClassPathXmlApplicationContext( "beans.xml" );

           us =(UserService) ctx.getBean( "userServiceBean" );

        }

        @Test

        public void testSave() {

           us .save( new User( " 李某某 " ));

        }

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

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

    3. 集成struts 框架

    首先准备好struts 框架的基本配置环境:(1 )在web.xml 中配置struts 的核心控制器Actionservlet (配置代码省略)。 (2 )在struts-config.xml 中建立strus-config.xml 配置文件,它的配置内容如下: <? xml version = "1.0" encoding = "ISO-8859-1" ?>

    <! DOCTYPE struts-config PUBLIC

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

             "http://struts.apache.org/dtds/struts-config_1_3.dtd" >

    < struts-config >

    < action-mappings >

        < action path = "/list" type = "com.asm.action.UserAction" >

           < forward name = "success" path = "/WEB-INF/page/showUser.jsp" ></ forward >

        </ action >

    </ action-mappings >

    </ struts-config >

    3 )上面所用到的UserAction 的代码如下: package com.asm.action;

    public class UserAction extends Action {

        @Override

        public ActionForward execute(ActionMapping mapping, ActionForm form,

               HttpServletRequest request, HttpServletResponse response) throws Exception {

             WebApplicationContext ctx = WebApplicationContextUtils

             .getWebApplicationContext ( this . servlet .getServletContext());

             UserService us = (UserService) ctx.getBean( "userServiceBean" );

             List<User> users = us.getUsers();

           request.setAttribute( "users" ,users);

           return mapping.findForward( "success" );

        }

    }

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

    < context-param >

        < param-name > contextConfigLocation </ param-name >

           < param-value > classpath:beans.xml </ param-value >

    </ context-param >

    < listener >

        < listener-class >

               org.springframework.web.context.ContextLoaderListener

        </ listener-class >

    </ listener >

    此监听器的作用就是在启动时把spring 容器进行实例化,并把spring 容器实例 WebApplicationContext 对象 放到application 作用域中,大致进行类似这样的操作:servletContext.setAttribute( WebApplicationContext. ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE ,spring 容器实例WebApplicationContext 对象) 。因此我们也可以通过保存的名字来获取 WebApplicationContext 对象,但是为了方便, spring 提供一个工具类 WebApplicationContextUtils 来获取此对象,关于这个工具类的使用,在 UserAction 就是使用了此工具类来获取 spring 容器实例。保存一个 List<User> 对象后,在 showUser.jsp 遍历 List 对象,代码如下:

    < c:forEach items = "${users} " var = "user" >

           ${user.id } -- ${user.name } < br />

    </ c:forEach >

    4. 改进集成struts 框架

    上面的代码其实并非整合struts 的最佳方式,整合struts 的优雅方式应该是把Action 也纳入spring 容器管理。具体操作如下:

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

    @Resource

    private UserService userService ;

    @Override

    public ActionForward execute(ActionMapping mapping, ActionForm form,

        HttpServletRequest request, HttpServletResponse response) throws Exception {

        request.setAttribute( "users" , userService .getUsers());

        return mapping.findForward( "success" );

    }

    说明 :上面的userService 属性通过spring 的注解方式注入,这样可以避免获取UserService 对象的繁琐过程。 步骤二 、在beans.xml 配置UserAction ,纳入spring 容器管理,配置内容如下:

    < bean name = "/list" class = "com.asm.action.UserAction" />

    说明 :此实例 bean 的配置使用了 name 属性,而未使用 id 属性,因为 id 不支持“ / ”这样的特殊字符。还需要说明的是这里的 name 之所以取值为“ /list ”,是因为纳入 spring 容器管理的 Action 的取名必须保证与 action path 属性值相同(为什么如此做,后面有解释)。 步骤三 、添加一个 ActionServet 的处理器类。 Struts1.x 中的 ActionServlet 最终其实是把请求派发给 RequestProcessor 类来处理,而如果想自己编写处理请求类,可以在 struts-config.xml 中配置一个 <controller> 来处理这些请求。 Spring 提供了一个 DelegatingRequestProcessor 类,它继承自 RequestProcessor 类,它除了具备父类的功能,还主要完成一件重要的工作:把 ActionServlet 派发过来的请求交给在 spring 中配置的 Action bean 实例来处理(由于最终它是把请求交给了 spring 中配置的 action bean 实例处理,所以在 struts-config.xml 中关于此 action type 属性可以不写)。因此我们还应 struts-config.xml 的中增加如下配置代码: < controller >

    < set-property property = "processorClass"

    value = "org.springframework.web.struts.DelegatingRequestProcessor" />

    </ controller >

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

    < controller processorClass = "org.springframework.web.struts.DelegatingRequestProcessor" />

    说明了为什么要在 beans.xml 配置的 UserAction 的名字为“ /list

     

    5. 使用二级缓存

    步骤一、在使用hibernate 框架时,可以为hibenate 配置一个第三方缓存。通常的做法是在hibernate 的主配置文件中开启缓存相关的功能。在此项目中,由于是集成了spring 框架,所以我们可以在spring 容器的beans.xml 中的sessionFactory bean 实例的 hibernateProperties 属性中增加如下配置: cache.use_second_level_cache=true         --> 开启二级缓存

    hibernate.cache.use_query_cache=false    --> 不使用查询缓存

    hibernate.cache.provider_class=org.hibernate.cache.EhCacheProvider

                                             --> 第三方缓存实现类           

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

    <? xml version = "1.0" encoding = "UTF-8" ?>

    <!--

        defaultCache 节点为缺省的缓存策略

         maxElementsInMemory 内存中最大允许存在的对象数量

         eternal 设置缓存中的对象是否永远不过期

         overflowToDisk 把溢出的对象存放到硬盘上

         timeToIdleSeconds 指定缓存对象空闲多长时间就过期 , 过期的对象会被清除掉

         timeToLiveSeconds 指定缓存对象总的存活时间

         diskPersistent jvm 结束是是否持久化对象

         diskExpiryThreadIntervalSeconds 指定专门用于清除过期对象的监听线程的轮询时间

      -->

    < ehcache >

        < diskStore path = "C:/cache" />

        < defaultCache  maxElementsInMemory = "1000" eternal = "false" overflowToDisk = "true"

            timeToIdleSeconds = "120"

            timeToLiveSeconds = "180"

            diskPersistent = "false"

            diskExpiryThreadIntervalSeconds = "60" />

        < cache name = "com.asm.entity.User" maxElementsInMemory = "100" eternal = "false"

        overflowToDisk = "true" timeToIdleSeconds = "300" timeToLiveSeconds = "600" diskPersistent = "false" />

    </ ehcache >

    步骤三、在User.htm.xml 中的<class> 目录下增加如下配置:

    < cache usage = "read-write" region = "com.asm.entity.User" />

    说明 :这里的region 的值和ehcache.xml 配置下的<cache> 中的name 属性值相同。结合echcache.xml 作如下说明: 通常情况下,实体类的二级缓存配置通常使用默认的<defaultCache> 这个配置,但是如果想使用特有的缓存配置,可以用实体类的<cacche  regin> 来和ehcache.xml 中的<cache name> 关联。

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

        public void testGetUser() {

           System. out .println( us .getUser(1).getName());

           System. out .println( " 关闭数据库服务 " );

           try {

               Thread.sleep (1000*40);

           } catch (InterruptedException e) {

               e.printStackTrace();

           }

           System. out .println( " 关闭数据库准备从缓存中获取 " );

           System. out .println( us .getUser(1).getName());

        }

    说明:当执行到控制台显示“关闭数据库服务”时,我们手工停掉数据库服务,休眠 40 秒后,仍能获取数据,证明是从缓存中取得的数据。

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

    6. 乱码问题

    当前台传递的数据包含中文时,获取这些中文数据时会出现乱码。大致原因 如下:在struts 框架中,我们把配置的*.do 的请求会被ActionServlet 拦截到,而访问ActionServlet 时用到的默认编码为ISO8859-1 , 即是说设置了 request.setCharacterEncoding (“ ISO8859-1 ”) 。这时ActionServlet 在获取参数时就会得到乱码,ActionServlet 填充ActionForm 时就把乱码填充进ActionForm 。当填充完毕后会把*.do 的请求派发给相应的Action,ActionActionForm 中获取数据时自然就出现了乱码。解决此问题很简单,只需把spring 提供的一个过滤器类配置到web.xml 中,即增加如下代码:

    < filter >

        < filter-name > encodingFilter </ filter-name >

        < filter-class >

           org.springframework.web.filter.CharacterEncodingFilter 

        </ filter-class >

        < init-param >

           < param-name > encoding </ param-name >

           < param-value > UTF-8 </ param-value >

        </ init-param >

    </ filter >

    < filter-mapping >

        < filter-name > encodingFilter </ filter-name >

        < url-pattern > /* </ url-pattern >

    </ filter-mapping >

    在以前我们是自己编写此filter 类,而spring 为我们提供了CharacterEncodingFilter 类,我们就自需配置即可。另要注意在配置时一定要指定一个encoding 参数名及值. 补充 :关于这里的乱码问题可参“struts1.x 深入学习笔记 - 四、2. 步骤五. 问题 ”。

    7.OSIV

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

    org.springframework.orm.hibernate3.support.OpenSessionInViewFilter

     

    八、 SSJ 集成实例

    1. 搭建环境

    hibernate 核心包

    /lib/bytecode/cglib/Hibernate-cglib-repack.jar

    hibernate 注解包

    hibernate-annotations.jar

    Lib/Ejb3-persistence.jar hibernate-commons-annotations.jar

    Hibernate 针对JPA 的实现包

    Hibernate-entitymanager.jar

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

    2. 集成spring+JPA

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

    步骤一、搭建JPA 开发环境

    src 下建立META-INF/persistence.xml 文件,此文件的主要内容如下: < persistence xmlns = "http://java.sun.com/xml/ns/persistence"

        xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"

        xsi:schemaLocation = "http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"

        version = "1.0" >

        < persistence-unit name = "jpaDemo"

           transaction-type = "RESOURCE_LOCAL" >

           < provider > org.hibernate.ejb.HibernatePersistence </ provider >

           < properties >

               < property name = "hibernate.dialect"

                  value = "org.hibernate.dialect.MySQL5Dialect" />

               < property name = "hibernate.hbm2ddl.auto" value = "update" />

               < property name = "hibernate.show_sql" value = "true" />

               < property name = "hibernate.format_sql" value = "true" />

               <!-- 配置要连接的数据库信息: -->

           < property name = "hibernate.connection.driver_class"

                  value = "com.mysql.jdbc.Driver" />

            < property name = "hibernate.connection.url"

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

           < property name = "hibernate.connection.username" value = "root" />

    < property name = "hibernate.connection.password" value = "123456" />

        </ properties >

        </ persistence-unit >

    </ persistence >

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

    package com.asm.entity;

    @Entity

    public class User {

        @Id

        @GeneratedValue

        private Integer id ;

        @Column (name= "u_name" )

        private String name ;

     

        public User() {

        }

     

        public User(String name) {

           this . name = name;

        }

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

        @GeneratedValue @Column

    }

    步骤二、把JPA 纳入spring 容器管理 beans.xml 中的配置如下:

    < context:annotation-config />

    < bean id = "localEMF"

    class = "org.springframework.orm.jpa.LocalEntityManagerFactoryBean" >

        < property name = "persistenceUnitName" value = "jpaDemo" />

    </ bean >

    < bean id = "txManager"

        class = "org.springframework.orm.jpa.JpaTransactionManager" >

        < property name = "entityManagerFactory" ref = "localEMF" />

    </ bean >

    < tx:annotation-driven transaction-manager = "txManager" />

    说明:localEMF 实例bean 的使用是把JPApersistence.xml 配置的jpaDemo 持久化单元加进JPA 实体工厂管理( LocalEntityManagerFactoryBean 包装了JPA 所使用的 EntityManager 对象的工厂类 EntityManagerFactoryBean )。再把工厂类加进事务管理器。

    步骤三、UserServiceBean 的代码如下:

    package com.asm.service.impl;

    public class UserServiceBean implements UserService {

        @PersistenceContext

        private EntityManager em ;

        @Transactional

        public void save(User user) {

           em .persist(user);

        }

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

    }

    说明: em 属性依赖于 spring 容器注入。只要把此类交给 spring 容器管理, spring 容器会根据 @PersistenceContext 注解,来用配置的产生 EntityManager 对象的 JPA 工厂类来完成此对象的注入。 强调 的是在 save update 方法上一定要打上事务注解标记。

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

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

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

    4.OSIV 问题

    在集成hibernate 时,OSIV 指的是sesion 关闭后的的解决方案。而在使用JPA 时,取代session 的是 EntityManager , 所以要解决因 EntityManager 关闭的OSIV 问题,应使用spring 提供的 org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter

     

    九、SSH2 集成实例

    1. 搭建环境并集成hibenate

    基本环境SSH 集成相同,只是要导入开发struts2 的开发包(在2.1.8 需要导入6 个基本包),hibenate 的集成可以前面的项目说明。

    2. 集成strust2

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

    package com.asm.action;

    public class UserAction extends ActionSupport {

        @Resource

        private UserService userService ;

        private String username ;

        @Override

        public String execute() throws Exception {

           User user= new User( username );

           userService .save(user);

           return "success" ;

        }

    ... 省略 username get/set 方法

    }

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

    < bean id = "userAction" class = "com.asm.action.UserAction" />

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

    <? xml version = "1.0" encoding = "UTF-8" ?>

    <! DOCTYPE struts PUBLIC

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

        "http://struts.apache.org/dtds/struts-2.0.dtd" >

    < struts >   

        < constant name = "struts.devMode" value = "true" />

        < package name = "baseSSH" namespace = "/" extends = "struts-default" >

           < action name = "add" class = "com.asm.action.UserAction" >

               < result name = "success" > /success.jsp </ result >

           </ action >

        </ package >

    </ struts >

    步骤二、配置一个监听器,实例化spring 容器,和集成struts1 相同。即在web.xml 中增加如下代码:

    < context-param >

        < param-name > contextConfigLocation </ param-name >

        < param-value > classpath:beans.xml </ param-value >

    </ context-param >

    < listener >

        < listener-class >

           org.springframework.web.context.ContextLoaderListener

        </ listener-class >

    </ listener >

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

    < form action = "<%= request.getContextPath() %> /add.action ">

        用户名: < input type = "text" name = "username" >

        < input type = "submit" value = " 提交 " >    

    </ form >

    步骤四、发布测试。

    十、整合总结:

     

    十一、错误总结:

    1. 使用new ClassPathXmlApplicationContext 对象时未传递 beans.xml 配置文件出错: BeanFactory not initialized or already closed

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

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

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

     

     


    最新回复(0)