Spring:AOP(面向切面编程),Spring的JDBC模板类

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring:AOP(面向切面编程),Spring的JDBC模板类相关的知识,希望对你有一定的参考价值。

1 AOP概述

 1.2 什么是AOP

在软件业,AOP为Aspect Oriented Programmig的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP解决了OOP遇到一些问题,采取横向抽取机制,取代了传统纵向继承体系重复性的代码,应用于性能监视、事务管理、安全检查、缓存。

1.3 为什么学习AOP

需求:需要在所有Dao类的save方法之前进行权限校验,必须是管理员才可以进行保存操作。

传统方式:需要在修改源代码的基础上编写程序。

AOP:底层使用代理机制完成,不需要修改源代码。

1.4 AOP的相关术语

  • Joinpoint(连接点):所谓连接点是指那些可以被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
  • Pointcut(切入点):所谓切入点是指我们要拦截的Joinpoint的定义。
  • Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情,即方法层面的增强。通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)。
  • Introduction(引介):引介是一种特殊的通知,是类层面的增强,通过一种字节码技术在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或Field属性。
  • Target(目标对象):代理的目标对象。
  • Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
  • Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
  • Aspect(切面): 是切入点和通知(引介)的结合。

技术分享

2 Spring的传统AOP

2.1 Spring的传统AOP底层实现

2.1.1 Spring的传统AOP代理机制

Spring的传统AOP底层使用了两种代理机制:

1、JDK动态代理:对实现了接口的类才生成代理对象

2、CGLIB动态代理:对没有实现接口的类产生代理对象,产生的是这个类的子类对象。Spring自带CGLIB类库

Spring的传统AOP根据类是否实现接口来决定使用哪种代理机制:如果类实现接口,使用JDK动态代理完成AOP;如果类没有实现接口,采用CGLIB动态代理完成AOP

2.1.2 JDK的动态代理原理

技术分享

技术分享

代理类MyJdkProxy:

public class MyJdkProxy implements InvocationHandler {

    private UserDao userDao;

    public MyJdkProxy(UserDao userDao) {
        this.userDao = userDao;
    }

    public UserDao createProxy() {
        UserDao proxy = (UserDao) Proxy.newProxyInstance(userDao.getClass()
                .getClassLoader(), userDao.getClass().getInterfaces(), this);//也可以使用匿名内部类
        return proxy;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        if("save".equals(method.getName())){
            System.out.println("==================权限校验===============");
            return method.invoke(userDao, args);
        }
        return method.invoke(userDao, args);
    }
}

技术分享

测试结果:

技术分享

2.1.3 CGLIB动态代理原理

技术分享

技术分享

代理类MyCglibProxy:

public class MyCglibProxy implements MethodInterceptor{
    
    private OrderDao orderDao;
    
    public MyCglibProxy(OrderDao orderDao) {
        this.orderDao = orderDao;
    }

    public OrderDao createProxy(){
        // 1.创建一个CGLIB的核心类:
        Enhancer enhancer = new Enhancer();
        // 2.设置父类:
        enhancer.setSuperclass(orderDao.getClass());
        // 3.设置回调:
        enhancer.setCallback(this);
        // 4.生成代理 :
        OrderDao proxy = (OrderDao) enhancer.create();
        return proxy;
    }

    @Override
    public Object intercept(Object proxy, Method method, Object[] args,
            MethodProxy methodProxy) throws Throwable {
        if("update".equals(method.getName())){
            long begin = System.currentTimeMillis();
            System.out.println("开始时间:=========="+begin);
            
            Object obj = methodProxy.invokeSuper(proxy, args);
            
            long end = System.currentTimeMillis();
            System.out.println("结束时间:=========="+end);
            return obj;
        }
        return methodProxy.invokeSuper(proxy, args);
    }
}

技术分享

测试结果:

技术分享

2.2 Spring的传统AOP的开发

2.2.1 Spring传统AOP中定义的通知类型

