1、AOP切面编程
1.1、什么是AOP
1.2、一个简单计算数功能加日记引入话题
1.3、原始方法统一日记处理
1.4、使用代理实现日记
1.4.1、使用jdk动态代理(接口代理:必须要有接口)统一日记
1.4.2、Cglib代理介绍
1.5、AOP编程的专业术语
1.6、使用Spring实现AOP简单切面编程
1.7、Spring的切入点表达式
1.8、Spring切面中的代理对象
1.9、Spring通知的执行顺序
1.10、获取连接点信息(通知的参数)
1.11、获取拦截方法的返回值和抛的异常信息(通知的参数)
1.12、Spring的环绕通知
1.13、切入点表达式的复用(引用)
1.14、多个通知的执行顺序
1.15、如何基于xml配置aop程序
2、Spring之数据访问
2.1、Spring数据访问工程环境搭建
2.2、Spring之JdbcTemplate使用
1、AOP切面编程
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.2、Cglib代理介绍
区别:
Cglib代理为目标对象的类产生一个继承关系的子类(类代理),
jdk动态代理为目标对象的类产生一个实现了同一接口平级的无关联类(接口代理)
优点:在没有接口的情况下,同样可以实现代理的效果。
缺点:同样需要自己编码实现代理全部过程。
但是为了更好的整合Spring框架使用。所以我们需要学习一下Spring 的AOP 功能。也就是学习Spring提供的AOP功能,而aop并不是spring独有的,很多框架也有aop功能。
注:Spring 底层结合使用了jdk动态代理与cglib代理
1.5、AOP编程的专业术语
通知(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.7、Spring的切入点表达式
@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.8、Spring切面中的代理对象
在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.9、Spring通知的执行顺序
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.12、Spring的环绕通知
必须具备的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);
}
}
测试运行的结果:
2、Spring之数据访问
2.1、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(不需要导,因为框架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.2、Spring之JdbcTemplate使用
在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()也可以接收