Spring详解三之AOP与数据访问

1、AOP切面编程

1.1、什么是AOP

1.2、一个简单计算数功能加日记引入话题

1.3、原始方法统一日记处理

1.4、使用代理实现日记

    1.4.1、使用jdk动态代理(接口代理:必须要有接口)统一日记

    1.4.2Cglib代理介绍

1.5AOP编程的专业术语

1.6、使用Spring实现AOP简单切面编程

1.7Spring的切入点表达式

1.8Spring切面中的代理对象

1.9Spring通知的执行顺序

1.10、获取连接点信息(通知的参数)

1.11、获取拦截方法的返回值和抛的异常信息(通知的参数)

1.12Spring的环绕通知

1.13、切入点表达式的复用(引用)

1.14、多个通知的执行顺序

1.15如何基于xml配置aop程序

2Spring之数据访问

2.1Spring数据访问工程环境搭建

2.2SpringJdbcTemplate使用


1AOP切面编程

1.1、什么是AOP

AOP是面向切面编程。全称:Aspect Oriented Programming

 

面向切面编程指的是:程序是运行期间,动态地将某段代码插入到原来方法代码的某些位置中。这就叫面向切面编程。

 

1.2、一个简单计算数功能加日记引入话题

计算器接口


public interface Calculate {
	// 加法
	public int add(int num1, int num2);

	// 减法
	public int sub(int num1, int num2);

	// 乘法
	public int mul(int num1, int num2);

	// 除法
	public int div(int num1, int num2);

}

计算器实现类

public class Calculator implements Calculate {

	@Override
	public int add(int num1, int num2) {
		
		int result = num1 + num2;
	
		return result;
	}

	@Override
	public int sub(int num1, int num2) {
		
		int result = num1 - num2;
		
		return result;
	}

	@Override
	public int mul(int num1, int num2) {
		
		int result = num1 * num2;
		
		return result;
	}

	@Override
	public int div(int num1, int num2) {
		int result = 0;
		result = num1 / num2;
		return result;
	}

}

测试的代码:

public class CalculatorTest {

	@Test
	public void test1() {
		// 需求:希望我们在方法执行前加日记
		// 需求:然后在方法执行后也加日记
		// 需要:希望,如果目标代码发生,异常。你给打印异常的信息,日记 
		Calculate calculate = new Calculator();
		System.out.println( calculate.add(100, 200) );
		System.out.println( calculate.sub(200, 100) );
		System.out.println( calculate.div(200, 100) );
	}
	
}

1.3、原始方法统一日记处理

定义一个日记工具类实现添加日记的功能:

public class LogUtils {

	public static void logBefore(String method, Object... args) {
		System.out.println("【LogUtils】目标方法之前前是【" + method + "】方法,然后参数是:"
				+ Arrays.asList(args));
	}

	public static void logAfter(String method, Object... args) {
		System.out.println("【LogUtils】目标方法之后是【" + method + "】方法,然后参数是:"
				+ Arrays.asList(args));
	}

	public static void logAfterException(String method, Exception e,
			Object... args) {
		System.out.println("【LogUtils】目标方法异常之后是【" + method + "】方法,异常信息是:"
				+ e.getMessage() + ",然后参数是:" + Arrays.asList(args));
	}

}

被日记工具类修改之后的计算器实现类代码

public class Calculator implements Calculate {

	@Override
	public int add(int num1, int num2) {
		LogUtils.logBefore("add", num1, num2);
		int result = num1 + num2;
		LogUtils.logAfter("add", num1, num2);
		return result;
	}

	@Override
	public int sub(int num1, int num2) {
		LogUtils.logBefore("sub", num1, num2);
		int result = num1 - num2;
		LogUtils.logAfter("sub", num1, num2);
		return result;
	}

	@Override
	public int mul(int num1, int num2) {
		LogUtils.logBefore("mul", num1, num2);
		int result = num1 * num2;
		LogUtils.logAfter("mul", num1, num2);
		return result;
	}

	@Override
	public int div(int num1, int num2) {
		int result = 0;
		try {
			LogUtils.logBefore("div", num1, num2);
			result = num1 / num2;
			LogUtils.logAfter("div", num1, num2);
		} catch (Exception e) {
			LogUtils.logAfterException("div", e, num1, num2);
		}
		return result;
	}

}

1.4、使用代理实现日记

1.4.1、使用jdk动态代理(接口代理:必须要有接口)统一日记