AOP的概念是由AOP联盟组织提出的。AOP联盟为通知Advice定义了接口org.aopalliance.aop.Interface.Advice

Spring中按照通知Advice在目标类方法的连接点位置,可分为5类:

  1. 前置通知:org.springframework.aop.MethodBeforeAdvice 在目标方法执行前实施增强
  2. 后置通知:org.springframework.aop.AfterReturningAdvice 在目标方法执行后实施增强
  3. 环绕通知:org.aopalliance.intercept.MethodInterceptor 在目标方法执行前后实施增强
  4. 异常抛出通知:org.springframework.aop.ThrowsAdvice 在方法抛出异常后实施增强
  5. 引介通知:org.springframework.aop.IntroductionInterceptor 在目标类中添加一些新的方法和属性

2.2.2 Spring传统AOP中定义的切面类型

  1. Advisor:代表一般切面,其中的通知Advice本身就是一个切面,对目标类所有方法进行拦截。(不带切入点切面:增强所有方法)
  2. PointcutAdvisor:代表具有切点的切面,可以指定拦截目标类哪些方法。(带有切入点切面:增强某些方法)
  3. IntroductionAdvisor:代表引介切面,针对引介通知而使用切面。(不要求掌握)

2.2.3 方式一:基于ProxyFactoryBean的代理

2.2.3.1 不带切入点切面的开发

步骤一:引入jar包

在引入Spring IOC开发包(6个)以及与JUnit整合的测试包(1个)的基础上,还要引入如下2个包:

  • AOP联盟的开发包:com.springsource.org.aopalliance-1.0.0.jar
  • spring的AOP的开发包:spring-aop-3.2.0.RELEASE.jar

技术分享

步骤二:创建包结构

com.itheima.spring.demo3
  ProductDao
  ProductDaoImpl

这里目标类ProductDaoImpl实现了一个接口ProductDao

步骤三:注入目标类

src目录下创建spring配置文件,完成Dao的注入

<!-- 配置目标类: -->
<bean id="productDao" class="com.itheima.spring.demo3.ProductDaoImpl"/>

步骤四:定义通知Advice类

使用Spring传统AOP中定义的一般切面,即不带切入点的切面,来增强目标类中的所有方法。

public class MyBeforeAdvice implements MethodBeforeAdvice{

    @Override
    public void before(Method method, Object[] args, Object target)
            throws Throwable {
        System.out.println("==============前置通知=============");
    }

}

步骤五:在Spring中配置通知

在spring配置文件中配置:

<!-- 配置通知:(前置通知) -->
<bean id="beforeAdvice" class="com.itheima.spring.demo3.MyBeforeAdvice"></bean>

因为没有切入点,所以这里通知Advice本身就是一个切面,所以不需要额外配置切面。(切面=切入点+通知)

步骤六:配置对目标类DAO生成代理

这里要在spring配置文件中配置ProxyFactoryBean类的bean。

首先了解一下ProxyFactoryBean常用可配置属性:

  • target:代理的目标对象
  • proxyInterfaces:代理要实现的接口。如果多个接口可以使用以下格式赋值:<list><value></value>....</list>
  • proxyTargetClass:是否对类代理而不是接口,设置为true时,使用CGLib代理
  • interceptorNames:需要织入目标的Advice
  • singleton:返回代理是否为单实例,默认为单例
  • optimize:当设置为true时,强制使用CGLib(在配置了proxyInterfaces默认使用jdk动态代理情况下)

配置对目标类生成代理:

<!-- 配置生成代理 -->
<bean id="productDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <!-- 配置目标类 -->
    <property name="target" ref="productDao"/>
    <!-- 配置类的实现的接口,这里只有一个接口 -->
    <property name="proxyInterfaces" value="com.itheima.spring.demo3.ProductDao"/>
    <!-- 配置切面(这里通知即切面),要拦截的名称(因为这里interceptorNames要的是名称而不是对象,所以使用value而不是ref)-->
    <property name="interceptorNames" value="beforeAdvice"/>
</bean>

