AOP是面向切面编程。全称:AspectOriented Programming
面向切面编程指的是:程序是运行期间,动态地将某段代码插入到原来方法代码的某些位置中。这就叫面向切面编程。
一个简单计算数功能加日记准备计算器相关类 计算接口 publicinterface Calculate { publicint add(int num1, int num2);
publicint mul(int num1, int num2);
publicint div(int num1, int num2);
publicint sub(int num1, int num2); } 计算机类 publicclass Calculator implements Calculate { publicint add(int num1, int num2) { System.out.println("日记 :【add】方法调用前 。参数1是:" + num1 + " , 参数2是:" + num2); return num1 + num2; }
publicint mul(int num1, int num2) { System.out.println("日记 :【mul】方法调用前 。参数1是:" + num1 + " , 参数2是:" + num2); return num1 * num2; }
publicint div(int num1, int num2) { System.out.println("日记 :【div】方法调用前 。参数1是:" + num1 + " , 参数2是:" + num2); return num1 / num2; }
publicint sub(int num1, int num2) { System.out.println("日记 :【sub】方法调用前 。参数1是:" + num1 + " , 参数2是:" + num2); return num1 - num2; } }
测试的代码 publicclass CalculatorTest { publicstaticvoid main(String[] args) { Calculate calculate = new Calculator(); int result =calculate.add(12, 12); System.out.println("相加的结果:" + result);
result = calculate.mul(12, 12); System.out.println("相乘的结果:" + result); } } 上面这种方法加日记处理操作。日记的代码就会耦合到业务代码中。而且后期如果需要修改日记就需要去指的修改所有方法中的日记操作。这个维护操作非常不方便。 可以说是一个很失败的例子。 |
原始方法统一日记处理。把日记的内容封装到一个类去中集中处理。 编写一个日记处理工具类 publicclass LogUtil { publicstaticvoid log(String method, int num1, int num2) { System.out.println("日记 :【" + method + "】 方法调用前 。参数1是:" + num1 + " , 参数2是:" + num2); } } 修改原来Calculator中的日记代码 @Override publicint add(int num1, int num2) { LogUtil.log("add", num1, num2); return num1 + num2; }
@Override publicint mul(int num1, int num2) { LogUtil.log("mul", num1, num2); return num1 * num2; } 但是这种方式的不足之处是,每有一个需要加日记的类,都需要到类的代码中去添加日记功能代码。 无法做到所有对象都统一处理。 |
使用代理实现日记
使用jdk动态代理统一日记创建一个计算器代理工具类 publicclass CalculateProxyFactory {
publicstatic CalculategetProxy(Calculate target) { // 定义一个计算器拦截处理类 classCalculateInvocationHandler implements InvocationHandler{ Calculate target;
publicCalculateInvocationHandler(Calculate calculate) { this.target = calculate; }
/** * proxy 代理对象 * method 调用的方法 * args 方法的参数 */ @Override public Object invoke(Object proxy, Method method,Object[] args) throws Throwable { if(method.getDeclaringClass().equals(Object.class)) { return method.invoke(target, args); } LogUtil.log(method.getName(),(int) args[0], (int) args[1]); Object result = null; try { result = method.invoke(target, args); System.out.println("后置日记"); } catch (Exception e) { System.out.println("异常日记"); } finally { System.out.println("finally日记"); } return result; } }
return (Calculate) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass() .getInterfaces(), newCalculateInvocationHandler(target)); }
} 修改原来计算器中的日记代码 publicclass Calculator implements Calculate { @Override publicint add(int num1, int num2) { return num1 + num2; }
@Override publicint mul(int num1, int num2) { return num1 * num2; }
@Override publicint div(int num1, int num2) { return num1 / num2; }
@Override publicint sub(int num1, int num2) { return num1 - num2; } } 测试代码调整 publicstaticvoid main(String[] args) { Calculatecalculate = CalculateProxyFactory.getProxy(new Calculator() ); int result = calculate.add(12,12); System.out.println("相加的结果:" + result);
result= calculate.mul(12, 12); System.out.println("相乘的结果:" + result); } 优点:这种方式已经解决我们前面所有日记需要的问题。非常的灵活。而且可以方便的在后期进行维护和升级。 缺点:当然使用jdk动态代理,需要有接口。如果没有接口。就无法使用jdk动态代理。 |
使用cglib代理publicclass CGLibProxyFactory implements MethodInterceptor {
publicstatic ObjectgetCGLibProxy(Object target, Callback callback) { // 创建一个CGLig生成器 Enhancerenhancer = new Enhancer(); // 设置父类。因为cglib是通过类,进行代码,不是通过接口 enhancer.setSuperclass(target.getClass()); // 设置拦截的代理方法 enhancer.setCallback(callback); // create 方法创建一个代理对象并返回 return enhancer.create(); }
@Override public Objectintercept(Object proxy, Method method, Object[] params, MethodProxymethodProxy) throws Throwable { LogUtil.log(method.getName(), (int) params[0], (int) params[1]); // 调用实际的对象的方法 // 一定要使用methodProxy对象 // 第一个参数是proxy代码对象的父类方法 Objectresult = methodProxy.invokeSuper(proxy, params); System.out.println("这是后置代码");
return result; }
publicstaticvoid main(String[] args) { Calculatorcalculator = (Calculator) CGLibProxyFactory.getCGLibProxy(new Calculator(), new CGLibProxyFactory()); calculator.add(12,13); } } 优点:在没有接口的情况下,同样可以实现代理的效果。 缺点:同样需要自己编码实现代理全部过程。 但是为了更好的整合Spring框架使用。所以我们需要学习一下Spring 的AOP 功能。也就是学习Spring提供的AOP功能。 |
AOP编程的专业术语
通知(Advice)
通知就是增强的代码。比如前置增强的代码。后置增强的代码。异常增强代码。这些就叫通知
切面(Aspect)
切面就是包含有通知代码的类叫切面。
横切关注点
横切关注点,就是我们可以添加增强代码的位置。比如前置位置,后置位置,异常位置。和返回值位置。这些都叫横切关注点。
目标(Target)
目标对象就是被关注的对象。或者被代理的对象。
代理(Proxy)
为了拦截目标对象方法,而被创建出来的那个对象,就叫做代理对象。
连接点(Joinpoint)
连接点指的是横切关注点和程序代码的连接,叫连接点。
切入点(pointcut)
切入点指的是用户真正处理的连接点,叫切入点。
在Spring中切入点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件。
图解AOP专业术语:
使用Spring实现AOP简单切面编程需要导入工程的jar包 Spring的核心包 spring-beans-4.0.0.RELEASE.jar spring-context-4.0.0.RELEASE.jar spring-core-4.0.0.RELEASE.jar spring-expression-4.0.0.RELEASE.jar Spring的测试包 spring-test-4.0.0.RELEASE.jar Spring日记相关包 commons-logging-1.1.3.jar log4j-1.2.17.jar Spring的AOP切面相关的包 spring-aop-4.0.0.RELEASE.jar spring-aspects-4.0.0.RELEASE.jar com.springsource.net.sf.cglib-2.2.0.jar com.springsource.org.aopalliance-1.0.0.jar com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar 需要有的类 publicinterface Calculate {
publicint add(int num1, int num2);
publicint mul(int num1, int num2);
publicint div(int num1, int num2);
publicint sub(int num1, int num2); } @Component publicclass Calculator implements Calculate { @Override publicint add(int num1, int num2) { return num1 + num2; }
@Override publicint mul(int num1, int num2) { return num1 * num2; }
@Override publicint div(int num1, int num2) { return num1 / num2; }
@Override publicint sub(int num1, int num2) { return num1 - num2; } } @Aspect @Component publicclass LogUtil { @Before(value = "execution(publicint com.xypuxing.aop.Calculator.add(int, int))") publicstaticvoid logBefore() { System.out.println("前置 日记 :【xxx】方法调用前 。参数1是:xxxx"); }
@After(value = "execution(publicint com.xypuxing.aop.Calculator.add(int, int))") publicstaticvoid logAfter() { System.out.println("后置 日记 :【xxxx】方法调用前 。参数1是:xxxx"); }
@AfterReturning(value = "execution(publicint com.xypuxing.aop.Calculator.add(int, int))") publicstaticvoid logAfterReturn() { System.out.println("返回之后: 日记 :【xxxxx】 方法调用前 。参数1是:xxxxxx"); }
@AfterThrowing(value = "execution(publicint com.xypuxing.aop.Calculator.add(int, int))") publicstaticvoid logThrowException() { System.out.println("抛异常:日记 :【xxxxx】 方法调用前 。参数1是:xxxxxx"); }
} applicationContext.xml配置文件中的内容 <context:component-scan base-package="com.xypuxing" /> <aop:aspectj-autoproxy /> 测试代码 @ContextConfiguration(locations = "classpath:applicationContext.xml") @RunWith(SpringJUnit4ClassRunner.class) publicclass SpringAopTest { @Autowired private Calculate calculate; @Test publicvoid test1() { System.out.println( "添加:" + calculate.add(1, 2)); } } 测试运行的结果 |
切入点表达式的重用切入点表达式的重点,需要分三个步骤: 1、在切面类中定义一个空的方法 publicstaticvoid pointcut1() {} 2、在方法中使用@Pointcut定义一个切入连表达式 @Pointcut(value="execution(publicint com.xypuxing.aop.Calculator.add(int, int))" + " || " + "execution(public *com.xypuxing.aop.Calculator.*(..))") publicstaticvoid pointcut1() {}
3、其他的通知注解中使用方法名()的形式引用方法上定义的切入点表达式。 比如:@Before("pointcut1()") |
Spring的切入点表达式@PointCut切入点表达式语法格式是: execution(访问权限返回值类型 方法全限定名(参数类型列表)) 限定符: *: 1) 匹配某全类名下,任意或多个方法。 表示匹配com.xypuxing.aop.Calculator下以a打头的任意方法。并且返回值和两个参数都是int类型。 execution(public int com.xypuxing.aop.Calculator.a*(int,int))
表示匹配com.xypuxing.aop.Calculator下的任意方法。并且返回值和两个参数都是int类型。 execution(public int com.xypuxing.aop.Calculator.*(int,int))
2) 在Spring中只有public权限能拦截到,访问权限可以省略(访问权限不能写*)。 // 权限省略,表示任意类型的访问权限,但Spring现在只支持public权限 execution(int com.xypuxing.aop.Calculator.*(int, int))
3) 匹配任意类型的返回值,可以使用* 表示 // 表示任意类型的返回值 execution(* com.xypuxing.aop.Calculator.*(int, int)) 4) 匹配任意子包。 // 表示匹配com的子包 execution(* com.*.aop.Calculator.*(int, int))
5) 任意类型参数 // 表示第二个参数是任意类型 execution(* com.xypuxing.aop.Calculator.*(int,*)) ..:可以匹配多层路径,或任意多个任意类型参数 // 表示com和aop之间可以有任意层级的包 execution(* com..aop.Calculator.*(int,int)) // 表示第一个参数是int。之后可以有任意个任意类型的参数 execution(* com.xypuxing.aop.Calculator.*(int,..)) 模糊匹配: // 表示任意返回值,任意方法全限定符,任意参数 execution(* *(..)) // 表示任意返回值,任意包名+任意方法名,任意参数 execution(**.*(..))
精确匹配: // int 返回值,com.xypuxing.aop.Calculator类的add方法,两个int参数 execution(public int com.xypuxing.aop.Calculator.add(int,int))
切入点表达式连接:&& 、|| // 表示需要同时满足两个表达式 @Before("execution(publicint com.xypuxing.aop.Calculator.add(int, int))" + " && " + "execution(public* com.xypuxing.aop.Calculator.add(..))") // 表示两个条件只需要满足一个,就会被匹配到 @Before("execution(publicint com.xypuxing.aop.Calculator.add(int, int))" + " || " + "execution(public * com.xypuxing.aop.Calculator.a*(int))") |
Spring切面中的代理对象
在Spring中,可以对有接口的对象和无接口的对象分别进行代理。在使用上有些细微的差别。
1) 如果被代理的对象实现了接口。在获取对象的时候,必须要以接口来接收返回的对象。
测试的代码:
2) 被切面拦截的代理对象,如果没有实现接口。获取对象的时候使用对象类型本身
测试的代码:
自行测试结果
Spring通知的执行顺序Spring通知的执行顺序是: 正常情况: 前置通知====>>>>后置通知=====>>>>返回值之后 异常情况: 前置通知====>>>>后置通知=====>>>>抛异常通知 测试需要的类 @Component publicclass Calculator {// implementsCalculate {
publicint div(int num1, int num2) { return num1 / num2; }
publicint add(int num1, int num2) { return num1 + num2; } 切面的类对象 @Aspect @Component publicclass LogUtil {
// 每个通知,都是同时监听加和除两个方法 @Before("execution(publicint com.xypuxing.aop.Calculator.add(int, int))" + " || " + "execution(public * com.xypuxing.aop.Calculator.d*(..))") publicstaticvoid logBefore() { System.out.println("前置 日记 :【xxx】方法调用前 。参数1是:xxxx"); }
@After("execution(publicint com.xypuxing.aop.Calculator.add(int, int))" + " || " + "execution(public * com.xypuxing.aop.Calculator.d*(..))") publicstaticvoid logAfter() { System.out.println("后置 日记 :【xxxx】方法调用前 。参数1是:xxxx"); }
@AfterReturning("execution(publicint com.xypuxing.aop.Calculator.add(int, int))" + " || " + "execution(public * com.xypuxing.aop.Calculator.d*(..))") publicstaticvoid logAfterReturn() { System.out.println("返回之后: 日记 :【xxxxx】 方法调用前 。参数1是:xxxxxx"); }
@AfterThrowing("execution(publicint com.xypuxing.aop.Calculator.add(int, int))" + " || " + "execution(public * com.xypuxing.aop.Calculator.d*(..))") publicstaticvoid logThrowException() { System.out.println("抛异常:日记 :【xxxxx】 方法调用前 。参数1是:xxxxxx"); } } 测试的代码 @ContextConfiguration(locations = "classpath:applicationContext.xml") @RunWith(SpringJUnit4ClassRunner.class) publicclass SpringAopTest { @Autowired private Calculator calculator;
@Test publicvoid test1() { //加法 calculator.add(1, 2); System.out.println("==============================="); //减法 calculator.div(1, 0); } } 测试的结果 |
获取连接点信息JoinPoint 是连接点的信息。 只需要在通知方法的参数中,加入一个JoinPoint参数。就可以获取到拦截方法的信息。 注意:是org.aspectj.lang.JoinPoint这个类。 @Aspect @Component publicclass LogUtil { @Before("execution(publicint com.xypuxing.aop.Calculator.add(int, int))" + " || " + "execution(public *com.xypuxing.aop.Calculator.d*(..))") publicstaticvoid logBefore(JoinPointjoinPoint) { System.out.println("前置 日记 :【" +joinPoint.getSignature().getName() + "】 方法调用前 。参数是:" + Arrays.asList(joinPoint.getArgs())); }
@After("execution(publicint com.xypuxing.aop.Calculator.add(int, int))" + " || " + "execution(public *com.xypuxing.aop.Calculator.d*(..))") publicstaticvoid logAfter(JoinPointjoinPoint) { System.out.println("后置 日记 :【" +joinPoint.getSignature().getName() + "】 方法调用 。参数是:" + Arrays.asList(joinPoint.getArgs())); }
@AfterReturning("execution(publicint com.xypuxing.aop.Calculator.add(int, int))" + " || " + "execution(public *com.xypuxing.aop.Calculator.d*(..))") publicstaticvoid logAfterReturn(JoinPointjoinPoint) { System.out.println("返回之后: 日记 :【" + joinPoint.getSignature().getName() + "】 方法调用 。参数是:" + Arrays.asList(joinPoint.getArgs())); }
@AfterThrowing("execution(publicint com.xypuxing.aop.Calculator.add(int, int))" + " || " + "execution(public *com.xypuxing.aop.Calculator.d*(..))") publicstaticvoidlogThrowException(JoinPoint joinPoint) { System.out.println("抛异常:日记 :【" + joinPoint.getSignature().getName() + "】 方法调用 。参数是:" + Arrays.asList(joinPoint.getArgs())); } } 前面测试的代码没有变。再次执行的结果。都能拿到拦截的方法名和参数 |
获取拦截方法的返回值和抛的异常信息
获取方法返回的值分为两个步骤:
1、在返回值通知的方法中,追加一个参数 Object result
2、然后在@AfterReturning注解中添加参数returning="参数名"
获取方法抛出的异常分为两个步骤:
1、在异常通知的方法中,追加一个参数Exception exception
2、然后在@AfterThrowing注解中添加参数throwing="参数名"
修改LogUtil切面类的代码
@AfterReturning(value = "execution(publicint com.xypuxing.aop.Calculator.add(int, int))"
+ " || " + "execution(public *com.xypuxing.aop.Calculator.d*(..))", returning = "result")
publicstaticvoid logAfterReturn(JoinPointjoinPoint, Object result){
System.out.println("返回之后: 日记 :【" + joinPoint.getSignature().getName() + "】 方法调用 。参数是:"
+Arrays.asList(joinPoint.getArgs()) + ",返回值:" + result);
}
@AfterThrowing(value = "execution(publicint com.xypuxing.aop.Calculator.add(int, int))"
+ " || " + "execution(public *com.xypuxing.aop.Calculator.d*(..))", throwing = "exception")
publicstaticvoid logThrowException(JoinPointjoinPoint, Exceptionexception) {
System.out.println("抛异常:日记 :【" + joinPoint.getSignature().getName() + "】 方法调用 。参数是:"
+Arrays.asList(joinPoint.getArgs()) + ",异常对象:" + exception);
}
测试的代码不变,测试的结果是
Spring的环绕通知
1、环绕通知使用@Around注解。
2、环绕通知如果和其他通知同时执行。环绕通知会优先于其他通知之前执行。
3、环绕通知一定要有返回值(环绕如果没有返回值。后面的其他通知就无法接收到目标方法执行的结果)。
4、在环绕通知中。如果拦截异常。一定要往外抛。否则其他的异常通知是无法捕获到异常的。
在LogUtil切面类中添加环绕通知
@Around(value = "execution(**(..))")
publicstatic Object logAround(ProceedingJoinPointproceedingJoinPoint) {
//获取请求参数
Object[]args = proceedingJoinPoint.getArgs();
ObjectresultObject = null;
try {
System.out.println("环绕前置");
//调用目标方法
resultObject =proceedingJoinPoint.proceed(args);
System.out.println("环绕后置");
} catch (Throwable e) {
System.out.println("环绕异常:" + e);
thrownewRuntimeException(e);
} finally {
System.out.println("环绕返回后");
}
//返回返回值
return resultObject;
}
修改测试的代码
@ContextConfiguration(locations = "classpath:applicationContext.xml")
@RunWith(SpringJUnit4ClassRunner.class)
publicclass SpringAopTest {
@Autowired
private Calculator calculator;
@Test
publicvoid test1() {
//加法
calculator.add(1, 2);
}
}
执行结果
多个通知的执行顺序当我们有多个切面,多个通知的时候: 1、通知的执行顺序默认是由切面类的字母先后顺序决定。 2、在切面类上使用@Order注解决定通知执行的顺序(值越小,越先执行) 再添加另一个切面类 @Component @Aspect @Order(1) publicclass Validation {
@Before(value = "com.xypuxing.aop.LogUtil.pointcut1()") publicstaticvoid before(JoinPointjoinPoint) { System.out.println("这是Validation的前置通知,拦截的方法是:" +joinPoint.getSignature().getName()); }
@After(value = "com.xypuxing.aop.LogUtil.pointcut1()") publicstaticvoid after(JoinPoint joinPoint){ System.out.println("这是Validation的后置通知,拦截的方法是:" +joinPoint.getSignature().getName()); }
@AfterReturning(value = "com.xypuxing.aop.LogUtil.pointcut1()", returning = "result") publicstaticvoid afterReturning(JoinPointjoinPoint, Object result) { System.out.println("这是Validation的后置通知,拦截的方法是:" +joinPoint.getSignature().getName() + ", 返回值:" + result); }
} 修改原来LogUtil中的切面内容(去掉环绕通知,留下前置,后置,返回后通知) @Aspect @Component @Order(2) publicclass LogUtil { @Pointcut(value="execution(publicint com.xypuxing.aop.Calculator.add(int, int))" + " || " + "execution(public *com.xypuxing.aop.Calculator.*(..))") publicstaticvoid pointcut1() {}
测试的代码 @ContextConfiguration(locations = "classpath:applicationContext.xml") @RunWith(SpringJUnit4ClassRunner.class) publicclass SpringAopTest { @Autowired private Calculator calculator; @Test publicvoid test1() { //加法 calculator.add(1, 0); } } 运行的结果 |
如何基于xml配置aop程序需要导入的包 com.springsource.net.sf.cglib-2.2.0.jar(可选) com.springsource.org.aopalliance-1.0.0.jar com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar commons-logging-1.1.3.jar log4j-1.2.17.jar spring-aop-4.0.0.RELEASE.jar spring-aspects-4.0.0.RELEASE.jar spring-beans-4.0.0.RELEASE.jar spring-context-4.0.0.RELEASE.jar spring-core-4.0.0.RELEASE.jar spring-expression-4.0.0.RELEASE.jar spring-test-4.0.0.RELEASE.jar 工程中编写的类 publicclass Calculator { publicint div(int num1, int num2) { return num1 / num2; }
publicint add(int num1, int num2) { return num1 + num2; } }
LogUtil切面类 publicclass LogUtil { publicstaticvoid logBefore(JoinPointjoinPoint) { System.out.println("前置 日记 :【" +joinPoint.getSignature().getName() + "】 方法调用前 。参数是:" + Arrays.asList(joinPoint.getArgs())); }
publicstaticvoid logAfter(JoinPointjoinPoint) { System.out.println("后置 日记 :【" +joinPoint.getSignature().getName() + "】 方法调用 。参数是:" + Arrays.asList(joinPoint.getArgs())); }
publicstaticvoid logAfterReturn(JoinPointjoinPoint, Object result) { System.out.println("返回之后: 日记 :【" + joinPoint.getSignature().getName() + "】 方法调用 。参数是:" + Arrays.asList(joinPoint.getArgs())+ ",返回值:" + result); }
publicstaticvoid logThrowException(JoinPointjoinPoint, Exception exception) { System.out.println("抛异常:日记 :【" + joinPoint.getSignature().getName() + "】 方法调用 。参数是:" + Arrays.asList(joinPoint.getArgs())+ ",异常对象:" + exception); } } Validation切面类 publicclass Validation { publicstaticvoid before(JoinPointjoinPoint) { System.out.println("这是Validation的前置通知,拦截的方法是:" +joinPoint.getSignature().getName()); }
publicstaticvoid after(JoinPoint joinPoint){ System.out.println("这是Validation的后置通知,拦截的方法是:" +joinPoint.getSignature().getName()); }
publicstaticvoid afterReturning(JoinPointjoinPoint, Object result) { System.out.println("这是Validation的后置通知,拦截的方法是:" +joinPoint.getSignature().getName() + ", 返回值:" + result); } } ApplicationContext.xml配置文件中的内容 <!-- 注册bean对象 添加@Component --> <bean id="calculator" class="com.xypuxing.aop.Calculator" /> <bean id="logUtil" class="com.xypuxing.aop.LogUtil" /> <bean id="validation" class="com.xypuxing.aop.Validation" /> <!-- 配置AOP --> <aop:config> <!-- 定义可以共用的切入点 --> <aop:pointcut expression="execution(* com.xypuxing.aop.Calculator.*(..))"id="pointcut1"/> <!-- 定义切面类--> <aop:aspect order="1" ref="logUtil"> <!-- 这是前置通知 method属性配置通知的方法 pointcut配置切入点表达式 --> <aop:before method="logBefore" pointcut="execution(* com.xypuxing.aop.Calculator.*(..))"/> <aop:after method="logAfter" pointcut="execution(* com.xypuxing.aop.Calculator.*(..))"/> <aop:after-returning method="logAfterReturn" returning="result" pointcut="execution(* com.xypuxing.aop.Calculator.*(..))"/> <aop:after-throwing method="logThrowException" throwing="exception" pointcut="execution(* com.xypuxing.aop.Calculator.*(..))"/> </aop:aspect> <!-- 定义切面类--> <aop:aspect order="2" ref="validation"> <aop:before method="before" pointcut-ref="pointcut1"/> <aop:after method="after" pointcut-ref="pointcut1"/> <aop:after-returning method="afterReturning" returning="result" pointcut-ref="pointcut1"/> </aop:aspect> </aop:config> 测试的代码 @ContextConfiguration(locations = "classpath:applicationContext.xml") @RunWith(SpringJUnit4ClassRunner.class) publicclass SpringAopTest { @Autowired Calculator calculator; @Test publicvoid test1() { calculator.add(1, 2); } } 测试运行的结果 |
Spring之数据访问Spring数据访问工程环境搭建创建一个Java工程,导入需要的Jar包Spring的核心jar包 spring-beans-4.0.0.RELEASE.jar spring-context-4.0.0.RELEASE.jar spring-core-4.0.0.RELEASE.jar spring-expression-4.0.0.RELEASE.jar Spring切面的jar包 com.springsource.net.sf.cglib-2.2.0.jar com.springsource.org.aopalliance-1.0.0.jar com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar spring-aop-4.0.0.RELEASE.jar spring-aspects-4.0.0.RELEASE.jar Spring数据访问的jar包 spring-jdbc-4.0.0.RELEASE.jar spring-orm-4.0.0.RELEASE.jar spring-tx-4.0.0.RELEASE.jar 数据库访问需要的jar包 c3p0-0.9.1.2.jar mysql-connector-java-5.1.37-bin.jar 日记需要的jar包 commons-logging-1.1.3.jar log4j-1.2.17.jar Spring的测试包 spring-test-4.0.0.RELEASE.jar 在src目录下jdbc.properties属性配置文件jdbc.username=root jdbc.password=root jdbc.url=jdbc:mysql://localhost:3306/spring jdbc.driverClass=com.mysql.jdbc.Driver
在src目录下的log4j.properties配置文件# Global logging configuration log4j.rootLogger=INFO,stdout # Console output... log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p[%t]-%m%n
在applicationContext.xml中配置数据源<!-- 加载jdbc.properties配置文件 --> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 配置数据库连接池--> <bean id="c3p0DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="driverClass" value="${jdbc.driverClass}"/> </bean> <!-- jdbcTemplate--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="c3p0DataSource"/> </bean>
测试数据源(测试数据库连接池) @ContextConfiguration(locations = "classpath:applicationContext.xml") @RunWith(SpringJUnit4ClassRunner.class) publicclass JdbcTest {
@Autowired DataSource dataSource;
@Test publicvoid test1() throws Exception { System.out.println( dataSource.getConnection()); }
} |
Spring之JdbcTemplate使用在Spring中提供了对jdbc的封装类叫JdbcTemplate。它可以很方便的帮我们执行sql语句,操作数据库。 先准备单表的数据库数据 drop database if exists jdbctemplate;
create databasejdbctemplate;
use jdbctemplate;
createtable `employee` ( `id` int(11) primarykey auto_increment, `name` varchar(100) defaultnull, `salary` decimal(11,2) defaultnull );
insert into`employee`(`id`,`name`,`salary`) values (1,'李三',5000.23),(2,'李四',4234.77),(3,'王五',9034.51), (4,'赵六',8054.33),(5,'孔七',6039.11),(6,'曹八',7714.11);
select * from employee;
JdbcTemplate的使用需要在applicationContext.xml中进行配置 <!-- jdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="c3p0DataSource"/> </bean> 实验2:将id=5的记录的salary字段更新为1300.00@Test publicvoid test2() throws Exception { // 实验2:将emp_id=5的记录的salary字段更新为1300.00 String sql = "update employee set salary = ? where id= ?"; jdbcTemplate.update(sql, 1300, 5); } 实验3:批量插入@Test publicvoid test3() throws Exception { String sql = "insert into employee(`name`,`salary`)values(?,?)"; ArrayList<Object[]> params = new ArrayList<>(); params.add(new Object[] {"aaa",100}); params.add(new Object[] {"bbb",100}); params.add(new Object[] {"ccc",100}); jdbcTemplate.batchUpdate(sql, params); } 实验4:查询id=5的数据库记录,封装为一个Java对象返回创建一个Employee对象 publicclass Employee {
private Integer id; private String name; private BigDecimal salary; @Test publicvoid test4() throws Exception { // 实验4:查询id=5的数据库记录,封装为一个Java对象返回 String sql = "select id ,name ,salary from employeewhere id = ?"; /** * 在queryRunner中使用的是ResultSetHandler * 在Spring的jdbcTemplate中,使用RowMapper。 * BeanPropertyRowMapper 可以把一个结果集转成一个bean对象 */ Employee employee = jdbcTemplate.queryForObject(sql, newBeanPropertyRowMapper(Employee.class), 5); System.out.println(employee); } 实验5:查询salary>4000的数据库记录,封装为List集合返回@Test publicvoid test5() throws Exception { // 实验5:查询salary>4000的数据库记录,封装为List集合返回 String sql = "select id,name,salary from employeewhere salary > ?"; List<Employee> list = jdbcTemplate.query(sql, newBeanPropertyRowMapper(Employee.class), 4000); System.out.println(list); } 实验6:查询最大salary@Test publicvoid test6() throws Exception { // 实验6:查询最大salary String sql = "select max(salary) from employee"; BigDecimal salary = jdbcTemplate.queryForObject(sql,BigDecimal.class); System.out.println(salary); } 实验7:使用带有具名参数的SQL语句插入一条员工记录,并以Map形式传入参数值<!-- namedParameterJdbcTemplate --> <beanid="namedParameterJdbcTemplate"class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate"> <constructor-arg name="dataSource" ref="c3p0DataSource" /> </bean>
@ContextConfiguration(locations = "classpath:applicationContext.xml") @RunWith(SpringJUnit4ClassRunner.class) publicclass JdbcTest {
@Autowired DataSource dataSource;
@Autowired JdbcTemplate jdbcTemplate;
@Autowired NamedParameterJdbcTemplatenamedParameterJdbcTemplate;
@Test publicvoid test7() throws Exception { // 实验7:使用带有具名参数的SQL语句插入一条员工记录,并以Map形式传入参数值 String sql = "insert into employee(`name`,`salary`)values(:name,:salary)"; Map<String, Object> param = new HashMap<String,Object>(); param.put("name", "小明"); param.put("salary", new BigDecimal(55)); namedParameterJdbcTemplate.update(sql, param); } 实验8:重复实验7,以SqlParameterSource形式传入参数值@Test publicvoid test8() throws Exception { // 实验8:重复实验7,以SqlParameterSource形式传入参数值 String sql = "insert into employee(`name`,`salary`)values(:name,:salary)"; //通过一个bean对象的属性会自动赋值 SqlParameterSource sqlParameterSource = newBeanPropertySqlParameterSource(new Employee(0, "小新", new BigDecimal(11111))); namedParameterJdbcTemplate.update(sql,sqlParameterSource); } 实验9:创建Dao,自动装配JdbcTemplate对象// 实验9:创建Dao,自动装配JdbcTemplate对象 添加类 @Repository publicclass EmployeeDao {
@Autowired JdbcTemplate jdbcTemplate;
publicint saveEmployee(Employeeemployee) { String sql = "insert into employee(`name`,`salary`)values(?,?)"; returnjdbcTemplate.update(sql, employee.getName(),employee.getSalary()); } } 在applicationContext.xml中配置 <!-- 添加包扫描 --> <context:component-scan base-package="com.xypuxing" /> 测试代码 @ContextConfiguration(locations = "classpath:applicationContext.xml") @RunWith(SpringJUnit4ClassRunner.class) publicclass JdbcTest {
@Autowired DataSource dataSource;
@Autowired JdbcTemplate jdbcTemplate;
@Autowired NamedParameterJdbcTemplatenamedParameterJdbcTemplate;
@Autowired EmployeeDao employeeDao;
@Test publicvoid test9() throws Exception { // 实验9:创建Dao,自动装配JdbcTemplate对象 employeeDao.saveEmployee(new Employee(null, "ssssss", new BigDecimal(999))); } 实验10:通过继承JdbcDaoSupport创建JdbcTemplate的Dao@Repository publicclass EmployeeDao extends JdbcDaoSupport {
@Autowired protectedvoid setDataSource2(DataSource dataSource) { System.out.println("自动注入 +" + dataSource); super.setDataSource(dataSource); }
publicint saveEmployee(Employeeemployee) { String sql = "insert into employee(`name`,`salary`)values(?,?)"; returngetJdbcTemplate().update(sql, employee.getName(), employee.getSalary()); } } |