说明:代理类与目标实现类是平级关系,不可相互赋值,两者只能用父接口接收对象(即接口代理)

注:注意区分目标对象与代理对象、代理类($ProxyN)的区别

代理增强:在原来的基础上做更多的功能,是在invoke()方法中执行的

测试时:要用代理对象调用接口方法,才可以进入invoke方法进行代理增强操作,而不是使用目标对象调用

日记处理工具类

public class LogUtils {

	public static void logAfterReturning(String method, Object result,
			Object... args) {
		System.out.println("【LogUtils】目标方法 返回结果 是【" + method + "】方法,返回值是:"
				+ result + ",然后参数是:" + Arrays.asList(args));
	}

	public static void logBefore(String method, Object... args) {
		System.out.println("【LogUtils】目标方法之前前是【" + method + "】方法,然后参数是:"
				+ Arrays.asList(args));
	}

	public static void logAfter(String method, Object... args) {
		System.out.println("【LogUtils】目标方法之后是【" + method + "】方法,然后参数是:"
				+ Arrays.asList(args));
	}

	public static void logAfterException(String method, Exception e,
			Object... args) {
		System.out.println("【LogUtils】目标方法异常之后是【" + method + "】方法,异常信息是:"
				+ e.getMessage() + ",然后参数是:" + Arrays.asList(args));
	}

}

JDK动态代理

public class JdkProxyFactory {

	public static Object newProxy(Object target) {
		//
		// JDk通过目标对象传入的接口。动态生成一个类,这个类实现了目标对象的所有接口。
		// 
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target
				.getClass().getInterfaces(), new InvocationHandler() {

			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				// 这是返回值
				Object result = null;
				try {
					// 前置增加代码
					LogUtils.logBefore(method.getName(), args);
					result = method.invoke(target, args);
					// 后置增加代码
					LogUtils.logAfter(method.getName(), args);
				} catch (Exception e) {
					// 异常增强代码
					LogUtils.logAfterException(method.getName(), e, args);
				}
				// 返回增强
				LogUtils.logAfterReturning(method.getName(), result, args);
				return result;
			}
		});
	}
}
public class CalculatorTest {

	@Test
	public void test1() {
		// 需求:希望我们在方法执行前加日记
		// 需求:然后在方法执行后也加日记
		// 需要:希望,如果目标代码发生,异常。你给打印异常的信息,日记
		Calculate calculate = new Calculator();

		Calculate proxy = (Calculate) JdkProxyFactory.newProxy(calculate);
		System.out.println(proxy instanceof Calculate);
		System.out.println(proxy instanceof Calculator);

		System.out.println(proxy.add(100, 200));
		System.out.println(proxy.sub(200, 100));
		System.out.println(proxy.div(200, 100));
	}

}

优点:这种方式已经解决我们前面所有日记需要的问题。非常的灵活。而且可以方便的在后期进行维护和升级。

缺点:当然使用jdk动态代理,需要有接口如果没有接口。就无法使用jdk动态代理

1.4.2Cglib代理介绍

区别:

Cglib代理为目标对象的类产生一个继承关系的子类(类代理),

jdk动态代理为目标对象的类产生一个实现了同一接口平级的无关联类(接口代理)


 

优点:在没有接口的情况下,同样可以实现代理的效果。

缺点:同样需要自己编码实现代理全部过程。

但是为了更好的整合Spring框架使用。所以我们需要学习一下Spring AOP 功能。也就是学习Spring提供的AOP功能,而aop并不是spring独有的,很多框架也有aop功能。

 

注:Spring 底层结合使用了jdk动态代理与cglib代理

1.5AOP编程的专业术语

通知(Advice)

通知就是增强的代码。比如前置增强的代码。后置增强的代码。异常增强代码。返回增强代码。这些就叫通知

切面(Aspect)

切面就是包含有通知代码的类叫切面。

 

横切关注点

横切关注点,就是我们可以添加增强代码的位置。比如前置位置,后置位置,异常位置。和返回值位置。这些都叫横切关注点。

目标(Target)

目标对象就是被关注的对象。或者被代理的对象。

 

代理(Proxy)

为了拦截目标对象方法,而被创建出来的那个对象(增强之后的那个对象),就叫做代理对象。

 

 

连接点(Joinpoint)

连接点指的是横切关注点和程序代码的连接,叫连接点。

 

切入点(pointcut)