步骤七:编写测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo3 {

    // @Resource(name = "productDao")
    // 注入代理类:
    @Resource(name="productDaoProxy")
    private ProductDao productDao;

    @Test
    public void demo1() {
        productDao.save();
        productDao.update();
        productDao.delete();
        productDao.find();
    }
}

测试结果:

技术分享

2.2.3.2 带切入点切面的开发

步骤一:引入jar包

同2.2.1.3步骤一

步骤二:创建包结构

com.itheima.spring.demo4

  CustomerDao

这里目标类CustomerDao没有实现接口

步骤三:注入目标类

src目录下创建spring配置文件,完成Dao的注入:

<!-- 配置目标类 -->
    <bean id="customerDao" class="com.itheima.spring.demo4.CustomerDao"/>

步骤四:定义通知类

这里MethodInterceptor是环绕通知:

public class MyAroundAdvice implements MethodInterceptor{

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("环绕前通知=================");
        // 执行目标方法:
        Object obj = methodInvocation.proceed();
        System.out.println("环绕后通知=================");
        return obj;
    }

}

步骤五:在Spring中配置通知

<!-- 配置通知:(环绕通知) -->
<bean id="myAroundAdvice" class="com.itheima.spring.demo4.MyAroundAdvice"/>

 步骤六:在Spring中配置带有切入点的切面

因为这里是带有切入点的切面,针对部分方法进行增强,而不是针对所有方法,所以需要进行切面配置。

查看Spring传统AOP中定义带有切入点的切面PointCutAdvisor接口的实现类:

技术分享

下面演示配置正则表达式方法带有切入点的切面实现类:RegexpMethodPointcutAdvisor。需要使用正则表达式配置哪些类的哪些方法需要进行增强。

<!-- 配置带有切入点的切面 -->
<bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <!-- 属性pattern值是一个正则表达式。-->
    <!-- 拦截所有方法:.:任意字符  *:任意次数 -->
    <!-- <property name="pattern" value=".*"/> -->
    <!-- 拦截某一个方法,点在正则中需要被转义成普通的文本 -->
    <!-- <property name="pattern" value="com\\.itheima\\.spring\\.demo4\\.CustomerDao\\.update"/> -->
    <!-- 多个切入点使用patterns,切入点用逗号分隔 -->
    <!-- <property name="patterns" value="com\\.itheima\\.spring\\.demo4\\.CustomerDao\\.update, com\\.itheima\\.spring\\.demo4\\.CustomerDao\\.delete"/> -->
    <!-- 多个切入点的简写形式 -->
    <property name="patterns" value=".*save.*, .*update.*"/>
    <!-- 配置通知 -->
    <property name="advice" ref="myAroundAdvice"/>
</bean>

步骤六:配置对目标类DAO生成代理

<!-- 配置生成代理 -->
<bean id="customerDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <!-- 配置目标 -->
    <property name="target" ref="customerDao"/>
    <!-- 配置代理目标类 -->
    <property name="proxyTargetClass" value="true"/>
    <!-- 配置切面 -->
    <property name="interceptorNames" value="myAdvisor"/>
</bean>

步骤八:编写测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo4 {

    // @Resource(name = "customerDao")
    // 注入代理对象
    @Resource(name="customerDaoProxy")
    private CustomerDao customerDao;

    @Test
    public void demo1() {
        customerDao.save();
        customerDao.update();
        customerDao.delete();
        customerDao.find();
    }
}

测试结果:

技术分享

2.2.3.3 基于ProxyFactoryBean代理的缺点

配置麻烦,需要为每一个要增强的类配置一个ProxyFactoryBean,通过其target标签指定要增强的目标类。

2.2.4 方式二:自动代理

由于ProxyFactoryBean方式配置麻烦,一般情况下我们采用自动代理的方式。

2.2.4.1 Spring传统AOP自动代理的方式

BeanNameAutoProxyCreator:根据Bean名称创建代理,针对所有连接点都有效,不带切入点,所以不配置切面 

