1、什么是AOP?面向切面编程。它和面向对象编程(OOP)都是一种编程思想。AOP也是为了实现代码重用,只是它的代码重用是通过代理来实现的,而OOP的代码重用是通过集成来实现的。比如我有一个B类,B类里面有几个方法,现在的需求是不破坏B类的前提下,给B类里的几个方法都添加事务。
——如果采用OOP的思想,就是我们继承B类,在新的类里面重写这几个方法,有几个方法,就如下德重写几遍。
class C extends B{
public void aaa(){
//开启事务
super.aaa();
//提交事务
}
}
——AOP思想的话,我们需要一个新的类,里面封装的是我们要给B类增强的功能代码,比如此处的事务管理代码。然后我们写一个B类的代理类,我们在代理类里面对B类的每个方法执行前后进行改造,执行前加上开启事务,执行结束加上提交事务。
——这个代理类就是我们学习AOP需要掌握的东西,我们先手写代理类,实际中代理类是由spring帮我们自动生成的。
——动态代理类的实现方法有两种:一种是接口+实现类的方式,spring采用的是jdk的动态代理Proxy。另一种是实现类方式,spring采用的是cglib字节码增强。
2、AOP术语。
——target目标类就是需要被代理被增强的类。joinpoint连接点就是可能被增强的方法。pointcut切入点就是被增强的方法。advice通知/增强,也就是增强代码,有前置的后置的等等。weaving织入过程就是把advice和pointcut连接起来的过程。proxy代理类就是我们主要研究和书写的东西。最后一个aspect切面,就是advice和pointcut的结合。
——补充一下:切面,一般就是指的切面方法或者切面代码,也就是要切入到业务逻辑里的那些增强功能。为什么叫切面呢?我想大概是因为,如果把业务逻辑想象成从上到下的流程的话(想象成圆柱形的),这些代码就是在这些流程的某些位置切入进去,比如在执行一个方法之前开启事务,在方法之后提交事务等,这些切入就像是在圆柱形的流程的某些地方横切了一刀,所以形象地称为切面编程。其本质是,在运行时动态地将代码(增强代码advice)切入到指定类的指定方法中。一般应用比如事务管理、日志管理、性能监测、缓存等。
3、我们先来看看利用JDK的动态代理实现AOP切面编程的功能。
——先编写一个UserService接口(必须要接口,因为动态代理就是凭借获得这套接口,才说明这个代理最终是代理谁的),写两简单的方法,然后写一个UserServiceImpl实现类。
——再编写一个增强类,也就是切面类,也就是我们需要添加的代码,这个类叫MyAspect,里面写一个myBefore和一个myAfter方法。
——再写一个工厂类,为什么?因为我们是模拟动态代理的过程,实际中spring的代理都是由工厂生成出来的,所以我们也要写一个工厂类Mybeanfactory,里面写一个生成代理类的方法createUserService。下面就是模拟的核心。其实本质上就是生成一个代理类(代理的是UserService),然后在处理方法中添加了切面方法。
package com.hello.factory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.hello.aspect.MyAspect;
import com.hello.service.UserService;
import com.hello.service.impl.UserServiceImpl;
public class Mybeanfactory {
public static UserService createUserService(){
//目标类
final UserService userService=new UserServiceImpl();
//切面类
final MyAspect myAspect=new MyAspect();
//整合,就是生成代理,返回这个代理对象
UserService userServiceProxy=(UserService) Proxy.newProxyInstance(Mybeanfactory.class.getClassLoader(),userService.getClass().getInterfaces(),new InvocationHandler() {
public Object invoke(Object proxy,Method method,Object[] args)
throws Throwable {
myAspect.myBefore();
Object invoke = method.invoke(userService,args);
myAspect.myAfter();
return invoke;
}
});
return userServiceProxy;
}
}
——当然,最终使用的就是我们的代理类,而不使用我们的原先的类:
@Test
public void test01(){
UserService createUserService = Mybeanfactory.createUserService();
createUserService.addUser();
createUserService.updateUser();
}
4、CGLIB字节码增强实现AOP编程。
——字节码增强有很多方式,CGLIB只是其中一种,它增强的核心原理是生成一个目标类的子类,然后对子类进行一些增强,最终返回。所以它不需要目标类的接口,只需要目标类即可。
——首先,我们需要导入jar包,这个jar包分为cglib核心包,和asm依赖包。这两个包我们在Hibernate和Struts里面都看到过,但我们的spring-core核心包里面已经包含了这两个,所以我们直接导入spring的core包即可。
——我们写一个目标类UserServiceImpl,写了一个切面类MyAspect同上,只是在工厂类里面有所不同,不是采用的动态代理,而是字节码增强的方式,但我们可以看到,其实核心逻辑都类似,里面的方法都可以和动态代理的一些方法一一对应起来联系:
package com.hello.factory;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import com.hello.aspect.MyAspect;
import com.hello.service.impl.UserServiceImpl;
public class Mybeanfactory {
public static UserServiceImpl createUserService(){
//目标类
final UserServiceImpl userService=new UserServiceImpl();
//切面类
final MyAspect myAspect=new MyAspect();
//整合,就是生成代理,返回这个代理对象
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(userService.getClass());
enhancer.setCallback(new MethodInterceptor() {
////前3个参数和动态代理的类似,第4个参数其实也有作用,是另一种调用父类方法的方法,我们这里的代理其实是UserServiceImpl的子类
public Object intercept(Object arg0,Method arg1,Object[] arg2,MethodProxy arg3) throws Throwable {
myAspect.myBefore();
Object invoke = arg1.invoke(userService,arg2);
// Object invokeSuper = arg3.invokeSuper(arg0,arg2);
myAspect.myAfter();
return invoke;
// return invokeSuper;
}
});
UserServiceImpl create = (UserServiceImpl) enhancer.create();
return create;
}
}
源代码:JavaEE CGLIB字节码增强方式实现AOP编程
5、知识点。
——利用JDK动态代理实现的方法,我们返回的UserService就是一个动态代理类,对象名称如下:
用CGLIB方式生成的类,从名字也看得出来。
——AOP联盟为Advice做了一些分类。我们只需要掌握环绕通知即可。Spring按照通知Advice在目标类方法的连接点位置,可以分为5类:
- 前置通知 org.springframework.aop.MethodBeforeAdvice,在目标方法执行前实施增强
- 后置通知 org.springframework.aop.AfterReturningAdvice,在目标方法执行后实施增强
- 环绕通知 org.aopalliance.intercept.MethodInterceptor,在目标方法执行前后实施增强
- 异常抛出通知 org.springframework.aop.ThrowsAdvice,在方法抛出异常后实施增强
- 引介通知 org.springframework.aop.IntroductionInterceptor,在目标类中添加一些新的方法和属性
环绕通知,必须手动执行目标方法
try{
//前置通知
//执行目标方法
//后置通知
} catch(){
//抛出异常通知
}
6、spring编写代理:半自动。
——让spring帮我们创建代理对象,我们从spring容器中获取代理对象。所以,我们需要的就是一个目标类(和接口)和切面类,然后配置一下,但是这个切面类我们是要遵循规范的,也就是实现环绕方法并实现了里面的方法,在这个方法里面我们写之前我们写的那些前置和后置代码。然后剩下的生成代理类都是通过spring的xml配置文件配置出来的。
——目标类和它的接口没什么好说的,我们再写一个切面类,这时候有所不同了:
package com.hello.aspect;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyAspect implements MethodInterceptor {
public Object invoke(MethodInvocation arg0) throws Throwable {
System.out.println("方法前");
//手动执行方法
Object proceed = arg0.proceed();
System.out.println("方法后");
return proceed;
}
}
——然后再配置文件:
<?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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 目标类 -->
<bean id="userService" class="com.hello.service.UserServiceImpl"></bean>
<!-- 切面类 -->
<bean id="myAspect" class="com.hello.aspect.MyAspect"></bean>
<!-- 代理类,以后真正要用的,就是这个代理类 -->
<!-- 我们之前说过 factorybean只生产一种特定的类,所以Proxyfactorybean只生产代理类,这是spring自带的工厂 -->
<bean id="userServiceProxy" class="org.springframework.aop.framework.Proxyfactorybean">
<!-- 目标类,值是引用 -->
<property name="target" ref="userService"></property>
<!-- 实现的接口,这应该是一个array里面包含一个value,但是只有1个value的时候可以省略array而直接写成如下 -->
<property name="interfaces" value="com.hello.service.UserService"></property>
<!-- 切面类,这里面也应该是一个array,里面包含多个value,此处同样省略,但是里面的值可以直接用id,上面的接口没有id,所以只能写全限定名称 -->
<property name="interceptorNames" value="myAspect"></property>
<!-- 是否采用cglib,配置了,它一定是采用cglib;不配置时,如果有接口优先采用jdk动态代理,否则用cglib -->
<property name="optimize" value="true"></property>
</bean>
</beans>
——最后测试一下:
package com.hello.test;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.hello.service.UserService;
public class TestSemiAutoAOP {
@Test
public void test01(){
String xmlPath="com/hello/aspect/applicationContext.xml";
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext(xmlPath);
UserService us=(UserService) classPathXmlApplicationContext.getBean("userServiceProxy");
us.addUser();
us.updateUser();
}
}
7、上面半自动的是spring帮我们创建代理对象,我们再从spring获得代理对象。也就是说我们在xml配置文件中写一个代理对象的bean。然后使用获取的时候就取这个代理对象bean的id。
而全自动的配置思路和这个不太一样,全自动的配置意思就是,我们还是在xml里面配置正常的目标类和切面类的bean,然后不配置代理对象的bean了,而是做一个aop的配置,这里面的大概意思就是把什么切面方法和什么目标方法连接起来。我们使用的时候,还是获取目标类bean的id,但是因为我们有了aop的配置,所以我们表面上是获取目标类bean的id,但是实际上spring给我们返回的是目标类bean的代理对象。如果我们不需要代理对象的话,只要把那段aop的配置删除即可。
——我们首先需要导入一个jar包,com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
。
——并且在配置文件里添加一个aop命名空间,这样我们就能使用<aop:xxx>
了,这个和之前说的P命名空间类似。仍然是之前的那个文件里面去找。
——然后开始配置。
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 目标类 -->
<bean id="userService" class="com.hello.service.UserServiceImpl"></bean>
<!-- 切面类 -->
<bean id="myAspect" class="com.hello.aspect.MyAspect"></bean>
<!-- aop配置,proxy-target-class取true表示采用cglib,否是是jdk动态代理-->
<aop:config proxy-target-class="true">
<!-- AspectJ表达式:选择方法(任意返回值 包名.任意类。任意方法(任意参数)) -->
<aop:pointcut expression="execution(* com.hello.service.UserServiceImpl.*(..))" id="myPointCut"/>
<!-- advisor表示切面,是由一个切入点和一个切面类组成的切面 -->
<aop:advisor advice-ref="myAspect" pointcut-ref="myPointCut"/>
</aop:config>
</beans>
——最后测试,注意我们测试的时候,获取对象,是直接获取的目标类,因为我们配置了aop,spring就会自动帮我们返回目标类的代理对象给我们。
@Test
public void test01(){
String xmlPath="com/hello/aspect/applicationContext.xml";
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext(xmlPath);
UserService us=(UserService) classPathXmlApplicationContext.getBean("userService");
us.addUser();
us.updateUser();
}
8、AspectJ。是基于java的AOP框架,不太常用,它主要用于自定义的开发的时候。我们先说说切入点表达式。
——execution(),用于描述方法。语法execution(修饰符 返回值 包.类.方法名(参数) throws异常)
。
- 修饰符,一般省略。
public公共方法,*任意
- 返回值,不能省略。
void返回没有值,String返回值字符串,*任意
- 包,[可省略]。
com.itheima.crm固定包,com.itheima.crm.*.service crm包下面子包任意 (例如:com.itheima.crm.staff.service),com.itheima.crm.. crm包下面的所有子包(含自己),com.itheima.crm.*.service.. crm包下面任意子包,固定目录service,service目录任意包
- 类,[可省略]。
UserServiceImpl 指定类,*Impl 以Impl结尾,User* 以User开头,*任意
- 方法名,不能省略。
addUser固定方法,add* 以add开头,*Do 以Do结尾,* 任意
- (参数)。`()无参,(int) 一个整型,(int,int)两个,(..)参数任意
- throws,可省略,一般不写。
常用案例:
<aop:pointcut expression="execution(* com.hello.do*.*(..))||execution(* com.hello.*Service.*(..))" id="myPointCut"/>
——除了execution之外,还有:
- within:匹配包或子包中的方法(了解),within(com.hello.aop..*)
- this:匹配实现接口的代理对象中的方法(了解),this(com.hello.aop.user.UserDAO)
- target:匹配实现接口的目标对象中的方法(了解),target(com.hello.aop.user.UserDAO)
- args:匹配参数格式符合标准的方法(了解),args(int,int)
- bean(id) 对指定的bean所有的方法(了解),bean(‘userServiceId’)
9、AspectJ的通知类型。注意之前我们说的是AOP的通知类型Advice。这里的通知有6种,需要掌握的就是around环绕通知。
- before:前置通知(应用:各种校验)。在方法执行前执行,如果通知抛出异常,阻止方法运行。
- afterReturning:后置通知(应用:常规数据处理)。方法正常返回后执行,如果方法中抛出异常,通知无法执行。必须在方法执行后才执行,所以可以获得方法的返回值。
- around:环绕通知(应用:十分强大,可以做任何事情)。方法执行前后分别执行,可以阻止方法的执行。必须手动执行目标方法。
- afterThrowing:抛出异常通知(应用:包装异常信息)。方法抛出异常后执行,如果方法没有抛出异常,无法执行。
- after:最终通知(应用:清理现场)。方法执行完毕后执行,无论方法中是否出现异常。
环绕
try{
//前置:before
//手动执行目标方法
//后置:afterRetruning
} catch(){
//抛出异常 afterThrowing
} finally{
//最终 after
}
我们看源码的话,也是如上面格式一样,如果是after的源码,那么它的主要部分就是写在finally里面的。
10、使用AspectJ。基于XML。先导入jar包,除了4+1之外,还需要aop联盟规范和spring-aop,aspectJ规范和spring-aspects。
——这里基于XML的AspectJ的使用和上面自动实现AOP的编程很像,我们同样需要的就是目标类(和接口)、切面类和xml配置,不同的是此处的切面类不需要实现标准接口和实现标准方法,这里面的方法我们可以随便写跟平常自定义一样,xml里面的配置自然也是有所不同,就这么一些差别。另外,测试几种通知类型的时候,不要一起测,因为它们彼此之间没有什么关联关系,顺序上不容易把握。
——先写目标接口和实现类,我们贴出实现类的代码即可:
package com.hello.service;
public class UserServiceImpl implements UserService {
public void addUser() {
System.out.println("addUser方法");
}
public Integer updateUser() {
System.out.println("updateUser方法");
return 888;
}
public void deleteUser() {
System.out.println("deleteUser方法");
}
}
下面是切面类的,我们定义一个方法,虽然我们这里名字叫做前置的通知,但说了不算,真正说这个方法是前置通知是通过aop配置指定它为前置通知才行,所以说我们这里的方法名字是任意的随便写。
package com.hello.aspect;
import org.aspectj.lang.JoinPoint;
public class MyAspect {
//JoinPoint和Joinpoint不要搞混,它是连接点的信息,就是方法的信息
public void myBefore(JoinPoint joinpoint){
System.out.println("我是前置通知"+joinpoint.getSignature().getName());
}
}
然后是配置文件:
<?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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 目标类 -->
<bean id="userService" class="com.hello.service.UserServiceImpl"></bean>
<!-- 切面类 -->
<bean id="myAspect" class="com.hello.aspect.MyAspect"></bean>
<!-- aop配置 -->
<aop:config>
<aop:aspect ref="myAspect">
<!-- 定义了一个切入点 -->
<aop:pointcut expression="execution(* com.hello.service.UserServiceImpl.*(..))" id="myPointCut"/>
<!-- 下面这里还有一个pointcut="",但是我们上面配置过切入点了,所以用上面的id,否则可以用这个属性,值是上面的表达式,二选一 -->
<!-- 这就是定义了一个前置通知,把我们写的方法关联进来 -->
<!-- 这里method是切面类里面我们自己写的方法名,因为上面已经引用了切面类,所以这里直接写方法名即可 -->
<aop:before method="myBefore" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
</beans>
测试:
@Test
public void test01(){
String xmlPath="com/hello/service/applicationContext.xml";
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext(xmlPath);
UserService us=(UserService) classPathXmlApplicationContext.getBean("userService");
us.addUser();
us.updateUser();
us.deleteUser();
}
结果:
我是前置通知addUser addUser方法 我是前置通知updateUser updateUser方法 我是前置通知deleteUser deleteUser方法
——其他几个后置通知、环绕通知、抛出异常通知、after通知等流程都一样,主要是在通知的写法和xml配置参数上稍有差别。我们先说后置通知,这里需要注意的就是返回值的设置和配置:
后置通知:
public void myReturning(JoinPoint joinpoint,Object ret){
<!-- 把返回值也输出来 -->
System.out.println("我是后置通知"+joinpoint.getSignature().getName()+"->"+ret);
}
配置:
<!-- 后置通知 -->
<!-- returning取值就是后置通知的第二个参数 -->
<aop:after-returning method="myReturning" pointcut-ref="myPointCut" returning="ret"/>
测试都一样,免了,直接看结果,有返回值的就显示出来了:
addUser方法
我是后置通知addUser->null
updateUser方法
我是后置通知updateUser->888
deleteUser方法
我是后置通知deleteUser->null
——环绕通知。有返回值Object,需要使用JoinPoint的子接口ProceedingJoinPoint 。需要手动执行方法。
public Object myAround(ProceedingJoinPoint joinpoint) throws Throwable{
System.out.println("我是around的前置");
Object proceed = joinpoint.proceed();
System.out.println("我是around的后置");
return proceed;
}
配置,没什么好说的:
<!-- 环绕通知 -->
<aop:around method="myAround" pointcut-ref="myPointCut"/>
直接看结果:
我是around的前置
addUser方法
我是around的后置
我是around的前置
updateUser方法
我是around的后置
我是around的前置
deleteUser方法
我是around的后置
——再看抛出异常通知,注意第二个参数是异常信息。
public void myAfterThrowing(JoinPoint joinpoint,Throwable e){
System.out.println("我是抛出异常通知"+"->"+e.getMessage());
}
看配置:
<!-- 抛出异常通知 -->
<!-- throwing就是抛出异常通知的第二个参数,这个配合和返回值的那个配置类似 -->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
public Integer updateUser() {
System.out.println("updateUser方法");
int i=1/0;
return 888;
}
看结果:
addUser方法
updateUser方法
我是抛出异常通知->/ by zero
public void myAfter(JoinPoint joinpoint){
System.out.println("我是最终通知"+joinpoint.getSignature().getName());
}
配置:
<!-- 最终通知 -->
<aop:after method="myAfter" pointcut-ref="myPointCut"/>
结果:
addUser方法 我是最终通知addUser updateUser方法 我是最终通知updateUser deleteUser方法 我是最终通知deleteUser
11、基于注解的AspectJ。主要就是把在xml里面的配置都替换成@xxx
之类的。
——我们需要先添加命名空间,context命名空间,因为需要扫描注解类。
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 扫描注解包 -->
<context:component-scan base-package="com.hello"></context:component-scan>
<!-- aop的注解也需要开启 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
然后在UserServiceImpl实现类上面添加:
@Service("userService")
然后在切面类上面添加:
//它三层都不是,所以就用@Component,然后把它声明为成切面,相当于<aop:aspect ref="myAspect">
@Component
@Aspect
在切面类里面声明前置方法:
//此处省略了value=
@Before("execution(* com.hello.service.UserServiceImpl.*(..))")
public void myBefore(JoinPoint joinpoint){
System.out.println("我是前置通知"+joinpoint.getSignature().getName());
}
就OK了。
——下面流程是一样的,后置通知,注意返回值的写法,以及我们在此声明了一个公共切入点,后面直接引用即可,不用每次都写一大串的表达式了。
//公共切入点声明 private void xxx(){} 引用的时候用方法名
@Pointcut("execution(* com.hello.service.UserServiceImpl.*(..))")
private void myPointCut(){
}
然后写后置方法即可:
@AfterReturning(value="myPointCut()",returning="ret")
public void myReturning(JoinPoint joinpoint,Object ret){
System.out.println("我是后置通知"+joinpoint.getSignature().getName()+"->"+ret);
}
——环绕通知。
@Around(value="myPointCut()")
public Object myAround(ProceedingJoinPoint joinpoint) throws Throwable{
System.out.println("我是around的前置");
Object proceed = joinpoint.proceed();
System.out.println("我是around的后置");
return proceed;
}
——抛出异常通知。
@AfterThrowing(value="myPointCut()",throwing="e")
public void myAfterThrowing(JoinPoint joinpoint,Throwable e){
System.out.println("我是抛出异常通知"+"->"+e.getMessage());
}
——最终通知。
@After(value="myPointCut()")
public void myAfter(JoinPoint joinpoint){
System.out.println("我是最终通知"+joinpoint.getSignature().getName());
}
会使用XML配置后,注解的使用原理是一致的,只需要注意注解的各种写法。
12、 JdbcTemplate,spring提供的操作JDBC的工具类,类似于DBUtils。它依赖数据源(连接池)dataSource。
——我们先来一个最原始的,使用不适用spring配置文件直接使用api的时候。先创建数据表和导入jar包:
然后写实体类User。此处略去。
最后写一个测试类。直接在测试类里面写数据源的设置,而不是用xml配置。我们在此可知的是:JdbcTemplate模板创建的话需要数据源,然后创建后得到的JdbcTemplate对象可以使用query和update等操作数据。
@Test
public void test01(){
//创建数据源
BasicDataSource dataSource=new BasicDataSource();
//设置数据源
dataSource.setDriverClassName("com.MysqL.jdbc.Driver");
dataSource.setUrl("jdbc:MysqL://localhost:3306/spring01");
dataSource.setUsername("root");
dataSource.setPassword("root");
//创建JdbcTemplate模板
JdbcTemplate jdbcTempplate=new JdbcTemplate(dataSource);
//操作数据
jdbcTempplate.update("insert into t_user(username,password) values(?,?)","Eric","1234");
}
——DBCP配置。我们知道上面只要写到new的都可以用Ioc在xml里面让spring管理,而且写到setxxx的基本都可以在xml里用property注入来解决。所以我们可以管理上面的代码。
我们先写一个UserDao接口和实现类,把操作数据库的代码放到这里面,当然这里面也是用到JdbcTemplate的,作为属性注入。
public class UserDaoImpl implements UserDao {
private JdbcTemplate jdbcTemplate;
//我们只要在applicationContext里面配置了UserDaoImpl的bean和这个属性,JdbcTemplate就会自动注入了
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void saveUser(User user) {
jdbcTemplate.update("update t_user set username=?,password=? where id=?",user.getUsername(),user.getPassword(),user.getId());
}
}
然后,我们直接写配置了,差不多把上面写的代码都在这里面配置了一遍,从数据源到Jdbc模板,然后到实现类的bean。
<?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.xsd">
<!-- 配置数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.MysqL.jdbc.Driver"></property>
<property name="url" value="jdbc:MysqL://localhost:3306/spring01"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!-- 创建jdbcTemplate,需要数据源,所以在上面创建数据源 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 我们最终要使用的,需要一个jdbcTemplate的ref,所以在上面创建一个jdbcTemplate -->
<bean id="userDao" class="com.hello.dao.impl.UserDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
</beans>
最终使用:
@Test
public void test02(){
String xmlPath="com/hello/domain/applicationContext.xml";
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext(xmlPath);
UserDao userDao=(UserDao)classPathXmlApplicationContext.getBean("userDao");
User u=new User();
u.setId(1);
u.setUsername("Andy");
u.setPassword("9999");
userDao.saveUser(u);
}
——我们再来做一遍C3P0的配置,其他不变,主要在配置上面做一些改变,因为有些属性名称和DBCP不一样。所以只改变数据源配置这一块即可。
<!-- C3P0配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.MysqL.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:MysqL://localhost:3306/spring01"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
——我们上面xml配置文件里面创建的JdbcTemplate都是自动创建的,也就是说我们给了一个数据源,然后系统就帮我们自动创建了,那么我们其实是可以省略手动配置JdbcTemplate这个步骤的,相当于我们直接给我们的目标类bean一个数据源,它会帮我们自动创建JdbcTemplate。
这里就需要添加一些规范,比如我们的数据类需要继承自JdbcDaoSupport,这个父类里面会帮我们自动创建JdbcTemplate。
我们在UserDaoImpl 实现类里面如下写,继承了JdbcDaoSupport ,这样我们就不能设置setJdbcTemplate了,因为父类里面有了,而且是final修饰的不允许覆写。
public class UserDaoImpl extends JdbcDaoSupport implements UserDao {
public void saveUser(User user) {
this.getJdbcTemplate().update("update t_user set username=?,user.getId());
}
}
配置文件也变得简单了,配置了一下数据源,然后直接用数据源给目标类的bean当做属性注入即可(这个属性甚至不需要写在我们的目标实现类里面,因为在父类JdbcDaoSupport里面已经写了):
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.MysqL.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:MysqL://localhost:3306/spring01"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<bean id="userDao" class="com.hello.dao.impl.UserDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
——最后,我们还可以使用properties文件来储存数据源的配置数据,然后进一步简化applicationContext里面的东西。
先写properties文件。
然后,修改配置文件,当然得先添加context的命名空间,之前说过。
<!-- 需要先加载属性文件 -->
<!-- classpath:前缀表示 src下,配置好之后通过 ${key}来获得值 -->
<context:property-placeholder location="classpath:dataSourceInfo.properties" />
<!-- 利用属性文件来进行C3P0配置 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.pwd}"></property>
</bean>
其他的bean的书写都不变,这样也可以成功使用JdbcTemplate模板来操作数据库。