切入点指的是用户真正处理的连接点,叫切入点。

 

在Spring切入点通过org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。

图解AOP专业术语:


1.6、使用Spring实现AOP简单切面编程

 

1导入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

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

说明:

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

依赖的联盟jar包。

commons-logging-1.1.3.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

依赖的jar

2、给组件添加注解

@Component
public class Calculator implements Calculate 

/**
 *	@Aspect表示这是切面(类)
 */
@Aspect
@Component
public class LogUtils {

	/**
	 * @Before表示前置操作。<br/>
	 *  对哪些类的目标方法感兴趣(也就是说,具体的切入点是哪些)<br/>
	 *  通过切入点表达式告诉Spring对哪个类的哪个目标方法感兴趣(直正的切入点)<br/>
	 *  
	 */
	@Before(value="execution(public int com.tcent.interfaces.impl.Calculator.add(int, int))")
	public static void logBefore() {
		System.out.println("【LogUtils】目标方法之前前是【 method 】方法,然后参数是:");
	}

3、在Spring配置文件中做如下的配置:

<context:component-scan base-package="com.tcent"></context:component-scan>
	<!-- 允许 Spring 支持@Aspect 注解方式自动代理 -->
	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

注意:通知方法不能有自己的参数,通知的参数必须按照它自己的要求

1.7Spring的切入点表达式

@PointCut切入点表达式语法格式是: execution(访问权限 返回值类型 方法全限定名(参数类型列表))

 

execution(public int com.tcent.interfaces.impl.Calculator.add(int, int))

public 访问权限

返回值类型 int

方法全限定名是指:包名+类名+方法名

com.tcent.interfaces.impl.Calculator.add

包名:com.tcent.interfaces.impl

类名:Calculator

方法名:add

参数类型列表:(int, int)

 

限定符:两种

* 表示任意的意思:

1) 匹配某全类名下,任意或多个方法。

execution(public int com.tcent.interfaces.impl.Calculator.*(int, int))

表示匹配Calculator类下全部的方法

execution(public int com.tcent.interfaces.impl.Calculator.add*(int, int))

表示匹配 Calculator类下add打头的方法

 

注意:参数类型必须匹配

 

 

2) Spring中只有public权限能拦截到,访问权限可以省略(访问权限不能写*)。

execution( int com.tcent.interfaces.impl.Calculator.*(int, int) )

public 访问权限可以省略

 

 

3) 匹配任意类型的返回值,可以使用 * 表示

execution(public int com.tcent.interfaces.impl.Calculator.*(int, int))

以上切入点表达式表示返回值必须是int类型,才会被拦截

execution(public long com.tcent.interfaces.impl.Calculator.*(int, int))

以上切入点表达式表示返回值必须是long类型。才会被拦截

 

 

4) 匹配任意子包。

execution( int com.*.interfaces.impl.Calculator.*(int, int))

以上切入点表达式表示com.任意包名.interfaces.impl.Calculator就匹配

以上切入点表达式表示com.任意包名.任意包名.interfaces.impl.Calculator就不匹配

 

5) 任意类型参数

execution( int com.tcent.interfaces.impl.Calculator.add(int, *))

以上切入点表达式的参数类型:

表示第一个参数必须是int类型

表示第二个参数是任意类型

 

..:可以匹配多层路径,或 任意多个任意类型参数

1) 任意层级的包

execution( int com..interfaces.impl.Calculator.add(int, *))

以上切入点表达式表示:

com.tcent.interfaces.impl包可以匹配

com.a.interfaces.impl包可以匹配

com.a.b.interfaces.impl包可以匹配

 

 

2) 任意类型的参数

execution( int com.tcent.interfaces.impl.Calculator.add(int, ..))

以上切入点表达式表示:

第一个参数必须是int类型。之后随意

execution( int com.tcent.interfaces.impl.Calculator.add(..))

以上切入点表达式表示:

不关心参数个数和以及参数的类型。

 

模糊匹配:

// 表示任意返回值,任意方法全限定符,任意参数

execution(* *(..))

// 表示任意返回值,任意包名+任意方法名,任意参数

execution(* *.*(..))

 

 

精确匹配:

execution(public int com.tcent.interfaces.impl.Calculator.add(int, int))

 

表示限定返回值类型

限定包名。

限定类名

限定方法名

限定参数类型

 

切入点表达式连接:&& || 

// 表示需要同时满足两个表达式