DefaultAdvisorAutoProxyCreator:根据Advisor本身包含的信息创建代理,可以指定接入点

AnnotationAwareAspectJAutoProxyCreator:基于Bean的AspectJ注解进行自动代理(重点,见3)

2.2.4.2 自动代理与ProxyFactoryBean对比

基于ProxyFactoryBean代理是先有被增强的目标类对象 ,将目标类的bean传递给ProxyFactoryBean,生成代理对象:

技术分享

基于ProxyFactoryBean代理注入时需要注入代理对象。

自动代理是基于BeanPostProcessor(后处理bean),是在bean的生成过程中对bean进行增强,在类实例化的过程中就产生了代理对象。

自动代理注入时需要注入目标对象。

2.2.4.3 基于Bean名称的自动代理(不带切入点):BeanNameAutoProxyCreator

将ProxyFactoryBean代理的spring配置文件进行修改,只留下目标类和通知:

<!-- 配置目标类: -->
<bean id="productDao" class="com.itheima.spring.demo3.ProductDaoImpl"/>
<bean id="customerDao" class="com.itheima.spring.demo4.CustomerDao"/>

<!-- 配置通知:(前置通知) -->
<bean id="beforeAdvice" class="com.itheima.spring.demo3.MyBeforeAdvice"/>
<!-- 配置通知:(环绕通知) -->
<bean id="myAroundAdvice" class="com.itheima.spring.demo4.MyAroundAdvice"/>

配置对所有的DAO目标类生产代理:

<!-- 配置基于Bean名称的自动代理。因为基于后处理bean,可以没有ID,由Spring内部进行调用生产代理对象 -->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <!-- 配置Bean名称 -->
    <property name="beanNames" value="*Dao"/>
    <!-- 配置切面,因为该方式针对所有连接点都有效,所以没有切入点,通知即切面 -->
    <property name="interceptorNames" value="beforeAdvice"/>
</bean>

测试类:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext2.xml")
public class SpringDemo5 {
    @Resource(name="productDao")//注入目标对象
    private ProductDao productDao;
    
    @Resource(name="customerDao")
    private CustomerDao customerDao;
    
    @Test
    public void demo1(){
        productDao.save();
        productDao.update();
        productDao.delete();
        productDao.find();
        
        customerDao.save();
        customerDao.update();
        customerDao.delete();
        customerDao.find();
    }
}

测试结果:

技术分享

2.2.4.4 基于切面信息的自动代理(带切入点):DefaultAdvisorAutoProxyCreator

将ProxyFactoryBean代理的spring配置文件进行修改,只留下目标类和通知:

<!-- 配置目标类: -->
<bean id="productDao" class="com.itheima.spring.demo3.ProductDaoImpl"/>
<bean id="customerDao" class="com.itheima.spring.demo4.CustomerDao"/>

<!-- 配置通知:(前置通知) -->
<bean id="beforeAdvice" class="com.itheima.spring.demo3.MyBeforeAdvice"/>
<!-- 配置通知:(环绕通知) -->
<bean id="myAroundAdvice" class="com.itheima.spring.demo4.MyAroundAdvice"/>

配置切面:

<!-- 配置切面,采用正则表达式方式带有切入点切面实现类 -->
<bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <!-- 表达式,定义切入点 -->
    <property name="pattern" value="com\\.itheima\\.spring\\.demo4\\.CustomerDao\\.save"/>
    <!-- 配置通知 -->
    <property name="advice" ref="myAroundAdvice"/>
</bean>

配置生成代理:

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

测试类:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext3.xml")
public class SpringDemo6 {

    @Resource(name="productDao")
    private ProductDao productDao;
    
    @Resource(name="customerDao")
    private CustomerDao customerDao;
    
    @Test
    public void demo1(){
        productDao.save();
        productDao.update();
        productDao.delete();
        productDao.find();
        
        customerDao.save();
        customerDao.update();
        customerDao.delete();
        customerDao.find();
    }
}

