Spring基础入门
一、基础知识
1.依赖注入、控制反转
依赖注入:在运行期,由外部容器动态地将依赖对象注入到组件中
控制反转:应用本身不负责依赖对象的创建及维护,依赖对象的创建及维护是由外部窗口负责得。这样控制权就由应用转移到了外部容器,控制权的转移就是所谓的反转。
2.spring 的主要特性。
(1)降低组件之间的耦合度,实现软件各层之间的解耦。
(2)可以使用容器提供的众多服务,如:事务管理服务、消息服务、JMS 服务、持久化服务等等。
(3)容器提供单例模式支持,开发人员不再需要自己编写实现代码。
(4)容器提供了AOP 技术,利用它很容易实现如权限拦截,运行期监控等功能。
(5)容器提供的众多辅作类,使用这些类能够加快应用的开发,如:JdbcTemplate、HibernateTemplate.
(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 https://www.sodocs.net/doc/3d8131241.html,erDao;
public class UserDaoImpl implements UserDao{
public void save() {
System.out.println("执行save方法...");
}
}
(3)在src目录下配置此beans.xml,它的配置如下:
xmlns:xsi="https://www.sodocs.net/doc/3d8131241.html,/2001/XMLSchema-instance" xsi:schemaLocation="https://www.sodocs.net/doc/3d8131241.html,/schema/beans https://www.sodocs.net/doc/3d8131241.html,/schema/beans/spring-beans-2.5.xsd">
说明: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 值为:https://www.sodocs.net/doc/3d8131241.html,/schema/beans/spring-beans-2.5.xsd
(4)Junit 测试单元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 静态工厂的配置如下:
相应的工厂类代码如下:
package com.asm.dao.impl;
public class UserDaoImplFactory {
public static UserDaoImpl getUserDaoImpl(){
return new UserDaoImpl();
}
}
bean实例工厂的配置如下:
factory-method="getUserDaoImpl"/> 相应的工厂类的代码如下: package com.asm.dao.impl; public class UserDaoImplFactory2 { public UserDaoImpl getUserDaoImpl() { return new UserDaoImpl(); } } 5.bean 的作用域 singleton:返回bean 的同一个实例,也是默认的作用域(无状态bean 使用此作用域)prototype:每次请求都会创建一个实例(有状态bean 使用此作用域) request、session、global session 这三个作用域主要用在web 应用中 6.bean 的生命周期 (1)什么时候初始化bean 实例 当scope=singleton,即默认情况,会在装载配置文件时实例化。如果希望在调用getBean 时才初始化,可以使用lazy-init="true" 补充:如果希望希望该配置文件中的所有bean 都延迟初始化,则应在beans 根结点中使用lazy-init="true"。 当scope=prototype 时,在调用getBean()方法时才会初始化。 (2)生命周期: 构造器、init 方法、获取bean 后的操作、destroy 方法(ctx.close、注意如果bean 的scope 设为prototype 时,当ctx.close 时,destroy 方法不会被调用) 7.属性注入Setter 方式 (1)简单属性(如String):
(2)对象属性-外部bean 注入:在上面的
对象属性-内部bean注入:在上面的
(3)集合属性注入:
注意:在相应的字段上一定要有setter 方法,才能注入。
补充:使用继承。在beans.xml中的配置如下:
相当于在XXX bean 实例中也有username 属性设置。
8.属性注入构造器方式
UserServiceBean对应的构造方法代码如下:
public UserServiceBean(String username, UserDao userDao, Set
https://www.sodocs.net/doc/3d8131241.html,ername=username;
https://www.sodocs.net/doc/3d8131241.html,erDao=userDao;
this.set=set;
}
注意:此方法会覆盖掉默认的构造方法,导致要依赖默认构造方法的配置不可用,因此我们还应为此类提供一个默认的构造器。
三、使用注解方式注入
1.准备
注解方式的注入主要针对对象属性的注入。
使用注解功能要引用注解包,另beans.xml的配置模板如下:
xmlns:xsi="https://www.sodocs.net/doc/3d8131241.html,/2001/XMLSchema-instance" xmlns:context="https://www.sodocs.net/doc/3d8131241.html,/schema/context" xsi:schemaLocation="https://www.sodocs.net/doc/3d8131241.html,/schema/beans https://www.sodocs.net/doc/3d8131241.html,/schema/beans/spring-beans-2.5.xsd https://www.sodocs.net/doc/3d8131241.html,/schema/context https://www.sodocs.net/doc/3d8131241.html,/schema/context/spring-context-2.5.xsd" >
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) {
https://www.sodocs.net/doc/3d8131241.html,erDao2 = userDao2;
}
public void test() {
userDao.save();
userDao2.save();
}
}
然后在bean.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配置文
件中设置如下内容:
class="https://www.sodocs.net/doc/3d8131241.html,erServiceBean2" autowire="byType"/> 说明:除了byType外,autowire的可选属性如下: byName:根据类中的字段名来查找对应的bean,如不能成功注入,则字段设为null. byType:根据类型装配,如果发现多个类型都能够匹配,则抛出异常。Consturctor:也byType相似,不同之处在于它应用于构造器的参数,如果容器中没有找到 与构造器参数类型一致的bean,则抛出异常。 Autodetect:通过bean类的自省机制来决定是使用consturctor还是byType方式进行自动装 配。如果发现默认的构造器,那么将使用byType方式。 四、自动扫描管理bean 1.准备工作 在前面使用注解的时候,除了 们要使用某个bean实例,总会配置 下标住了@service (业务层组件)、@controller(控制层组件)、@repository(数据访问 组件)或@component(泛指组件)的类,并把它们作一个实例bean,相当于在beans.xml中配 置了 以在这些类中用到了注解配置时并不需要再配置 为什么提出自动扫描管理:在一些比较大的项目中,涉及到的bean实例会有很多,如果依次 对每个bean实例进行配置,不但配置内容繁琐,而且配置文件也会显得杂乱。因此spring 提出了自动扫描bean,它依据在xml文件中指定的包名和类中标记的component系列注解。 2.实例 建立spring_03_autoscan项目,内容基本和前面两个项目一样,只是在要纳入spring管理 的类前增加了component这样的注解。Beans.xml的配置如下: xmlns:context="https://www.sodocs.net/doc/3d8131241.html,/schema/context" xsi:schemaLocation="https://www.sodocs.net/doc/3d8131241.html,/schema/beans https://www.sodocs.net/doc/3d8131241.html,/schema/beans/spring-beans-2.5.xsd https://www.sodocs.net/doc/3d8131241.html,/schema/context https://www.sodocs.net/doc/3d8131241.html,/schema/context/spring-context-2.5. xsd" > 说明:以上的配置会自动管理service 包和impl 包及它们子包下的带有component 标记的类,上面两行配置代码等价于 下面以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) { https://www.sodocs.net/doc/3d8131241.html,erDao2 = 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注解指定了bean的init方法。关于其它的一些注解配置参文档(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 https://www.sodocs.net/doc/3d8131241.html,erDao; public class UserDaoImp implements UserDao { private String username; public UserDaoImp() { } public UserDaoImp(String username) { https://www.sodocs.net/doc/3d8131241.html,ername = 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,才可以调用save 与update 方法,为null 则不 能调用。当然要解决此问题,可以在save 与update 方法内部进行判断,但是如果在方法内 部进行判断,代码则失去了灵活性,如果以后的需求改变,比如变成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.基于spring 的AOP 实现 步骤一、导入spring 开发的基本包(包括切面及注解包) 步骤二、编写切面类TheInterceptor,代码如下: package com.asm.dao.impl.factory; @Aspect public class TheInterceptor { @Pointcut("execution (* https://www.sodocs.net/doc/3d8131241.html,erDaoImp.*(..))") // 声明一个切入点(第一个*后要留一个空格) 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 配置文件的内容如下: xmlns:context="https://www.sodocs.net/doc/3d8131241.html,/schema/context" xmlns:aop="https://www.sodocs.net/doc/3d8131241.html,/schema/aop" xsi:schemaLocation="https://www.sodocs.net/doc/3d8131241.html,/schema/beans https://www.sodocs.net/doc/3d8131241.html,/schema/beans/spring-beans-2.5.xsd https://www.sodocs.net/doc/3d8131241.html,/schema/context https://www.sodocs.net/doc/3d8131241.html,/schema/context/spring-context-2.5. xsd https://www.sodocs.net/doc/3d8131241.html,/schema/aop https://www.sodocs.net/doc/3d8131241.html,/schema/aop/spring-aop-2.5.xsd"> class="com.asm.dao.impl.factory.TheInterceptor" /> 说明:要想切面类起作用,首先要把切面类纳入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中增加如下内容: 步骤三、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 文件中启用 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配置文件,内容如下: class="com.asm.dao.impl.factory.TheInterceptorXML" /> expression="execution(* https://www.sodocs.net/doc/3d8131241.html,erDaoImp.*(..))" /> method="afterReturningRes" returning="result" /> method="catchException" throwing="e"/> 测试代码如下: public void advieeTest() { ApplicationContext ctx = new ClassPathXmlApplicationContext("beansXML.xml"); UserDao ud=(UserDao) ctx.getBean("userDaoImp"); ud.add("xxx"); } 未解决问题:不能成功传参给前置通知。 8.解析切入点表达式 1.格式:execution(返回值空格方法选择)。两部分组成,中间一定要有空格。 返回值:可以是*,说明拦截任何方法。https://www.sodocs.net/doc/3d8131241.html,ng.String(全名),拦截返回值为String 类型的方法。常用的实例如下: 方法选择:包名[类名].*()。设定要拦截的方法签名。 表达式(省略execution)说明 (https://www.sodocs.net/doc/3d8131241.html,ng.String 方法选择略) 拦截返回值为String 类型的方法 (!void方法选择略) 拦截返回值非空的方法 (* com.asm..*.*(..)) 拦截com.asm 包及子包下每个类的全部方法 (* com.asm.*.*(..)) 拦截com.asm 包下每个类的全部方法 (* https://www.sodocs.net/doc/3d8131241.html,er.*(..)) 拦截asm 包下User 类的所有方法 (* https://www.sodocs.net/doc/3d8131241.html,er.* (https://www.sodocs.net/doc/3d8131241.html,ng.String,..)) 拦截User 类中第一个参数为String,后面参 数任一的方法 待增加 待增加 9.总结 面向切面的常见应用(如权限拦截)、spring 的aop 依赖两种方式实现代理(依被代理的对 象是否实现接口而定)、通知概念、基于注解与基于XML 两种方式来配置切面、基本步骤(依要拦截的方法来设定切入点,依据业务需求实现拦截通知代码,切面纳入spring 容器管理,要被监控的类只能是通过Spring 容器获取)、切入点的格式。 六、与JDBC 集成 1.搭建环境 建立spring_05_integrationJdbc 项目,此项目使用dbcp 作为数据源来整合JDBC 技术。因此除了spring 所需的jar 包,还应导入dbcp 的jar 包:commons-dbcp-1.2.2.jar 及此jar 所依赖的两个apache 开源jar 包:commons-collections-3.1.jar、commons-pool.jar。由于涉及到数据库操作,还应导入数据库驱动包,这里选择的是mySQL 驱动。 2.基于注解的事务管理 步骤一、配置事务管理器。 建立beans.xml文件,它的内容如下: xmlns:xsi="https://www.sodocs.net/doc/3d8131241.html,/2001/XMLSchema-instance" xmlns:aop="https://www.sodocs.net/doc/3d8131241.html,/schema/aop" xmlns:tx="https://www.sodocs.net/doc/3d8131241.html,/schema/tx" xmlns:context="https://www.sodocs.net/doc/3d8131241.html,/schema/context" xsi:schemaLocation=" https://www.sodocs.net/doc/3d8131241.html,/schema/beans https://www.sodocs.net/doc/3d8131241.html,/schema/beans/spring-beans-2.5.xsd https://www.sodocs.net/doc/3d8131241.html,/schema/tx https://www.sodocs.net/doc/3d8131241.html,/schema/tx/spring-tx-2.5.xsd https://www.sodocs.net/doc/3d8131241.html,/schema/aop https://www.sodocs.net/doc/3d8131241.html,/schema/aop/spring-aop-2.5.xsd https://www.sodocs.net/doc/3d8131241.html,/schema/context https://www.sodocs.net/doc/3d8131241.html,/schema/context/spring-context-2.5. xsd" > class="https://www.sodocs.net/doc/3d8131241.html,mons.dbcp.BasicDataSource"> class="org.springframework.jdbc.datasource.DataSourceTransactionM anager"> 说明:首先是配置了一个数据源,以供数据源事务管理器配置引用。接着配置了数据源事务管 理器,随后开启了基于@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 } 建立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 return(List 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中增加对应的配置内容如下: 结合配置文件解析UserDaoImp实现类:(1)此类作为一个bean实例纳入spring容器管理,使用setter注入方式完成对datasource的注入,实质是完成的JdbcTemplate对象的初始 化。(2)该类CRUD方法都使用了Spring容器提供的JdbcTemplate对象来简化了CRUD操作,在spring文档的11.2.1.节对JdbcTemplate类作了较详细的介绍。(3)此类打上了 @Transactional注解,表示此类中的业务方法都会受beans.xml配置的 步骤四、编写测试类 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获取它的实现类对象将会报错。 步骤五、感知事务管理。 在UserDaoImp的delete方法中增加如下代码: 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=1 和2 这两条记录都不会被删除。如果把UseDaoImp 类前标记@Transactional 的注解去掉,id=1 的记录会被删除,因为失去 了spring 容器的事务管理。 小结spring 事务: (1)在beans.xml 中配置事务管理器,可以是依赖于数据源的事务管理器,也可以其它的事务管理器(比如和JPA 集成的事务管理器等),这些事务管理器类都继承自AbstractPlatformTransactionManager 抽象类。(2)事务管理器配置后,要想让配置的事务管理器对某些类的事务管理起作用,可以有两种方式配置:一种是声明式配置(两种:基于注解或基于XML),一种是编程是配置。(3)上面的一些操作,都是基于注解的声明式事务配置:关键两点:开启基于事务的注解支持( 可以在类前声明事务标记,也可以在类的方法中具体声明详细的事务标记。(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 配置文件。