@Before("execution(public int com.tcent.aop.Calculator.add(int, int))"

+ " && "

+ "execution(public * com.tcent.aop.Calculator.add(..))")

 

// 表示两个条件只需要满足一个,就会被匹配到

@Before("execution(public int com.tcent.aop.Calculator.add(int, int))"

+ " || "

+ "execution(public * com.tcent.aop.Calculator.a*(int))")

1.8Spring切面中的代理对象

Spring中,可以对有接口的对象和无接口的对象分别进行代理(即AOP的底层实现:基于接口的JDK动态代理与基于类的cglib代理)。在使用上有些细微的差别。

1) 如果被代理(被增强)的对象(Calculator实现了接口。在获取对象的时候,必须要以接口来接收返回的对象。

 

被增强的对象有接口的时候,只能用接口接收Spring注入的值。


@ContextConfiguration(locations = "classpath:application.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringTest {

	@Autowired
	private Calculate calculate;

	@Test
	public void test1() {
		System.out.println( Arrays.asList(calculate.getClass().getInterfaces()) );
		calculate.add(100, 200);
	} 
	
}

那么 Spring AOP 底层自动使用JDK动态代理。我们只能用类对象实例的接口来接收spring 注入的值。

 

2) 如果被代理对象,如果没有实现接口。获取对象的时候使用对象类型本身

 

如果被增强(代理)的对象没有实现接口。


那么 Spring AOP 底层自动使用CGLIB代理。我们只能用类对象实例来接收spring 注入的值。


1.9Spring通知的执行顺序

Spring通知的执行顺序是:

正常情况(整个执行过程没有收到抛出的异常):

前置通知====>>>>后置通知=====>>>>返回值通知

异常情况(收到抛出的异常):

前置通知====>>>>后置通知=====>>>>抛异常通知

@Aspect
@Component
public class LogUtils {

	/**
	 * @Before表示前置操作。<br/>
	 *  对哪些类的目标方法感兴趣(也就是说,具体的切入点是哪些)<br/>
	 *  通过切入点表达式告诉Spring对哪个类的哪个目标方法感兴趣(直正的切入点)<br/>
	 */
	@Before(value="execution(public int com.tcent.interfaces.impl.Calculator.*(int, int))")
	public static void logBefore() {
		System.out.println("【LogUtils】目标方法之前前是【 method 】方法,然后参数是:");
	}
	
	/**
	 * @AfterReturning是返回通知
	 */
	@AfterReturning(value="execution(public int com.tcent.interfaces.impl.Calculator.*(int, int))")
	public static void logAfterReturning() {
		System.out.println("【LogUtils】目标方法 返回结果 是【 method 】方法,返回值是:,然后参数是:" );
	}

	/**
	 * @After是后置通知
	 */
	@After(value="execution(public int com.tcent.interfaces.impl.Calculator.*(int, int))")
	public static void logAfter() {
		System.out.println("【LogUtils】目标方法之后是【 method 】方法,然后参数是:");
	}

	/**
	 * @AfterThrowing是异常通知
	 */
	@AfterThrowing(value="execution(public int com.tcent.interfaces.impl.Calculator.*(int, int))")
	public static void logAfterException() {
		System.out.println("【LogUtils】目标方法异常之后是【  method 】方法,异常信息是:,然后参数是:" );
	}

}

1.10、获取连接点信息(通知的参数)

JoinPoint 是连接点的信息。(@PointCut切入点注解)

只需要在通知方法的参数中,加入一个JoinPoint参数。就可以获取到拦截方法的信息。

 

注意:是org.aspectj.lang.JoinPoint这个类。

/**
	 * @Before表示前置操作。<br/> 对哪些类的目标方法感兴趣(也就是说,具体的切入点是哪些)<br/>
	 *                     通过切入点表达式告诉Spring对哪个类的哪个目标方法感兴趣(直正的切入点)<br/>
	 */
	@Before(value = "execution(public int com.tcent.interfaces.impl.Calculator.*(int, int))")
	public static void logBefore(JoinPoint jp) {
		// jp.getSignature().getName() 获取目标对象方法名
		// jp.getArgs() 获取目标对象方法参数
		System.out.println("【LogUtils】目标方法之前前是【 " + jp.getSignature().getName()
				+ "】方法,然后参数是:" + Arrays.asList(jp.getArgs()));
	}

  •  jp.getSignature().getName() 获取目标对象方法名
  •  jp.getArgs() 获取目标对象方法参数