测试结果:

技术分享

2.3 Spring传统AOP总结

1、Spring的传统AOP底层使用JDK或者Cglib产生代理

  如果类实现了接口:使用JDK动态代理。

  如果类没有实现接口:使用cglib生成代理。

2、基于ProxyFactoryBean代理方式

  不带有切点切面

  带有切入点切面

3、基于BeanPostProcessor的自动代理

  基于Bean名称自动代理

  基于切面信息的自动代理

4、自动代理和基于ProxyFactoryBean代理模式的区别

  自动代理:在类的实例对象生成过程中产生代理,返回就是代理对象。注入的时候需要注入目标对象

  基于ProxyFactoryBean代理:先有被代理实例对象 , 将被代理对象作为参数传递给ProxyFactoryBean,产生代理对象。注入的时候需要注入代理对象

 3 Spring基于AspectJ的AOP(重点)

3.1 AspectJ的概述

AspectJ是一个基于Java语言的AOP框架。AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。

Spring为了简化AOP开发引入了AspectJ作为自身AOP的开发。

Spring2.0以后新增了对AspectJ切入点表达式的支持。

@AspectJ是AspectJ1.5新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面。

在新版本Spring框架中,建议使用AspectJ方式来开发AOP。

3.2 AspectJ注解方式的AOP开发

3.2.1 开发过程

步骤一:创建web项目,引入jar包

在引入Spring IOC开发包(6个),与JUnit整合的测试包(1个),AOP联盟的开发包(1个)以及spring的AOP的开发包(1个)的基础上,还要引入:

  • AspectJ本身jar包:com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar。路径:spring-framework-3.0.2.RELEASE-dependencies\\org.aspectj\\com.springsource.org.aspectj.weaver\\1.6.8.RELEASE
  • spring支持aspectj的包:spring-framework-3.2.0.RELEASE\\libs\\spring-aspects-3.2.0.RELEASE.jar

技术分享

步骤二:创建Spring的配置文件

在src目录下创建,引入Spring的Bean和AOP约束:

<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">

步骤三:创建包结构

com.itheima.spring.demo1

  OrderDao

这里目标类没有实现接口。

步骤四:将目标类配置到Spring中

<!-- 目标类 -->
<bean id="orderDao" class="com.itheima.spring.demo1.OrderDao"/>

步骤五:编写切面类

切面 = 通知 + 切入点

AspectJ注解提供的通知类型有:

  1. @Before:前置通知,相当于Spring传统AOP的MethodBeforeAdvice
  2. @AfterReturning:后置通知,相当于Spring传统AOP的AfterReturningAdvice
  3. @Around:环绕通知,相当于Spring传统AOP的MethodInterceptor
  4. @AfterThrowing:抛出通知,相当于Spring传统AOP的ThrowAdvice
  5. @After:最终final通知,不管是否异常,该通知都会执行
  6. @DeclareParents:引介通知,相当于Spring传统AOP的IntroductionInterceptor(不要求掌握)

AspectJ注解的切入点表达式定义(用来限制哪些类的哪些方法需要进行增强):

使用方式:@通知注解("execution(切入点表达式)")

切入点表达式的语法:[访问修饰符(可省略)] 方法返回值 方法名(参数)

例如:

execution(public * com.itheim.spring.demo1.OrderDao.save(..))

execution(* *.*(..))

execution(public * com.itheim.spring.demo1.*.*(..))

execution(public * com.itheim.spring.demo1..*.*(..))

execution(public * com.itheim.spring.demo1.OrderDao+.*(..))//包含子类

编写切面类:

@Aspect
public class MyAspectAnno {

    // 定义通知和切入点: 
    @Before("execution(* com.itheima.spring.demo1.OrderDao.save(..))")
    public void before(){//方法名可以是任意的
        System.out.println("前置通知================");
    }
}

步骤六:在Spring配置文件中开启AspectJ的注解

<!-- 开启AspectJ的注解 -->
<aop:aspectj-autoproxy/>