1.11、获取拦截方法的返回值和抛的异常信息(通知的参数)

获取方法返回的值分为两个步骤:

1、在返回值通知的方法中,追加一个参数 Object result

2、然后在@AfterReturning注解中添加参数returning="参数名"

/**
	 * @AfterReturning是返回通知
	 */
	@AfterReturning(value = "execution(public int com.tcent.interfaces.impl.Calculator.*(int, int))", returning = "result")
	public static void logAfterReturning(JoinPoint jp, Object result) {
		System.out.println("【LogUtils】目标方法 返回结果 是【"
				+ jp.getSignature().getName() + "】方法,返回值是:" + result
				+ ",然后参数是: " + Arrays.asList(jp.getArgs()));
	}

获取方法抛出的异常分为两个步骤:

1、在异常通知的方法中,追加一个参数Exception exception

2、然后在@AfterThrowing 注解中添加参数 throwing="参数名"

/**
	 * @AfterThrowing是异常通知
	 */
	@AfterThrowing(value = "execution(public int com.tcent.interfaces.impl.Calculator.*(int, int))", throwing = "e")
	public static void logAfterException(JoinPoint jp, Exception e) {
		System.out.println("【LogUtils】目标方法异常之后是【" + jp.getSignature().getName()
				+ "】方法,异常信息是:" + e + ",然后参数是:" + Arrays.asList(jp.getArgs()));
	}

注:对于前置和后置通知获取切入点的信息只需要JoinPoint参数对象就够了,但如果是返回值通知和异常通知,还需要添加一个参数,分别是:Object类型的result,Exception类型的e。在切入点表达式中将参数通过returning = "result"throwing = "e"传进去即可

 

1.12Spring的环绕通知

必须具备的4点:

1、环绕通知使用@Around注解。

2、环绕通知如果和其他通知同时执行。环绕通知会优先于其他通知之前执行。

3、环绕通知一定要有返回值(环绕如果没有返回值。后面的其他通知就无法接收到目标方法执行的结果)。

4、在环绕通知中。如果拦截异常。一定要往外抛。否则其他的异常通知是无法捕获到异常的。

 

注:环绕通知获取参数的对象是ProceedingJoinPoint 和其他四种通知不同JoinPoint

相似:

环绕通知执行目标方法:result = pjp.proceed(pjp.getArgs());

jdk动态代理中的invoke()方法一样,也是执行目标方法

/**
	 * 1、环绕通知优先于其他通知先执行。 <br/>
	 * 2、环绕通知必须有返回值,否则其他的返回通知就收到返回值 <br/>
	 * 3、环绕通知如果捕获到异常。一定要往外抛。否则其他的异常通知接收不到<br/>
	 * 
	 * @return
	 */
	@Around(value = "execution(public int com.tcent.interfaces.impl.Calculator.*(int, int))")
	public static Object around(ProceedingJoinPoint pjp) throws Throwable {
		Object result = null;
		try {
			try {
				System.out.println("这是环绕的前置通知");
				// 执行目标方法
				result = pjp.proceed(pjp.getArgs());
			} finally {
				System.out.println("这是环绕的后置通知");
			}
			System.out.println("这是环绕的返回通知 ");
			return result;
		} catch (Exception e) {
			System.out.println("这是环绕的异常通知");
			throw e;
		}
	}

环绕通知必须具备的4点:中的后两点可以解释为什么这里执行目标方法要两次抛异常,第一次是前置和后置的异常。第二次是为了获得执行目标方法的返回值,传递给其他方法,来接收目标方法执行的结果(如果没有返回值,后面的通知方法就获取不到结果值了 3)。并且将异常也往外抛(不可以在这里处理异常,如果在这里吃掉异常,后面的通知方法将捕获不到异常了).

当然有的人可能会想都放在一个异常里面,里面包含捕获异常和结果值不就行了。其实不行,因为这样就没办法区分四种通知的功能了,也就是没有了前置和后置。

1.13、切入点表达式的复用(引用)

切入点表达式的复用步骤:

 

1、定义一个静态static空方法

2、在此空方法是使用@Pointcut(value=”切入点表达式”)

@Pointcut(value="execution(public int com.tcent.interfaces.impl.Calculator.*(int, int))")

public static void pointcut1() {}

 

3、在需要复用此切入点表达式的地址。填入方法名()即可。