步骤七:将切面类配置到Spring中

<!-- 配置切面类 -->
<bean class="com.itheima.spring.demo1.MyAspectAnno"/>

步骤八:编写测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo1 {

    @Resource(name="orderDao")
    private OrderDao orderDao;
    
    @Test
    public void demo1(){
        orderDao.save();
        orderDao.update();
        orderDao.delete();
        orderDao.find();
    }
}

3.2.2 AspectJ注解的通知类型的具体使用

3.2.2.1 前置通知 @Before

代表在切入点方法之前执行增强

方法参数Joinpoint:获得切入点信息

代码示例:

//单独定义切入点
@Pointcut(value="execution(* com.itheima.spring.demo1.OrderDao.save(..))")
private void pointcut2(){}

//定义前置通知并使用切入点
@Before("MyAspectAnno.pointcut2()")
public void before(JoinPoint joinPoint){
    System.out.println("前置通知================"+joinPoint);
}    

3.2.2.2 后置通知@AfterReturning

代表在切入点方法之后执行增强

因此方法参数可以获得切入点方法的返回值

方法参数JoinPoint:获得切入点信息

代码示例:

//后置通知
@AfterReturning(value="execution(* com.itheima.spring.demo1.OrderDao.update(..))",returning="result")
public void afterReturning(Object result){
    System.out.println("后置通知================"+result);
}

3.2.2.3 环绕通知@Around

代表在切入点方法之前和之后执行增强,用来控制目标方法的执行

方法参数ProceedingJoinPoint:获得切入点信息

代码示例:

// 环绕通知
@Around("execution(* com.itheima.spring.demo1.OrderDao.delete(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
    System.out.println("环绕前通知===============");
    // 执行目标方法:
    Object obj = joinPoint.proceed();//不调用该方法就会阻止目标方法的执行
    System.out.println("环绕后通知===============");
    return obj;
}

3.2.2.4 异常抛出通知@AfterThrowing

代表在切入点方法出现异常的时候执行增强

方法参数JoinPoint:获得切入点信息

方法参数Throwable:获得到异常的信息

代码示例:

// 异常抛出通知
@AfterThrowing(value="execution(* com.itheima.spring.demo1.OrderDao.find(..))",throwing="e")
public void afterThrowing(Throwable e){
    System.out.println("异常抛出通知=============="+e.getMessage());
}

3.2.2.5 最终通知@After

代表无论切入点方法是否出现异常,该通知总是会执行,类似try{}catch(){}finally{}

方法参数JoinPoint:获得切入点信息

代码示例:

// 最终通知
@After(value="execution(* com.itheima.spring.demo1.OrderDao.find(..))")
public void after(){
    System.out.println("最终通知================");
}

3.2.3 AspectJ注解的切入点定义

@Pointcut(value="execution(* com.itheima.spring.demo1.OrderDao.find(..))")
private void pointcut1(){}

注意使用private而不是public。

使用定义的切入点:

// 异常抛出通知:
@AfterThrowing(value="MyAspectAnno.pointcut1()",throwing="e")
public void afterThrowing(Throwable e){
    System.out.println("异常抛出通知=============="+e.getMessage());
}

// 最终通知
@After("MyAspectAnno.pointcut1()")
public void after(){
    System.out.println("最终通知================");
}

3.3 AspectJ的XML方式的AOP开发

3.3.1 开发过程

步骤一:创建web项目,引入jar包

同3.2.1步骤一。

步骤二:创建Spring的配置文件

同3.2.1步骤二。

步骤三:创建包结构

com.itheima.spring.demo2
  CustomerDao
  CustomerDaoImpl

这里目标类CustomerDaoImpl实现了接口CustomerDao

步骤四:将目标类配置到Spring中

<!-- 配置目标类 -->
<bean id="customerDao" class="com.itheima.spring.demo2.CustomerDaoImpl"/>

步骤五:编写切面类

public class MyAspectXml {
    public void before(){
        System.out.println("前置通知==============");
    }
}