1) 比如:@Before(value = "pointcut1()"),此处只有 一个value的参数,value可省略

@Pointcut(value="execution(public int com.tcent.interfaces.impl.Calculator.*(int, int))")
	public static void pointcut1() {}

	/**
	 * @Before表示前置操作。<br/> 对哪些类的目标方法感兴趣(也就是说,具体的切入点是哪些)<br/>
	 *                     通过切入点表达式告诉Spring对哪个类的哪个目标方法感兴趣(直正的切入点)<br/>
	 */
	@Before(value = "pointcut1()")
	public static void logBefore(JoinPoint jp) {
		// jp.getSignature().getName() 获取目标对象方法名
		// jp.getArgs() 获取目标对象方法参数
		System.out.println("【LogUtils】目标方法之前前是【 " + jp.getSignature().getName()
				+ "】方法,然后参数是:" + Arrays.asList(jp.getArgs()));
	}

	/**
	 * @AfterReturning是返回通知
	 */
	@AfterReturning(value = "pointcut1()", returning = "result")
	public static void logAfterReturning(JoinPoint jp, Object result) {
		System.out.println("【LogUtils】目标方法 返回结果 是【"
				+ jp.getSignature().getName() + "】方法,返回值是:" + result
				+ ",然后参数是: " + Arrays.asList(jp.getArgs()));
	}

1.14、多个通知的执行顺序

当我们有多个切面,多个通知的时候:

1、通知的执行顺序默认是由切面类的字母先后顺序决定。

2、在切面类上使用3@Order注解决定通知执行的顺序(值越小,越先执行)

 

再添加另一个切面类

@Component
@Aspect
@Order(1)
public class Validation {

	 
	@Before(value="execution(public int com.tcent.interfaces.impl.Calculator.*(int, int))")
	public static void logBefore() {
		System.out.println("【LogUtils】目标方法之前前是【 method 】方法,然后参数是:");
	}
	 
	@AfterReturning(value="execution(public int com.tcent.interfaces.impl.Calculator.*(int, int))")
	public static void logAfterReturning() {
		System.out.println("【LogUtils】目标方法 返回结果 是【 method 】方法,返回值是:,然后参数是:" );
	}

	 
	@After(value="execution(public int com.tcent.interfaces.impl.Calculator.*(int, int))")
	public static void logAfter() {
		System.out.println("【LogUtils】目标方法之后是【 method 】方法,然后参数是:");
	}


}

修改原来LogUtils中的切面内容(去掉环绕通知,留下前置,后置,返回后通知)

@Aspect
@Component
@Order(2)
public class LogUtil {
	@Pointcut(value="execution(public int  com.tcent.interfaces.impl.Calculator.add(int, int))" + " || "
			+ "execution(public *  com.tcent.interfaces.impl.Calculator.*(..))")
	public static void pointcut1() {}

测试的代码

@ContextConfiguration(locations = "classpath:application.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringAopTest {
	@Autowired
	private Calculator calculator;
	@Test
	public void test1() {
		//加法
		calculator.add(1, 0);
	}
}

运行的结果


1.15如何基于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

 

工程中编写的类

public class Calculator {
	public int div(int num1, int num2) {
		return num1 / num2;
	}

	public int add(int num1, int num2) {
		return num1 + num2;
	}
}

LogUtils切面类

public class LogUtils {

	 
//	public static void pointCut() {
//	}

	
	public static void logBefore(JoinPoint jp) {
		// jp.getSignature().getName() 获取目标对象方法名
		// jp.getArgs() 获取目标对象方法参数
		System.out.println("【LogUtils】目标方法之前前是【 " + jp.getSignature().getName()
				+ "】方法,然后参数是:" + Arrays.asList(jp.getArgs()));
	}

	public static void logAfterReturning(JoinPoint jp, Object result) {
		System.out.println("【LogUtils】目标方法 返回结果 是【"
				+ jp.getSignature().getName() + "】方法,返回值是:" + result
				+ ",然后参数是: " + Arrays.asList(jp.getArgs()));
	}

	public static void logAfter(JoinPoint jp) {
		System.out.println("【LogUtils】目标方法之后是【" + jp.getSignature().getName()
				+ "】方法,然后参数是:" + Arrays.asList(jp.getArgs()));
	}