注意这里只定义了通知,没有定义切入点,切面(切入点+通知)的完整配置在spring配置文件中进行,见步骤七。

步骤六:将切面类配置到Spring中

<!-- 配置切面类 -->
<bean id="myAspectXml" class="com.itheima.spring.demo2.MyAspectXml"/>

步骤七:在Spring中进行完整的AOP配置

这里可以配置个切入点和多个通知的组合

<!-- 配置完整AOP -->
<aop:config>
    <!-- 配置切入点 -->    
    <aop:pointcut expression="execution(* com.itheima.spring.demo2.CustomerDao+.save(..))" id="pointcut1"/>
    <!-- 配置切面,里面可以配置多个通知,关联多个切入点 -->
    <aop:aspect ref="myAspectXml">
        <aop:before method="before" pointcut-ref="pointcut1"/>
    </aop:aspect>
</aop:config>

步骤八:编写测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext2.xml")
public class SpringDemo2 {

    @Resource(name="customerDao")
    private CustomerDao customerDao;
    @Test
    public void demo1(){
        customerDao.save();
        customerDao.update();
        customerDao.delete();
        customerDao.find();
    }
}

3.3.2 AspectJ的XML方式的通知和切入点配置

<!-- 配置AOP -->
<aop:config>
    <!-- 定义多个切入点 -->
    <aop:pointcut expression="execution(* com.itheima.spring.demo2.CustomerDao+.save(..))" id="pointcut1"/>
    <aop:pointcut expression="execution(* com.itheima.spring.demo2.CustomerDao+.update(..))" id="pointcut2"/>
    <aop:pointcut expression="execution(* com.itheima.spring.demo2.CustomerDao+.delete(..))" id="pointcut3"/>
    <aop:pointcut expression="execution(* com.itheima.spring.demo2.CustomerDao+.find(..))" id="pointcut4"/>
    <!-- 配置多个通知和切入点的组合 -->
    <aop:aspect ref="myAspectXml">
        <aop:before method="before" pointcut-ref="pointcut1"/>
        <aop:after-returning method="afterReturing" pointcut-ref="pointcut2" returning="result"/>
        <aop:around method="around" pointcut-ref="pointcut3"/>
        <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="e"/>
        <aop:after method="after" pointcut-ref="pointcut4"/>
    </aop:aspect>
</aop:config>

3.4 Advisor和Aspect区别

Advisor:Spring传统的切面,一般都是由一个切入点和一个通知的组合。
Aspect:Aspect是真正意义上的切面,是由多个切入点和多个通知组合。

4 Spring的JDBC模板类

4.1 Spring的持久层模板类

Spring提供了很多持久层技术的模板类来简化编程:

技术分享

4.2 Spring JDBC模板类的使用示例

步骤一:创建web项目,引入jar包

在引入Spring IOC开发包(6个)的基础上,还要引入3个包:

  • 数据库驱动包
  • Spring JDBC模板包:spring-jdbc-3.2.0.RELEASE.jar
  • Spring事务包:spring-tx-3.2.0.RELEASE.jar

技术分享

步骤二:创建数据库

create database spring_day02;

步骤三:编写测试类

public class SpringDemo1 {
    @Test
    public void demo1(){
        // 创建连接池,使用spring内置连接池DriverManagerDataSource
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql:///spring_day02");
        dataSource.setUsername("root");
        dataSource.setPassword("123");
        // 创建Spring JDBC模板类对象
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        //使用Spring模板类对象执行SQL语句
        jdbcTemplate.execute("create table user (id int primary key auto_increment,name varchar(20))");
    }
}

4.3 Spring整合DAO层和JDBC模板的开发

在src下创建Spring配置文件applicationContext1.xml,然后在4.2的基础上进行

4.3.1 在Spring中配置连接池

4.3.1.1 spring的内置连接池

<!-- 配置Spring的内置连接池 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql:///spring_day02"/>
    <property name="username" value="root"/>
    <property name="password" value="123"/>