	public static void logAfterException(JoinPoint jp, Exception e) {
		System.out.println("【LogUtils】目标方法异常之后是【" + jp.getSignature().getName()
				+ "】方法,异常信息是:" + e + ",然后参数是:" + Arrays.asList(jp.getArgs()));
	}
}

Application.xml配置文件中的内容

<!--   	 
				id 是给这个切入点表达式定义一个唯一标识
				id可随便起名字,和Calculator、LogUtils类名名无关
		-->
<bean id="calculator" class="com.tcent.interfaces.impl.Calculator"></bean>
<bean id="logUtils" class="com.tcent.utils.LogUtils"></bean>
<aop:config>
<!-- 定义一个切入点表达式 
				expression是切入点表达式
				id 是给这个切入点表达式定义一个唯一标识
				id可随便起名字,和LogUtils中的pointCut()方法名无关
				但这条语句就相当于在切面类中定义了一个静态切点方法
		-->
	<aop:pointcut  id="pointCut1" expression="execution(public int com.tcent.interfaces.impl.Calculator.*(..))"/>
	<!-- 定义哪个类是切面类 -->
	<aop:aspect ref="logUtils">
	<!-- 前置通知	method是定义前置通知的方法名	pointcut-ref引用哪个全局的切入点表达式 -->
		<aop:before method="logBefore" pointcut-ref="pointCut1"/>
		<aop:after method="logAfter" pointcut-ref="pointCut1"/>
		<aop:after-returning method="logAfterReturning" pointcut-ref="pointCut1" returning="result"/>
		<aop:after-throwing method="logAfterException" pointcut-ref="pointCut1" throwing="e"/>
	</aop:aspect>
</aop:config>

Spring自带的增强版测试:

@ContextConfiguration(locations="classpath:applicationl.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringTest {

	@Autowired
	private Calculate calculator1;
//	@Autowired
//	private Calculate calculator2;
	//注意这里,在xml中进行了测试,添加了两个bean组件对象,并注入到spring容器中,@Autowired需要到spring容器中
	//进行自动装配, 先根据类型名,找到多个再按id值是否和变量名一样,如果一样自动自动装配,找不到就报错
	@Test
	public void test1() throws Exception {
		calculator1.add(222, 444);
		System.out.println("+++++++++++++++++++++====");
		//calculate.div(100, 0);
	}

传统junit4测试:

public class SpringTest {

	@Test
	public void test1() {
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
		Calculate calculate = (Calculate) applicationContext.getBean("calculator");
		calculate.add(100, 100);
	}

}

测试运行的结果:


2Spring之数据访问

2.1Spring数据访问工程环境搭建



创建一个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(不需要导,因为框架3.0之后核心core包已经集成进来了,如下图)

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(也是spring核心依赖的包)

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:application .xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class JdbcTest {

	@Autowired
	DataSource dataSource;

	@Test
	public void test1() throws Exception {
		System.out.println( dataSource.getConnection() );
	}

}

2.2SpringJdbcTemplate使用

Spring中提供了对jdbc封装类JdbcTemplate。它可以很方便的帮我们执行sql语句,操作数据库。

 

先准备单表的数据库数据

drop database if exists jdbctemplate;

create database jdbctemplate;

use jdbctemplate;

create table `employee` (
  `id` int(11) primary key auto_increment,
  `name` varchar(100) default null,
  `salary` decimal(11,2) default null
);

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的使用需要在application.xml中进行配置

<!-- jdbcTemplate -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource"  ref="c3p0DataSource"/>
	</bean>

实验2:将id=5的记录的salary字段更新为1300.00

@ContextConfiguration(locations="classpath:application.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class JDBCTest {

	@Autowired
	JdbcTemplate jdbcTemplate;//JdbcTemplate类接收
	@Autowired
	 DataSource dataSource;//ComboPooledDataSource接口接收
	@Test
	public void test1() throws Exception {
		System.out.println(dataSource.getConnection());
	}
	@Test
	public void test2() throws Exception {
		// 实验2:将emp_id=5的记录的salary字段更新为1300.00
		String sql = "update employee set salary=? where id=?";
		jdbcTemplate.update(sql, 1300.00,5);
	}
}

实验3:批量插入

@Test
	public void 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对象

public class Employee {

	private Integer id;
	private String name;
	private BigDecimal salary;

@Test
	public void test4() throws Exception {
		// 实验4:查询id=5的数据库记录,封装为一个Java对象返回
		String sql = "select id ,name ,salary from employee where id = ?";
		/**
		 * 在queryRunner中使用的是ResultSetHandler
		 * 	在Spring的jdbcTemplate中,使用RowMapper。
		 * 		BeanPropertyRowMapper 可以把一个结果集转成一个bean对象
		 */
		Employee employee = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper(Employee.class), 5);
		System.out.println(employee);
	}



实验5:查询salary>4000的数据库记录,封装为List集合返回

@Test
	public void test5() throws Exception {
		// 实验5:查询salary>4000的数据库记录,封装为List集合返回
		String sql = "select id,name,salary from employee where salary > ?";
		List<Employee> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper(Employee.class), 4000);
		System.out.println(list);
	}

实验6:查询最大salary

@Test
	public void 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 -->
	<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" >
		<constructor-arg name="dataSource" ref="c3p0DataSource" />
	</bean>

@ContextConfiguration(locations = "classpath:application.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class JdbcTest {

	@Autowired
	DataSource dataSource;

	@Autowired
	JdbcTemplate jdbcTemplate;

	@Autowired
	NamedParameterJdbcTemplate namedParameterJdbcTemplate;

	@Test
	public void 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);
    }