</bean>

4.3.1.2 DBCP连接池

引入DBCP连接池的jar包:

  • com.springsource.org.apache.commons.dbcp-1.2.2.osgi.jar
  • com.springsource.org.apache.commons.pool-1.5.3.jar

然后配置DBCP连接池:

<!-- 配置DBCP连接池 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql:///spring_day02"/>
    <property name="username" value="root"/>
    <property name="password" value="123"/>
</bean>

4.3.1.3 C3P0连接池

引入C3P0连接池的jar包:

  • com.springsource.com.mchange.v2.c3p0-0.9.1.2.jar

配置C3P0连接池:

<!-- 配置C3P0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="com.mysql.jdbc.Driver"/>
    <property name="jdbcUrl" value="jdbc:mysql:///spring_day02"/>
    <property name="user" value="root"/>
    <property name="password" value="123"/>
</bean>

4.3.2 在Spring中配置数据库.properties属性文件

在src目录下创建属性文件jdbc.properties,将数据库的相关属性配置在里面:

jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///spring_day02
jdbc.user=root
jdbc.password=123

然后在Spring配置文件中引入该属性文件,有两种方式:

第一种,使用一个<bean>配置:

<!-- 引入外部属性文件 -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="location" value="classpath:jdbc.properties"/>
</bean>

第二种(常用),基于context约束,需要在Spring配置文件中引入context约束:

<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">
    
    <!-- 引入外部属性文件 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!-- 配置C3P0连接池 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driverClass}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

</beans>

4.3.3 配置Spring JDBC模板bean到Spring中

<!-- 配置Jdbc模板bean,并注入数据源连接池对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"/>
</bean>

4.3.4 配置DAO,注入Spring JDBC模板

<!-- 配置DAO,并注入Spring JDBC模板对象 -->
<bean id="userDao" class="com.itheima.spring.demo2.UserDao">
    <property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>

这里是set方法注入属性,所以DAO类的对应属性需要提供set方法

4.3.5 编写DAO类

public class UserDao {
    private JdbcTemplate jdbcTemplate;
    //提供注入属性的set方法
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    } 
    
    public void save(User user){
        String sql = "insert into users values (null,?)";
        jdbcTemplate.update(sql, user.getName());
    }
}

4.3.5.1 JdbcDaoSupport注入JDBC模板对象

为了简便,Spring框架提供了一个工具类JdbcDaoSupport完成JDBC模板对象的注入:

技术分享

同理,在Spring整合Hibernate时,HibernateDaoSupport工具类完成Hibernate模板对象在DAO层的注入。

4.3.6 编写测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext1.xml")
public class demo1 {
    @Resource(name="userDao")
    private UserDao userDao;
    @Test
    public void demo2(){
        User user = new User();
        user.setName("小溪");
        userDao.save(user);
        
    }
}

4.4 Spring JDBC模板的CRUD操作

4.4.1 更新

update(String sql,Object... args);

4.4.2 查询

4.4.2.1 简单查询

int queryForInt(String sql,Object... args);//返回一个数,比如统计表中记录个数
<T> queryForObject(String sql,Class<T> clazz ,Object... args);//返回一个对象,第二个参数定义对象类型,比如查询某个人的名称,需要先返回一个对象

4.4.2.2 复杂查询

返回一个对象或者集合:

<T> queryForObject(String sql,RowMapper<T> rowMapper,Object... args);
List<T> query(String sql,RowMapper<T> rowMapper,Object... args);

使用RowMapper接口完成,需要自定义该接口的实现:

技术分享

以上是关于Spring:AOP(面向切面编程),Spring的JDBC模板类的主要内容,如果未能解决你的问题,请参考以下文章

Spring的AOP面向切面编程

spring框架学习——AOP( 面向切面编程)

Spring——面向切面编程(AOP模块)

Spring框架深入--AOP面向切面

Spring AOP——Spring 中面向切面编程

Spring-AOP面向切面编程