NamedParameterJdbcTemplate 的更新方法:


实验8:重复实验7,以SqlParameterSource形式传入参数值

@Test
	public void test8() throws Exception {
		// 实验8:重复实验7,以SqlParameterSource形式传入参数值
		String sql = "insert into employee(`name`,`salary`) values(:name,:salary)";
		//通过一个bean对象的属性会自动赋值
		SqlParameterSource sqlParameterSource = new BeanPropertySqlParameterSource(new Employee(0,
				"小新", new BigDecimal(11111)));
		namedParameterJdbcTemplate.update(sql, sqlParameterSource);
	}

实验9:创建Dao,自动装配JdbcTemplate对象

// 实验9:创建Dao,自动装配JdbcTemplate对象

添加类

@Repository
public class EmployeeDao {

	@Autowired
	JdbcTemplate jdbcTemplate;

	public int saveEmployee(Employee employee) {
		String sql = "insert into employee(`name`,`salary`) values(?,?)";
		return jdbcTemplate.update(sql, employee.getName(), employee.getSalary());
	}
}

在applicationContext.xml中配置

<!-- 添加包扫描 -->

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

 

测试代码

@ContextConfiguration(locations = "classpath:application.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class JdbcTest {

	@Autowired
	DataSource dataSource;

	@Autowired
	JdbcTemplate jdbcTemplate;

	@Autowired
	NamedParameterJdbcTemplate namedParameterJdbcTemplate;

	@Autowired
	EmployeeDao employeeDao;

	@Test
	public void test9() throws Exception {
		// 实验9:创建Dao,自动装配JdbcTemplate对象
		employeeDao.saveEmployee(new Employee(null, "ssssss", new BigDecimal(999)));
	}

实验10:通过继承JdbcDaoSupport创建JdbcTemplate的Dao

@Repository
public class EmployeeDao extends JdbcDaoSupport{
	// 实验9:创建Dao,自动装配JdbcTemplate对象
//	@Autowired
//	JdbcTemplate jdbcTemplate;
//	public int saveEmployee(Employee employee){
//		String sql = "insert into employee(`name`,`salary`) values(?,?)";
//		return jdbcTemplate.update(sql,employee.getName(),employee.getSalary());
//	}
	/*
	 * @Autowired注入的是自己的方法,不可以在setJdbcTemplate(jdbcTemplate);上注入
	 * 因为这是别人的源码,但源码中又是用final修饰发方法,也不能重写,所以我们只能自定义一个方法,将方法进行装配,进而
	 * 参数就装配进去了
	 */
	
	@Autowired
	public void setff(JdbcTemplate jdbcTemplate){
		setJdbcTemplate(jdbcTemplate);
	}
	@Autowired
	public void setff(DataSource jdbcTemplate){
		setDataSource(jdbcTemplate);
	}
	//JdbcDaoSupport里面会自动封装jdbcTemplate,所以有自带的getJdbcTemplate()方法
	public int saveEmployee(Employee employee){
		String sql = "insert into employee(`name`,`salary`) values(?,?)";
		return getJdbcTemplate().update(sql,employee.getName(),employee.getSalary());
	}
	//因为jdbcTemplate就是对jdbc的封装类,所以用setDataSource()也可以接收


 Spring详解二之注解功能及其他

常用spring开发jar包即额外包下载


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值