Spring详解(三)

11、 代理模式

为什么要学习代理模式?因为这就是SpringAOP的底层!

11.1、 静态代理

角色分析:

  • 抽象角色:一半会使用接口或者抽象类来解决
  • 真实角色:被代理的角色
  • 代理角色:代理真实角色,代理真是角色后,我们一般会做一些附属操作
  • 客户:访问代理对象的人!

代码步骤:

  1. 接口
package com.yujian.demo01;

//租房
public interface Rent {
    public void rent();
}
  1. 真实角色
package com.yujian.demo01;

//房东
public class Host implements Rent{
    public void rent() {
        System.out.println("房东要出租房子");
    }
}
  1. 代理角色
package com.yujian.demo01;

public class Proxy implements Rent{
    private Host host;
    public Proxy() {
    }
    public Proxy(Host host) {
        this.host = host;
    }

    public void rent() {
        seeHouse();
        host.rent();
        hetong();
        fare();
    }

    //看房
    public void seeHouse(){
        System.out.println("中介带你看房");
    }

    //收中介费
    public void fare(){
        System.out.println("中介收中介费");
    }

    //签合同
    public void hetong(){
        System.out.println("中介与你签租赁合同");
    }
}
  1. 客户端访问代理角色
package com.yujian.demo01;

public class Client {
    public static void main(String[] args) {
        //房东要租房子
        Host host = new Host();
        //代理,直接帮房东租房子,但是代理角色会有一些附属操作
        Proxy proxy = new Proxy(host);

        //你不用面对房东,直接找中介即可
        proxy.rent();
    }
}

代理模式的好处:

  • 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
  • 公共业务交给代理角色!实现了业务的分工!
  • 公共业务发生扩展的时候,方便集中管理!

缺点:

  • 一个真实角色就会产生一个代理角色;代码量会翻倍,开发效率变低

11.2、 加深理解

  1. 接口
package com.yujian.demo02;

public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void query();
}
  1. Service层
package com.yujian.demo02;

//真实对象
public class UserServiceImpl implements UserService{
    public void add() {
        System.out.println("增加了一个用户");
    }

    public void delete() {
        System.out.println("删除了一个用户");
    }

    public void update() {
        System.out.println("修改了一个用户");
    }

    public void query() {
        System.out.println("查询了一个用户");
    }
}
  1. 代理
package com.yujian.demo02;

public class UserServiceProxy implements UserService{

    private UserService userService;

    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void add() {
        log("add");
        userService.add();
    }

    public void delete() {
        log("delete");
        userService.delete();
    }

    public void update() {
        log("update");
        userService.update();
    }

    public void query() {
        log("query");
        userService.query();
    }

    //日志方法
    public void log(String msg){
        System.out.println("[Debug]使用了"+msg+"方法");
    }
}
  1. 客户端
package com.yujian.demo02;

public class Client {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();

        UserServiceProxy proxy = new UserServiceProxy();
        proxy.setUserService(userService);

        proxy.add();
    }
}

11.3、 动态代理

  • 动态代理和静态代理角色一样
  • 动态代理的代理类是动态生成的,不是我们直接写好的!
  • 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
    • 基于接口 — JDK 动态代理
    • 基于类: cglib
    • java字节码实现: javasist

需要了解两个类: Proxy: 代理, InvocationHandler: 调用处理程序

动态代理的好处:

  • 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
  • 公共业务交给代理角色!实现了业务的分工!
  • 公共业务发生扩展的时候,方便集中管理!
  • 一个动态代理类代理的是一个接口,一般就是对应的一类业务
  • 一个动态代理类可以代理多个类,只要是实现了同一个接口即可
  1. 接口
package com.yujian.demo02;

public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void query();
}
  1. Service层实现类
package com.yujian.demo02;

//真实对象
public class UserServiceImpl implements UserService{
    public void add() {
        System.out.println("增加了一个用户");
    }

    public void delete() {
        System.out.println("删除了一个用户");
    }

    public void update() {
        System.out.println("修改了一个用户");
    }

    public void query() {
        System.out.println("查询了一个用户");
    }
}
  1. 动态生成代理对象
package com.yujian.demo04;


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//等会我们会用这个类自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {

    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    //生成得到代理类
    public Object getProxy(){
       return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }

    //处理代理实例,并返回结果 :
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        log(method.getName());

        Object result = method.invoke(target, args);
        return result;

    }

    public void log(String msg){
        System.out.println("执行了"+msg+"方法");
    }
}
  1. 测试
package com.yujian.demo04;

import com.yujian.demo02.UserService;
import com.yujian.demo02.UserServiceImpl;

public class Client {
    public static void main(String[] args) {
        //真实角色
        UserServiceImpl userService = new UserServiceImpl();

        //代理角色,不存在
        ProxyInvocationHandler pih = new ProxyInvocationHandler();

        //设置要代理的对象
        pih.setTarget(userService);

        //动态生成代理类
        UserService proxy = (UserService) pih.getProxy();

        proxy.delete();


    }
}

12、 AOP

12.1、 什么是AOP

除了控制反转(IoC)和依赖注入(DI)外,Spring 框架还提供了对面向切面编程(AOP)的支持。本节,我们就对 AOP 面向切面编程进行讲解。
AOP 的全称是“Aspect Oriented Programming”,译为“面向切面编程”,和 OOP(面向对象编程)类似,它也是一种编程思想。

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

通俗描述 : 不通过修改源代码方式,在主干方法中添加新的功能

Spring详解(三)_第1张图片


Spring详解(三)_第2张图片

12.2、AOP 的类型

AOP 可以被分为以下 2 个不同的类型。

1. 动态 AOP

动态 AOP 的织入过程是在运行时动态执行的。其中最具代表性的动态 AOP 实现就是 Spring AOP,它会为所有被通知的对象创建代理对象,并通过代理对象对被原对象进行增强。

相较于静态 AOP 而言,动态 AOP 的性能通常较差,但随着技术的不断发展,它的性能也在不断的稳步提升。
动态 AOP 的优点是它可以轻松地对应用程序的所有切面进行修改,而无须对主程序代码进行重新编译。

2. 静态 AOP

静态 AOP 是通过修改应用程序的实际 Java 字节码,根据需要修改和扩展程序代码来实现织入过程的。最具代表性的静态 AOP 实现是 AspectJ。

相较于动态 AOP 来说,性能较好。但它也有一个明显的缺点,那就是对切面的任何修改都需要重新编译整个应用程序。

12.3、AOP 的优势

AOP 是 Spring 的核心之一,在 Spring 中经常会使用 AOP 来简化编程。

在 Spring 框架中使用 AOP 主要有以下优势。

  • 提供声明式企业服务,特别是作为 EJB 声明式服务的替代品,最重要的是,这种服务是声明式事务管理。
  • 允许用户实现自定义切面。在某些不适合用 OOP 编程的场景中,采用 AOP 来补充。
  • 可以对业务逻辑的各个部分进行隔离,从而使业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时也提高了开发效率。

12.4、 AOP在Spring中的作用

1. AOP 术语

与大多数的技术一样,AOP 已经形成一套属于自己的概念和术语。

名称 说明
Joinpoint(连接点) AOP 的核心概念,指的是程序执行期间明确定义的一个点,例如方法的调用、类初始化、对象实例化等。 在 Spring 中,连接点则指可以被动态代理拦截目标类的方法。
Pointcut(切入点) 又称切点,指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。
Advice(通知) 指拦截到 Joinpoint 之后要执行的代码,即对切入点增强的内容。
Target(目标) 指代理的目标对象,通常也被称为被通知(advised)对象。
Weaving(织入) 指把增强代码应用到目标对象上,生成代理对象的过程。
Proxy(代理) 指生成的代理对象。
Aspect(切面) 切面是切入点(Pointcut)和通知(Advice)的结合。

提供声明式事务;允许用户自定义切面

  • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等等。。。
  • 切面(ASPECT):横切关注点被模块化的特殊对象,即,他是一个类。
  • 通知(Advice):切面必须要完成的工作。即,他是类中的一个方法。
  • 目标(Target):被通知的对象。
  • 代理(Proxy):向目标对象应用通知之后创建的对象。
  • 切入点(PointCut):切面通知执行的“地点”的定义。
  • 连接点(JointPoint):与切入点匹配的执行点。
  • 引入(introduction):允许我们向现有的类添加新方法属性。这不就是把切面(也就是新方法属性:通知定义的)用到目标类中吗
  • 织入(weaving):把切面应用到目标对象来创建新的代理对象的过程。有3种方式,spring采用的是运行时。

Spring详解(三)_第3张图片

SpringAOP中,通过Advice定义横切逻辑。Spring中支持5种类型的Advice:

通知 说明
before(前置通知) 通知方法在目标方法调用之前执行
after(后置通知) 通知方法在目标方法返回或异常后调用
after-returning(返回后通知) 通知方法会在目标方法返回后调用
after-throwing(抛出异常通知) 通知方法会在目标方法抛出异常后调用
around(环绕通知) 通知方法会将目标方法封装起来

Spring详解(三)_第4张图片

即AOP在不改变原有代码的情况下,去增加新的功能。


用通俗的话讲

连接点:类里面哪些方法可以被增强,这些方法被称为连接点

切入点:实际被真正增强的方法,称为切入点

通知(增强):实际增强的逻辑部分称为通知(增强)。通知的类型:前置通知、后置通知、环绕通知、异常通知、最终通知

切面:把通知应用到切入点过程(是动作)

12.5、 使用Spring实现AOP

Spring AOP 是 Spring 框架的核心模块之一,它使用纯 Java 实现,因此不需要专门的编译过程和类加载器,可以在程序运行期通过代理方式向目标类织入增强代码。

【重点】使用AOP织入,需要导入一个依赖包!


<dependency>
    <groupId>org.aspectjgroupId>
    <artifactId>aspectjweaverartifactId>
    <version>1.9.6version>
dependency>

以下我们对Aspectj进行简单介绍下

12.5.1AspectJ 简介

AspectJ:Java社区里最完整最流行的AOP框架。在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP。

在 Spring 中使用 AspectJ 进行 AOP 操作

实现 AOP 操作的步骤

  1. 编写切面类(通过 @Aspect 注解标识这是一个切面类),并且不要忘记将切面类交给 Spring IOC 管理(Component 注解),并编写相应的通知方法与切入点表达式

  2. 在 Spring 配置文件中开启 aop 功能:通过 < aop:aspectj-autoproxy/ > 注解开启 aop 功能.

  3. 当Spring IOC容器侦测到bean配置文件中的< aop:aspectj-autoproxy >元素时,会自动为与AspectJ切面匹配的bean创建代理


AspectJ支持5种类型的通知注解

[1]@Before:前置通知,在方法执行之前执行

[2]@After:后置通知,在方法执行之后执行

[3]@AfterRunning:返回通知,在方法返回结果之后执行

[4]@AfterThrowing:异常通知,在方法抛出异常之后执行

[5]@Around:环绕通知,围绕着方法执行


切入点表达式的相关细节

切入点的作用:通过表达式的方式定位一个或多个具体的连接点(哪些方法需要被增强)

切入点表达式的语法格式execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名]([参数列表]))


切入点表达式的举例一

表达式:execution(* com.yujian.service.UserServiceImpl.*(..))

含义:增强 UserServiceImpl 接口中声明的所有方法

解释说明:第一个" * "代表任意修饰符及任意返回值第二个" * "代表任意方法“…”匹配任意数量、任意类型的参数

注:若目标类、接口与该切面类在同一个包中可以省略包名


切入点表达式的举例二

表达式:execution(public * UserServiceImpl.*(..))

含义: 增强 UserServiceImpl 接口的所有公有方法(TMD 接口中的方法不都是 public 吗)


切入点表达式的举例三

表达式:execution(public double UserServiceImpl.*(..))

含义:增强 ArithmeticCalculator 接口中返回double类型数值的方法


切入点表达式的举例四

表达式:execution(public double UserServiceImpl.*(double, ..))

含义:第一个参数为double类型的方法。“…” 匹配任意数量、任意类型的参数


切入点表达式的举例五

表达式: execution(public double ArithmeticCalculator.*(double, double))

含义:参数类型为double,double类型的方法


切入点表达式的举例六:在AspectJ中,切入点表达式可以通过 “&&”、“||”、“!”等操作符结合起来。

表达式:execution (* *.add(int,..)) || execution(* *.sub(int,..))

含义:任意类中第一个参数为int类型的add方法或sub方法


将切入点表达式应用到实际的切面类中

当前连接点的相关细节

切入点表达式通常都会是从宏观上定位一组方法,和具体某个通知的注解结合起来就能够确定对应的连接点。

那么就一个具体的连接点而言,我们可能会关心这个连接点的一些具体信息,例如:当前连接点所在方法的方法名、当前传入的参数值等等。这些信息都封装在JoinPoint接口的实例对象中

五种通知的相关细节

通知的概述

  1. 在具体的连接点上要执行的操作。
  2. 一个切面可以包括一个或者多个通知。
  3. 通知所使用的注解的值往往就是切入点表达式

前置通知

前置通知:在方法执行之前执行的通知,使用@Before注解


后置通知

后置通知:后置通知是在连接点完成之后执行的,即连接点返回结果或者抛出异常的时候,使用@After注解


返回通知

返回通知:无论连接点是正常返回还是抛出异常,后置通知都会执行。如果只想在连接点返回的时候记录日志,应使用返回通知代替后置通知。使用@AfterReturning注解

在返回通知中访问连接点的返回值

  1. 在返回通知中,只要将returning属性添加到@AfterReturning注解中,就可以访问连接点的返回值。该属性的值即为用来传入返回值的参数名称
  2. 必须在通知方法的签名中添加一个同名参数。在运行时Spring AOP会通过这个参数传递返回值
  3. 原始的切点表达式需要出现在pointcut属性中

异常通知

异常通知:只在连接点抛出异常时才执行异常通知

throwing属性添加到@AfterThrowing注解中,也可以访问连接点抛出的异常。Throwable是所有错误和异常类的顶级父类,所以在异常通知方法可以捕获到任何错误和异常。

如果只对某种特殊的异常类型感兴趣,可以将参数声明为其他异常的参数类型。然后通知就只在抛出这个类型及其子类的异常时才被执行

环绕通知

环绕通知是所有通知类型中功能最为强大的,能够全面地控制连接点,甚至可以控制是否执行连接点。

对于环绕通知来说,连接点的参数类型必须是ProceedingJoinPoint。它是 JoinPoint的子接口,允许控制何时执行,是否执行连接点。

在环绕通知中需要明确调用ProceedingJoinPointproceed()方法来执行被代理的方法。如果忘记这样做就会导致通知被执行了,但目标方法没有被执行。

注意:环绕通知的方法需要返回目标方法执行之后的结果,即调用 joinPoint.proceed();的返回值,否则会出现空指针异常。

12.5.2 Spring AOP 切面类型

Spring 使用 org.springframework.aop.Advisor 接口表示切面的概念,实现对通知(Adivce)和连接点(Joinpoint)的管理。

在 Spring AOP 中,切面可以分为三类:一般切面、切点切面和引介切面。

切面类型 接口 描述
一般切面 org.springframework.aop.Advisor Spring AOP 默认的切面类型。 由于 Advisor 接口仅包含一个 Advice(通知)类型的属性,而没有定义 PointCut(切入点),因此它表示一个不带切点的简单切面。 这样的切面会对目标对象(Target)中的所有方法进行拦截并织入增强代码。由于这个切面太过宽泛,因此我们一般不会直接使用。
切点切面 org.springframework.aop.PointcutAdvisor Advisor 的子接口,用来表示带切点的切面,该接口在 Advisor 的基础上还维护了一个 PointCut(切点)类型的属性。 使用它,我们可以通过包名、类名、方法名等信息更加灵活的定义切面中的切入点,提供更具有适用性的切面。
引介切面 org.springframework.aop.IntroductionAdvisor Advisor 的子接口,用来代表引介切面,引介切面是对应引介增强的特殊的切面,它应用于类层面上,所以引介切面适用 ClassFilter 进行定义。

12.5.3 一般切面的 AOP 开发

当我们在使用 Spring AOP 开发时,若没有对切面进行具体定义,Spring AOP 会通过 Advisor 为我们定义一个一般切面(不带切点的切面),然后对目标对象(Target)中的所有方法连接点进行拦截,并织入增强代码。

示例 1

下面我们就通过一个简单的实例演示下一般切面的 AOP 开发流程。

  1. 在com.yujian.dao 包下,创建一个名为 UserDao 的接口,代码如下。

    package com.yujian.dao;
    
    public interface UserDao {
        public void add();
    
        public void delete();
    
        public void modify();
    
        public void get();
    }
    
  2. 在 com.yujian.dao.impl 包下,创建 UserDao 的实现类 UserDaoImpl,代码如下。

    package com.yujian.dao.impl;
    
    import com.yujian.dao.UserDao;
    
    public class UserDaoImpl implements UserDao {
        @Override
        public void add() {
            System.out.println("正在执行 UserDao 的 add() 方法……");
        }
        @Override
        public void delete() {
            System.out.println("正在执行 UserDao 的 delete() 方法……");
        }
        @Override
        public void modify() {
            System.out.println("正在执行 UserDao 的 modify() 方法……");
        }
        @Override
        public void get() {
            System.out.println("正在执行 UserDao 的 get() 方法……");
        }
    }
    
  3. 在 com.yujian.advice 包下,创建一个名为 UserDaoBeforeAdvice 的前置增强类,代码如下。

    package com.yujian.advice;
    
    import org.springframework.aop.MethodBeforeAdvice;
    
    import java.lang.reflect.Method;
    
    /**
     * 增强代码
     * MethodBeforeAdvice 前置增强
     */
    public class UserDaoBeforeAdvice implements MethodBeforeAdvice {
    
        @Override
        public void before(Method method, Object[] args, Object target) throws Throwable {
            System.out.println("正在执行前置增强操作…………");
        }
    
        
    
    }
    
  4. 在 resources目录下创建一个 Spring 的配置文件 bean.xml,配置内容如下。

    
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        
    
        
        <bean id="userDao" class="com.yujian.dao.impl.UserDaoImpl">bean>
    
        
        <bean id="beforeAdvice" class="com.yujian.advice.UserDaoBeforeAdvice"/>
    
        
        <bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
            
            <property name="target" ref="userDao"/>
            
            <property name="proxyInterfaces" value="com.yujian.dao.UserDao"/>
            
            <property name="interceptorNames" value="beforeAdvice"/>
        bean>
    
    beans>
    

    Spring 能够基于 org.springframework.aop.framework.ProxyFactoryBean 类,根据目标对象的类型(是否实现了接口)自动选择使用 JDK 动态代理或 CGLIB 动态代理机制,为目标对象(Target Bean)生成对应的代理对象(Proxy Bean)。

    ProxyFactoryBean 的常用属性如下表所示。

    属性 描述
    target 需要代理的目标对象(Bean)
    proxyInterfaces 代理需要实现的接口,如果需要实现多个接口,可以通过 元素进行赋值。
    proxyTargetClass 针对类的代理,该属性默认取值为 false(可省略), 表示使用 JDK 动态代理;取值为 true,表示使用 CGlib 动态代理
    interceptorNames 拦截器的名字,该属性的取值既可以是拦截器、也可以是 Advice(通知)类型的 Bean,还可以是切面(Advisor)的 Bean。
    singleton 返回的代理对象是否为单例模式,默认值为 true。
    optimize 是否对创建的代理进行优化(只适用于CGLIB)。
  5. 在 com.yujian 包下,创建一个名为 MainApp 的类,代码如下。

    package com.yujian;
    
    import com.yujian.dao.UserDao;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class MainApp {
        public static void main(String[] args) {
            //获取 ApplicationContext 容器
            ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
            //获取代理对象
            UserDao userDao = context.getBean("userDaoProxy", UserDao.class);
            //调用 UserDao 中的各个方法
            userDao.add();
            userDao.delete();
            userDao.get();
            userDao.modify();
        }
    }
    
    
  6. 执行 MainApp 中的 main 方法,控制台输出如下。

    正在执行前置增强操作…………
    正在执行 UserDao 的 add() 方法……
    正在执行前置增强操作…………
    正在执行 UserDao 的 delete() 方法……
    正在执行前置增强操作…………
    正在执行 UserDao 的 get() 方法……
    正在执行前置增强操作…………
    正在执行 UserDao 的 modify() 方法……

    从控制台输出可以看出,UserDao 接口中的所有方法都被增强了。

12.5.4 基于 PointcutAdvisor 的 AOP 开发

PointCutAdvisor 是 Adivsor 接口的子接口,用来表示带切点的切面。使用它,我们可以通过包名、类名、方法名等信息更加灵活的定义切面中的切入点,提供更具有适用性的切面。

Spring 提供了多个 PointCutAdvisor 的实现,其中常用实现类如如下。

  • NameMatchMethodPointcutAdvisor:指定 Advice 所要应用到的目标方法名称,例如 hello* 代表所有以 hello 开头的所有方法。
  • RegExpMethodPointcutAdvisor:使用正则表达式来定义切点(PointCut),RegExpMethodPointcutAdvisor 包含一个 pattern 属性,该属性使用正则表达式描述需要拦截的方法。
示例 2

下面我们就通过一个简单的实例,演示下切点切面的 AOP 开发。

  1. 在 my-spring-aop-demo 的 com.yujian.dao 包下,创建一个名为 OrderDao,代码如下。

    package com.yujian.dao;
    
    public class OrderDao {
        public void add() {
            System.out.println("正在执行 UserDao 的 add() 方法……");
        }
        public void adds() {
            System.out.println("正在执行 UserDao 的 adds() 方法……");
        }
        public void delete() {
            System.out.println("正在执行 UserDao 的 delete() 方法……");
        }
        public void modify() {
            System.out.println("正在执行 UserDao 的 modify() 方法……");
        }
        public void get() {
            System.out.println("正在执行 UserDao 的 get() 方法……");
        }
    }
    
  2. 在 com.yujian.advice 包下,创建一个名为 OrderDaoAroundAdvice 的环绕增强类,代码如下。

    package com.yujian.advice;
    
    import org.aopalliance.intercept.MethodInterceptor;
    import org.aopalliance.intercept.MethodInvocation;
    
    /**
     * 增强代码
     * 环绕增强
     *
     * @author c语言中文网 c.biancheng.net
     */
    public class OrderDaoAroundAdvice implements MethodInterceptor {
        @Override
        public Object invoke(MethodInvocation methodInvocation) throws Throwable {
            System.out.println("环绕增强前********");
            //执行被代理对象中的逻辑
            Object result = methodInvocation.proceed();
            System.out.println("环绕增强后********");
            return result;
        }
    }
    
  3. 在 bean1.xml 中添加以下配置。

    
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        
        
        <bean id="orderDao" class="com.yujian.dao.OrderDao">bean>
    
        
        <bean id="aroundAdvice" class="com.yujian.advice.OrderDaoAroundAdvice">bean>
    
        
        <bean id="myPointCutAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
            
            
            <property name="patterns" value="com.yujian.dao.OrderDao.add.*,com.yujian.dao.OrderDao.delete.*">property>
            <property name="advice" ref="aroundAdvice">property>
        bean>
    
        
        <bean id="orderDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
            
            <property name="target" ref="orderDao">property>
            
            <property name="proxyTargetClass" value="true">property>
            
            <property name="interceptorNames" value="myPointCutAdvisor">property>
        bean>
    
    beans>
    
  4. 修改 MainApp 类 main 方法的代码。

    package com.yujian;
    
    import com.yujian.dao.OrderDao;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class MainApp1 {
        public static void main(String[] args) {
            //获取 ApplicationContext 容器
            ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
            //获取代理对象
            OrderDao orderDao = context.getBean("orderDaoProxy", OrderDao.class);
            //调用 OrderDao 中的各个方法
            orderDao.add();
            orderDao.adds();
            orderDao.delete();
            orderDao.get();
            orderDao.modify();
        }
    }
    
  5. 执行 MainApp 中的 main() 方法,控制台输出如下。

    环绕增强前********
    正在执行 UserDao 的 add() 方法……
    环绕增强后********
    环绕增强前********
    正在执行 UserDao 的 adds() 方法……
    环绕增强后********
    环绕增强前********
    正在执行 UserDao 的 delete() 方法……
    环绕增强后********
    正在执行 UserDao 的 get() 方法……
    正在执行 UserDao 的 modify() 方法……
    

12.5.5 自动代理

在前面的案例中,所有目标对象(Target Bean)的代理对象(Proxy Bean)都是在 XML 配置中通过 ProxyFactoryBean 创建的。但在实际开发中,一个项目中往往包含非常多的 Bean, 如果每个 Bean 都通过 ProxyFactoryBean 创建,那么开发和维护成本会十分巨大。为了解决这个问题,Spring 为我们提供了自动代理机制。

Spring 提供的自动代理方案,都是基于后处理 Bean 实现的,即在 Bean 创建的过程中完成增强,并将目标对象替换为自动生成的代理对象。通过 Spring 的自动代理,我们在程序中直接拿到的 Bean 就已经是 Spring 自动生成的代理对象了。

Spring 为我们提供了 3 种自动代理方案:

  • BeanNameAutoProxyCreator:根据 Bean 名称创建代理对象。
  • DefaultAdvisorAutoProxyCreator:根据 Advisor 本身包含信息创建代理对象。
  • AnnotationAwareAspectJAutoProxyCreator:基于 Bean 中的 AspectJ 注解进行自动代理对象。

本节我们就通过两个简单的实例,对 BeanNameAutoProxyCreator 和 DefaultAdvisorAutoProxyCreator 进行演示,至于 AnnotationAwareAspectJAutoProxyCreator,我们会在后续的教程中进行讲解。

根据 Bean 名称创建代理对象
  1. 在 resources 目录下,创建一个名为 Beans2.xml 的配置文件,配置内容如下。

    
    <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-3.0.xsd
        http://www.springframework.org/schema/context
                http://www.springframework.org/schema/context/spring-context.xsd">
    
        
        <bean id="userDao" class="com.yujian.dao.impl.UserDaoImpl">bean>
        <bean id="orderDao" class="com.yujian.dao.OrderDao">bean>
    
        
        <bean id="beforeAdvice" class="com.yujian.advice.UserDaoBeforeAdvice">bean>
        <bean id="aroundAdvice" class="com.yujian.advice.OrderDaoAroundAdvice">bean>
    
        
        <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
            <property name="beanNames" value="*Dao">property>
            <property name="interceptorNames" value="beforeAdvice,aroundAdvice">property>
        bean>
    
    beans>
    
  2. 修改 MainApp 中 main 方法,代码如下。

    package com.yujian;
    
    import com.yujian.dao.OrderDao;
    import com.yujian.dao.UserDao;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class MainApp2 {
        public static void main(String[] args) {
            //获取 ApplicationContext 容器
            ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
            //获取代理对象
            UserDao userDao = context.getBean("userDao", UserDao.class);
            //获取代理对象
            OrderDao orderDao = context.getBean("orderDao", OrderDao.class);
            //调用 UserDao 中的各个方法
            userDao.add();
            userDao.delete();
            userDao.modify();
            userDao.get();
            //调用 OrderDao 中的各个方法
            orderDao.add();
            orderDao.adds();
            orderDao.delete();
            orderDao.get();
            orderDao.modify();
        }
    }
    
  3. 执行 MainApp 中的 main() 方法,控制台输出如下。

    正在执行前置增强操作…………
    环绕增强前********
    正在执行 UserDao 的 add() 方法……
    环绕增强后********
    正在执行前置增强操作…………
    环绕增强前********
    正在执行 UserDao 的 delete() 方法……
    环绕增强后********
    正在执行前置增强操作…………
    环绕增强前********
    正在执行 UserDao 的 modify() 方法……
    环绕增强后********
    正在执行前置增强操作…………
    环绕增强前********
    正在执行 UserDao 的 get() 方法……
    环绕增强后********
    正在执行前置增强操作…………
    环绕增强前********
    正在执行 UserDao 的 add() 方法……
    环绕增强后********
    正在执行前置增强操作…………
    环绕增强前********
    正在执行 UserDao 的 adds() 方法……
    环绕增强后********
    正在执行前置增强操作…………
    环绕增强前********
    正在执行 UserDao 的 delete() 方法……
    环绕增强后********
    正在执行前置增强操作…………
    环绕增强前********
    正在执行 UserDao 的 get() 方法……
    环绕增强后********
    正在执行前置增强操作…………
    环绕增强前********
    正在执行 UserDao 的 modify() 方法……
    环绕增强后********
    
根据切面中信息创建代理对象
  1. 修改 Beans2.xml 中的配置,配置内容如下。

    
    <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-3.0.xsd
        http://www.springframework.org/schema/context
                http://www.springframework.org/schema/context/spring-context.xsd">
    
        
        <bean id="userDao" class="net.biancheng.c.dao.impl.UserDaoImpl">bean>
        <bean id="orderDao" class="net.biancheng.c.dao.OrderDao">bean>
    
        
        <bean id="beforeAdvice" class="net.biancheng.c.advice.UserDaoBeforeAdvice">bean>
        <bean id="aroundAdvice" class="net.biancheng.c.advice.OrderDaoAroundAdvice">bean>
    
        
        <bean id="myPointCutAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
            
            
            <property name="patterns"
                      value="net.biancheng.c.dao.OrderDao.add.*,net.biancheng.c.dao.OrderDao.delete.*">property>
            <property name="advice" ref="aroundAdvice">property>
        bean>
    
        
        <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">bean>
    
    beans>
    
  2. 执行 MainApp 中的 main() 方法,控制台输出如下。

    正在执行 UserDao 的 add() 方法……
    正在执行 UserDao 的 delete() 方法……
    正在执行 UserDao 的 modify() 方法……
    正在执行 UserDao 的 get() 方法……
    环绕增强前********
    正在执行 OrderDao 的 add() 方法……
    环绕增强后********
    环绕增强前********
    正在执行 OrderDao 的 adds() 方法……
    环绕增强后********
    环绕增强前********
    正在执行 OrderDao 的 delete() 方法……
    环绕增强后********
    正在执行 OrderDao 的 get() 方法……
    正在执行 OrderDao 的 modify() 方法……
    

12.6、AOP 进阶操作

重用切入点定义

在编写AspectJ切面时,可以直接在通知注解中书写切入点表达式。但同一个切点表达式可能会在多个通知中重复出现。

在AspectJ切面中,可以通过@Pointcut注解将一个切入点声明成简单的方法。切入点的方法体通常是空的。

切入点方法的访问控制符同时也控制着这个切入点的可见性。如果切入点要在多个切面中共用,最好将它们集中在一个公共的类中。在这种情况下,它们必须被声明为public。在引入这个切入点时,必须将类名也包括在内。如果类没有与这个切面放在同一个包中,还必须包含包名。

其他通知可以通过方法名称引入该切入点

指定切面的优先级

在同一个连接点上应用不止一个切面时,除非明确指定,否则它们的优先级是不确定的。

切面的优先级可以通过实现Ordered接口或利用@Order注解指定。

实现Ordered接口,getOrder()方法的返回值越小,优先级越高。

若使用@Order注解,序号出现在注解中

@Component
@Aspect
@Order(0)
public class CalculatorValidationAspect {
    
@Component
@Aspect
@Order(1)
public class CalculatorLoggingAspect {

13、 Spring集成AspectJ

我们知道,Spring AOP 是一个简化版的 AOP 实现,并没有提供完整版的 AOP 功能。通常情况下,Spring AOP 是能够满足我们日常开发过程中的大多数场景的,但在某些情况下,我们可能需要使用 Spring AOP 范围外的某些 AOP 功能。

例如 Spring AOP 仅支持执行公共(public)非静态方法的调用作为连接点,如果我们需要向受保护的(protected)或私有的(private)的方法进行增强,此时就需要使用功能更加全面的 AOP 框架来实现,其中使用最多的就是 AspectJ。

AspectJ 是一个基于 Java 语言的全功能的 AOP 框架,它并不是 Spring 组成部分,是一款独立的 AOP 框架。

但由于 AspectJ 支持通过 Spring 配置 AspectJ 切面,因此它是 Spring AOP 的完美补充,通常情况下,我们都是将 AspectJ 和 Spirng 框架一起使用,简化 AOP 操作。

使用 AspectJ 需要在 Spring 项目中导入 Spring AOP 和 AspectJ 相关 Jar 包。

  • spring-aop-xxx.jar
  • spring-aspects-xxx.jar
  • aspectjweaver-xxxx.jar

在以上 3 个 Jar 包中,spring-aop-xxx.jar 和 spring-aspects-xxx.jar 为 Spring 框架提供的 Jar 包,而 aspectjweaver-xxxx.jar 则是 AspectJ 提供的。

1. AspectJ Jar 包下载

AspectJ Jar 包的下载步骤如下。

  1. 使用浏览器访问 AspectJ 包下载页面,选择相应的版本,这里我们以 1.9.5 稳定版本为例进行介绍。

    Spring详解(三)_第5张图片

    ​ 图1:AspectJ 下载

    1. 点击 aspectj-1.9.5.jar 进入下载页面,选择 Select another mirror,如下图。

      Spring详解(三)_第6张图片

      ​ 图2:AspectJ Jar 包下载

    2. 根据自己所处地区选择下载,这里我们选择的是中国科学技术大学的下载地址。

      Spring详解(三)_第7张图片

    3. 下载完成后解压该 Jar 文件,需要导入的 jar 包在 files 文件夹的 lib 目录下。

      Spring详解(三)_第8张图片

2. spring——基于XML的AspectJ AOP开发

我们可以在 Spring 项目中通过 XML 配置,对切面(Aspect 或 Advisor)、切点(PointCut)以及通知(Advice)进行定义和管理,以实现基于 AspectJ 的 AOP 开发。

Spring 提供了基于 XML 的 AOP 支持,并提供了一个名为“aop”的命名空间,该命名空间提供了一个 aop:config 元素。

  • 在 Spring 配置中,所有的切面信息(切面、切点、通知)都必须定义在 aop:config 元素中;
  • 在 Spring 配置中,可以使用多个 aop:config。
  • 每一个 aop:config 元素内可以包含 3 个子元素: pointcut、advisor 和 aspect ,这些子元素必须按照这个顺序进行声明。

2.1 引入 aop 命名空间

首先,我们需要在 XML 配置文件中导入 Spring 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/beans/spring-aop.xsd">

beans>

2.2 定义切面

在Spring配置文件中,使用元素定义切面,该元素可以将定义好的Bean转换为切面Bean,所以使用之前需要定义一个普通的Spring Bean

<aop:config>
    <aop:aspect id="myAspect" ref="webApplicationContext">

    aop:aspect>
aop:config>

其中,id用来定义该切面的唯一标识符,ref用于引用普通的Spring Bean

2.3 定义切入点

用来定义一个切入点,用来表示对哪个类中的哪个方法进行增强,它即可以在 元素中使用,也可以在元素下使用

  • 元素作为元素的子元素定义时,表示该切入点时全局切入点,它可以被多个切入点所共享
  • 元素作为元素的子元素时,表示该切入点只对当前切面有效
<aop:config>
    <aop:pointcut id="myPointCut" expression="execution(* com.yujian.dao.*.*(..))"/>
aop:config>

其中,id用于指定切入点的唯一标识名称,execution用于指定切入点关联的切入点表达式

execution的语法格式为:

execution([权限修饰符] [返回值类型] [类的完全限定名] [方法名称]([参数列表]))

其中:

  • 返回值类型,方法名,参数列表是必须配置的选项,而其他参数则为可选配置项.
  • 返回值类型: *表示可以为任何返回值.如果返回值为对象,则需指定全路径的类名
  • 类的完全限定名 : 指定包名 + 类名
  • 方法名 : * 表示所有方法, set* 代表set开头的所有方法
  • 参数列表 : (…)代表所有参数;(*)代表只有一个参数,参数类型为任意参数; (* String)代表有两个参数,第一个参数可以为任意值,第二个为String类型的值.

举例一 : 对com.yujian包下的 UserDao类中的 add()方法进行增强,配置如下.

execution(* com.yujian.UserDao.add(…))

举例二 : 对com.yujian包下的 UserDao类中的所有方法进行增强,配置如下.

execution(* com.yujian.UserDao.*(…))

举例三 : 对com.yujian包下的 所有类中的所有方法进行增强,配置如下.

execution(* com.yujian.*.*(…))

2.4 定义通知

Aspect 支持5种类型的advice,如下:

<aop:config>
    <aop:aspect id="myAsepct" ref="webApplicationContext">
        
        <aop:before pointcut-ref="myPointCut" method="..."/>

        
        <aop:after-returning pointcut-ref="myPointCut" method="..."/>

        
        <aop:around pointcut-ref="myPointCut" method="..."/>

        
        <aop:after-throwing pointcut-ref="myPointCut" method="..."/>

        
        <aop:after pointcut-ref="myPointCut" method="..."/>
        ...
    aop:aspect>
aop:config>

示例

下面我们通过一个示例来演示下 Spring 集成 AspectJ 基于 XML 实现 AOP 开发。

  1. 新建一个名为 my-spring-asepctj-demo 的 Java 项目,并将以下依赖 Jar 包导入到该项目中。

    <dependencies>
        <dependency>
          <groupId>junitgroupId>
          <artifactId>junitartifactId>
          <version>4.11version>
          <scope>testscope>
        dependency>
    
        <dependency>
          <groupId>org.springframeworkgroupId>
          <artifactId>spring-aopartifactId>
          <version>5.3.7version>
        dependency>
    
        <dependency>
          <groupId>org.springframeworkgroupId>
          <artifactId>spring-webmvcartifactId>
          <version>5.3.7version>
        dependency>
    
        <dependency>
          <groupId>org.aspectjgroupId>
          <artifactId>aspectjweaverartifactId>
          <version>1.9.7version>
        dependency>
    
      dependencies>
    
  2. 在 com.yujian.dao 包下,创建一个名为 OrderDao 的接口,代码如下。

    package com.yujian.dao;
    
    public interface OrderDao {
        public void add();
    
        public void delete();
    
        public int modify();
    
        public void get();
    }
    
  3. 在 com.yujian.dao.impl 包下,创建 OrderDao 的实现类 OrderDaoImpl,代码如下。

    package com.yujian.dao.impl;
    
    import com.yujian.dao.OrderDao;
    
    public class OrderDaoImpl implements OrderDao {
        @Override
        public void add() {
            System.out.println("正在执行 OrderDao 中的 add() 方法");
        }
    
        @Override
        public void delete() {
            System.out.println("正在执行 OrderDao 中的 delete() 方法");
        }
    
        @Override
        public int modify() {
            System.out.println("正在执行 OrderDao 中的 modify() 方法");
            return 1;
        }
    
        @Override
        public void get() {
            //异常
            int a = 10 / 0;
            System.out.println("正在执行 OrderDao 中的 get() 方法");
        }
    }
    
  4. 在 com.yujian. 包下,创建一个名为 MyOrderAspect 的类,代码如下。

    package com.yujian;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    
    public class MyOrderAspect {
    
        public void before() {
            System.out.println("前置增强……");
        }
    
        public void after() {
            System.out.println("最终增强……");
        }
    
        public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            System.out.println("环绕增强---前……");
            proceedingJoinPoint.proceed();
            System.out.println("环绕增强---后……");
        }
    
        public void afterThrow(Throwable exception) {
            System.out.println("异常增强…… 异常信息为:" + exception.getMessage());
        }
    
        public void afterReturning(Object returnValue) {
            System.out.println("后置返回增强…… 方法返回值为:" + returnValue);
        }
    }
    
  5. 在 resources 目录下创建一个 Spring 配置文件 Bean.xml,配置内容如下。

    
    <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"
           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/context
                               http://www.springframework.org/schema/context/spring-context.xsd
                               http://www.springframework.org/schema/aop
                               http://www.springframework.org/schema/aop/spring-aop.xsd">
        
        <bean id="orderDao" class="com.yujian.dao.impl.OrderDaoImpl">bean>
        
        <bean id="myOrderAspect" class="com.yujian.MyOrderAspect">bean>
    
        <aop:config>
            <aop:pointcut id="beforePointCut" expression="execution(* com.yujian.dao.OrderDao.add(..))"/>
    
            <aop:pointcut id="throwPointCut" expression="execution(* com.yujian.dao.OrderDao.get(..))"/>
    
            <aop:pointcut id="afterReturnPointCut" expression="execution(* com.yujian.dao.OrderDao.modify(..))"/>
    
            <aop:pointcut id="afterPointCut" expression="execution(* com.yujian.dao.OrderDao.*(..))"/>
    
            <aop:aspect ref="myOrderAspect">
                
                <aop:before method="before" pointcut-ref="beforePointCut">aop:before>
                
                <aop:after-returning method="afterReturning" pointcut-ref="afterReturnPointCut"
                                     returning="returnValue">aop:after-returning>
                
                <aop:after-throwing method="afterThrow" pointcut-ref="throwPointCut"
                                    throwing="exception">aop:after-throwing>
                
                <aop:after method="after" pointcut-ref="afterPointCut">aop:after>
                
                <aop:around method="around" pointcut-ref="beforePointCut">aop:around>
            aop:aspect>
        aop:config>
    
    beans>
    
  6. 在com.yujian包下,创建一个名为MainApp的类,代码如下:

    package com.yujian;
    
    import com.yujian.dao.OrderDao;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class MainApp {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
            OrderDao orderDao = context.getBean("orderDao", OrderDao.class);
            orderDao.add();
            orderDao.delete();
            orderDao.modify();
            orderDao.get();
        }
    }
    
  7. 执行MainApp中的main方法,控制台输出如下.

    前置增强……
    环绕增强—前……
    正在执行 OrderDao 中的 add() 方法
    环绕增强—后……
    最终增强……
    正在执行 OrderDao 中的 delete() 方法
    最终增强……
    正在执行 OrderDao 中的 modify() 方法
    最终增强……
    后置返回增强…… 方法返回值为:1
    最终增强……
    异常增强…… 异常信息为:/ by zero

    其中,id用于指定切入点的唯一标识名称,execution用于指定切入点关联的切入点表达式

3. spring——基于注解的AspectJ AOP开发

在 Spring 中,虽然我们可以使用 XML 配置文件可以实现 AOP 开发,但如果所有的配置都集中在 XML 配置文件中,就势必会造成 XML 配置文件过于臃肿,从而给维护和升级带来一定困难。

为此,AspectJ 框架为 AOP 开发提供了一套 @AspectJ 注解。它允许我们直接在 Java 类中通过注解的方式对切面(Aspect)、切入点(Pointcut)和增强(Advice)进行定义,Spring 框架可以根据这些注解生成 AOP 代理。

关于注解的介绍如表 1 所示。

​ 表 1 Annotation 注解介绍

名称 说明
@Aspect 用于定义一个切面。
@Pointcut 用于定义一个切入点。
@Before 用于定义前置通知,相当于 BeforeAdvice。
@AfterReturning 用于定义后置通知,相当于 AfterReturningAdvice。
@Around 用于定义环绕通知,相当于 MethodInterceptor。
@AfterThrowing 用于定义抛出通知,相当于 ThrowAdvice。
@After 用于定义最终通知,不管是否异常,该通知都会执行。
@DeclareParents 用于定义引介通知,相当于 IntroductionInterceptor(不要求掌握)。

3.1. 启用 @AspectJ 注解支持

在使用 @AspectJ 注解进行 AOP 开发前,首先我们要先启用 @AspectJ 注解支持。
我们可以通过以下 2 种方式来启用 @AspectJ 注解。

1)使用 Java 配置类启用

我们可以在 Java 配置类(标注了 @Configuration 注解的类)中,使用 @EnableAspectJAutoProxy 和 @ComponentScan 注解启用 @AspectJ 注解支持。

package com.yujian.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan(basePackages = "com.yujian") //注解扫描
@EnableAspectJAutoProxy //开启AspectJ 的自动代理
public class AppConfig {
}
2)基于XML配置启用

在Spring的 XML 配置文件,添加以下内容启用 @AspectJ 注解支持


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/c"
       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
       https://www.springframework.org/schema/aop/spring-aop.xsd">

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

    
    <aop:aspectj-autoproxy>aop:aspectj-autoproxy>
beans>

3.2 定义切面 @Aspect

我们可以通过 @Aspect 注解将一个 Bean 定义为切面

在启用 @AspectJ 注解支持情况下,Spring会自动将IoC容器(ApplicationContext) 中的所有使用了 @Aspect 注解的Bean识别为一个切面

我们可以在XML 配置通过一些配置将这个类定义为一个Bea,如下:

<bean id="myAspect" class="com.yujian.MyAspect">
	...
bean>

在定义完Bean后,我们只需要在Bean对应的java类中使用一个@Aspect注解,将这个Bean定义为一个切面,代码如下.

package com.yujian;

import org.aspectj.lang.annotation.Aspect;

@Aspect //定义为切面
public class MyAspect {
    
}

全注解方式定义切面

我们可以在Java类上使用下面2个注解,使用全注解方式定义切面

package com.yujian;

import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Component //定义成Bean
@Aspect //定义为切面
public class MyAspect {
    
}

在上述代码中共使用两个注解:

  • @Component 注解 : 将这个类的对象定义为一个Bean
  • @Aspect 注解 : 则是将这个Bean定义为一个切面

3.2 定义切点 @PointCut

在Aspectl中,我们可以使用@Pointcut 注解用来定义一个切点。需要注意的是,定义为切点的方法,它的返回值类型必须为void,示例代码如下。

//要求:方法必须是private,返回值类型为void,名称自定义,没有参数
@Pointcut("execution(* com.yujian..*.*(..))")
private void myPointCut(){

}

@Pointcut 注解中有一个value属性,这个属性的值就是切入点表达式。有关切入点表达式的具体介绍请参考《使用AspectJ 实现AOP(基于XML)》中的execution 语法格式介绍。

值得注意的是,我除了可以通过切入点表达式(execution)直接对切点进行定义外,还可以通过切入点方法的名称来引用其他的切入点。在使用方法名引用其他切入点时,还可以使用“&&”、"||"和“!”等表示“与”、“或”、“非”的含义,示例代码如下。

package com.yujian;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component //定义成Bean
@Aspect //定义为切面
public class MyAspect {

    //要求:方法必须是private,返回值类型为void,名称自定义,没有参数
    @Pointcut("execution(* com.yujian..*.*(..))")
    private void myPointCut(){

    }

    /**
     * 将com.yujian.dao包下OrderDao类中的get()方法定义为一个切点
     */
    @Pointcut(value = "execution(* com.yujian.dao.OrderDao.get(..))")
    public void pointCut1(){
    }

    /**
     * 将com.yujian.dao包下OrderDao类中的delete()方法定义为一个切点
     */
    @Pointcut(value = "execution(* com.yujian.dao.OrderDao.delete(..))")
    public void pointCut2(){
    }

    /**
     * 除了com.yujian.dao包下UserDao类中get()方法和delete()方法外,其他方法都定义为切点
     *
     * !表示非,即不是的含义,求补集
     * * &&表示与,即”并且“,求交集
     * ||表示或,即“或者”,求并集
     */
    @Pointcut(value = "!pointCut1() && pointCut2()")
    public void pointCut3(){

    }
}

3.3 定义通知

AspectJ 为我们提供了以下 6 个注解,来定义 6 种不同类型的通知(Advice),如下表。

注解 说明
@Before 用于定义前置通知,相当于 BeforeAdvice。
@AfterReturning 用于定义后置通知,相当于 AfterReturningAdvice。
@Around 用于定义环绕通知,相当于 MethodInterceptor。
@AfterThrowing 用于定义抛出通知,相当于 ThrowAdvice。
@After 用于定义最终通知,不管是否异常,该通知都会执行。
@DeclareParents 用于定义引介通知,相当于 IntroductionInterceptor(不要求掌握)。

以上这些通知注解中都有一个 value 属性,这个 value 属性的取值就是这些通知(Advice)作用的切点(PointCut),它既可以是切入点表达式,也可以是切入点的引用(切入点对应的方法名称),示例代码如下。

package com.yujian;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component //定义成Bean
@Aspect //定义为切面
public class MyAspect {

    //要求:方法必须是private,返回值类型为void,名称自定义,没有参数
    @Pointcut("execution(* com.yujian..*.*(..))")
    private void myPointCut(){

    }

    /**
     * 将com.yujian.dao包下OrderDao类中的get()方法定义为一个切点
     */
    @Pointcut(value = "execution(* com.yujian.dao.OrderDao.get(..))")
    public void pointCut1(){
    }

    /**
     * 将com.yujian.dao包下OrderDao类中的delete()方法定义为一个切点
     */
    @Pointcut(value = "execution(* com.yujian.dao.OrderDao.delete(..))")
    public void pointCut2(){
    }

    /**
     * 除了com.yujian.dao包下UserDao类中get()方法和delete()方法外,其他方法都定义为切点
     *
     * !表示非,即不是的含义,求补集
     * * &&表示与,即”并且“,求交集
     * ||表示或,即“或者”,求并集
     */
    @Pointcut(value = "!pointCut1() && pointCut2()")
    public void pointCut3(){

    }

    //使用切入点引用
    @Before("MyAspect.pointCut3()")
    public void around() throws Throwable{
        System.out.println("环绕增强....");
    }

    //使用切入点表达式
    @AfterReturning(value = "execution(* com.yujian.dao.OrderDao.get(..))",returning = "returnValue")
    public void afterReturning(Object returnValue){
        System.out.println("方法返回值为:"+returnValue);
    }
}

3.4 示例

下面,我们就通过一个完整的实例,来演示下如何通过注解的方式实现 AspectJ AOP 开发。

  1. 新建一个名为 my-spring-asepctj-demo2 的 Java 项目,并将以下依赖 Jar 包导入到该项目中。

    <dependency>
        <groupId>junitgroupId>
        <artifactId>junitartifactId>
        <version>4.11version>
        <scope>testscope>
    dependency>
    
    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-aopartifactId>
        <version>5.3.7version>
    dependency>
    
    <dependency>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-webmvcartifactId>
        <version>5.3.7version>
    dependency>
    
    <dependency>
        <groupId>org.aspectjgroupId>
        <artifactId>aspectjweaverartifactId>
        <version>1.9.7version>
    dependency>
    
  2. 在 net.biancheng.c.dao 包下,创建一个名为 UserDao 的接口,代码如下。

    package net.biancheng.c.dao;
    
    public interface UserDao {
        public void add();
    
        public void delete();
    
        public int modify();
    
        public void get();
    }
    
  3. 在 net.biancheng.c.dao.impl 包下,创建UserDao的实现类UserDaoImpl,代码如下。

    package net.biancheng.c.dao.impl;
    
    import net.biancheng.c.dao.UserDao;
    import org.springframework.stereotype.Component;
    
    @Component("userDao")
    public class UserDaoImpl implements UserDao {
    
        @Override
        public void add() {
            System.out.println("正在执行 UserDao 的add方法");
        }
    
        @Override
        public void delete() {
            System.out.println("正在执行 UserDao 的delete方法");
        }
    
        @Override
        public int modify() {
            System.out.println("正在执行 UserDao 的modify方法");
            return 1;
        }
    
        @Override
        public void get() {
            System.out.println("正在执行 UserDao 的get方法");
        }
    }
    
  4. 在 net.biancheng.c.config包下,创建一个名为AppConfig的配置类,代码如下.

    package net.biancheng.c.config;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    @Configuration
    @ComponentScan(basePackages = "net.biancheng.c") //注解扫描
    @EnableAspectJAutoProxy //开启 AspectJ 的自动代理
    public class AppConfig {
    }
    
  5. 在 net.biancheng.c 包下,创建一个名为 MyAspect 的切面类,代码如下。

    package net.biancheng.c;
     
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
     
    @Component // 定义成 Bean
    @Aspect //定义为切面
    public class MyAspect {
        @Before("execution(* net.biancheng.c.dao.UserDao.add(..))")
        public void before(JoinPoint joinPoint) {
            System.out.println("前置增强……" + joinPoint);
        }
     
        @After("execution(* net.biancheng.c.dao.UserDao.get(..))")
        public void after(JoinPoint joinPoint) {
            System.out.println("最终增强……" + joinPoint);
        }
     
     
        /**
         * 将 net.biancheng.c.dao包下的 UserDao 类中的 get() 方法 定义为一个切点
         */
        @Pointcut(value = "execution(* net.biancheng.c.dao.UserDao.get(..))")
        public void pointCut1() {
     
        }
     
        /**
         * 将 net.biancheng.c.dao包下的 UserDao 类中的 delete() 方法 定义为一个切点
         */
        @Pointcut(value = "execution(* net.biancheng.c.dao.UserDao.delete(..))")
        public void pointCut2() {
     
        }
     
        //使用切入点引用
        @Around("MyAspect.pointCut2()")
        public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            System.out.println("环绕增强……1");
            proceedingJoinPoint.proceed();
            System.out.println("环绕增强……2");
        }
     
        //使用切入点表达式
        @AfterReturning(value = "execution(* net.biancheng.c.dao.UserDao.modify(..))", returning = "returnValue")
        public void afterReturning(Object returnValue) {
            System.out.println("后置返回增强……,方法返回值为:" + returnValue);
        }
    }
    
  6. 在 net.biancheng.c 包下,创建一个MainApp的类,代码如下:

    package net.biancheng.c;
    
    import net.biancheng.c.config.AppConfig;
    import net.biancheng.c.dao.UserDao;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    public class MainApp {
        public static void main(String[] args) {
            ApplicationContext content = new AnnotationConfigApplicationContext(AppConfig.class);
            UserDao userDao = content.getBean("userDao", UserDao.class);
            userDao.add();
            userDao.delete();
            userDao.modify();
            userDao.get();
        }
    }
    
  7. 执行MainApp中的main()方法,控制台输出如下:

    前置增强……execution(void net.biancheng.c.dao.UserDao.add())
    正在执行 UserDao 的add方法
    环绕增强……1
    正在执行 UserDao 的delete方法
    环绕增强……2
    正在执行 UserDao 的modify方法
    后置返回增强……,方法返回值为:1
    正在执行 UserDao 的get方法
    最终增强……execution(void net.biancheng.c.dao.UserDao.get())
    

14、Spring JdbcTemplate

我们知道,JDBC 是 Java 提供的一种用于执行 SQL 语句的 API,可以对多种关系型数据库(例如 MySQL、Oracle 等)进行访问。

但在实际的企业级应用开发中,却很少有人直接使用原生的 JDBC API 进行开发,这是因为使用 JDBC API 对数据库进行操作十分繁琐,需要我们对每一步都做到“步步把控,处处关心”,例如我们需要手动控制数据库连接的开启,异常处理、事务处理、最后还要手动关闭连接释放资源等等。

Spring 提供了一个 Spring JDBC 模块,它对 JDBC API 进行了封装,其的主要目的降低 JDBC API 的使用难度,以一种更直接、更简洁的方式使用 JDBC API。

使用 Spring JDBC,开发人员只需要定义必要的参数、指定需要执行的 SQL 语句,即可轻松的进行 JDBC 编程,对数据库进行访问。

至于驱动的加载、数据库连接的开启与关闭、SQL 语句的创建与执行、异常处理以及事务处理等繁杂乏味的工作,则都是由 Spring JDBC 完成的。这样就可以使开发人员从繁琐的 JDBC API 中解脱出来,有更多的精力专注于业务的开发。

Spring JDBC 提供了多个实用的数据库访问工具,以简化 JDBC 的开发,其中使用最多就是 JdbcTemplate。

14.1 JdbcTemplate

JdbcTemplate 是 Spring JDBC 核心包(core)中的核心类,它可以通过配置文件、注解、Java 配置类等形式获取数据库的相关信息,实现了对 JDBC 开发过程中的驱动加载、连接的开启和关闭、SQL 语句的创建与执行、异常处理、事务处理、数据类型转换等操作的封装。我们只要对其传入SQL 语句和必要的参数即可轻松进行 JDBC 编程。

JdbcTemplate 的全限定命名为 org.springframework.jdbc.core.JdbcTemplate,它提供了大量的查询和更新数据库的方法,如下表所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-neaVNYLM-1677319308605)(photo/image-20221028102637200.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XzrqDEEq-1677319308605)(photo/image-20221028102653871.png)]

示例 1

下面我们就结合实例,对使用 JdbcTemplate 进行 JDBC 编程进行讲解,步骤如下。

  1. 在 MySQL 数据库中创建一个 spring_jdbc_db 数据库实例,并执行以下 SQL 语句创建一个用户信息(user)表。

    DROP TABLE IF EXISTS `user`;
    CREATE TABLE `user` (
      `user_id` int NOT NULL AUTO_INCREMENT COMMENT '用户 ID',
      `user_name` varchar(255) DEFAULT NULL COMMENT '用户名',
      `status` varchar(255) DEFAULT NULL COMMENT '用户状态',
      PRIMARY KEY (`user_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
    
  2. 创建一个名为 my-spring-jdbc-demo 的项目,并在将以下依赖导入到工程中。

    除了 Spring 的核心依赖以及 commons-logging 日志包外,我们还需要在项目中导入以下依赖。

    依赖 说明
    spring-jdbc-xxx.jar Spring JDBC 的核心依赖包
    spring-tx-xxx.jar 用来处理事务和异常的依赖包
    mysql-connector-java-xxx.jar MySQL 提供的 JDBC 驱动包
  3. 在 src 目录下创建一个 jdbc.properties,并在该配置文件中对数据库连接信息进行配置。

    jdbc.driver=com.mysql.cj.jdbc.Driver
    jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_jdbc_db
    jdbc.username=root
    jdbc.password=123456
    
  4. 在 src 目录下创建一个 XML 配置文件 Beans.xml,配置内容如下。

    
    <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-3.0.xsd
        http://www.springframework.org/schema/context
                http://www.springframework.org/schema/context/spring-context.xsd">
        
        <context:component-scan base-package="net.biancheng.c">context:component-scan>
        
        <context:property-placeholder location="classpath:jdbc.properties">context:property-placeholder>
    
        
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            
            <property name="url" value="${jdbc.url}"/>
            
            <property name="username" value="${jdbc.username}"/>
            
            <property name="password" value="${jdbc.password}"/>
            
            <property name="driverClassName" value="${jdbc.driver}"/>
        bean>
    
        
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            
            <property name="dataSource" ref="dataSource">property>
        bean>
       
    beans>
    

在以上配置中,我们共定义了两个 Bean,

  • dataSource 为数据库连接池对象的 Bean。
  • jdbcTemplate 则为 JdbcTemplate 的 Bean,它由一个名为 datasSource 的属性。

Spring 默认使用 DriverManagerDataSource 对数据库连接池进行管理,我们可以在 Spring 的 XML 配置文件中定义 DriverManagerDataSource 的 Bean,并注入到 JdbcTempate 的 Bean 中。

在 dataSource 中,定义了 4 个连接数据库的属性,如下表所示。

属性名 说明
driverClassName 所使用的驱动名称,对应驱动 JAR 包中的 Driver 类
url 数据源所在地址
username 访问数据库的用户名
password 访问数据库的密码

上表中的属性值需要根据数据库类型或者机器配置的不同进行相应设置。如果数据库类型不同,则需要更改驱动名称;如果数据库不在本地,则需要将 localhost 替换成相应的主机 IP。

  1. 在 net.biancheng.c.entity 包下,创建名为 User 的实体类,代码如下。

    package net.biancheng.c.entity;
    
    public class User {
        private Integer userId;
        private String userName;
        private String status;
    
        public Integer getUserId() {
            return userId;
        }
    
        public void setUserId(Integer userId) {
            this.userId = userId;
        }
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName) {
            this.userName = userName;
        }
    
        public String getStatus() {
            return status;
        }
    
        public void setStatus(String status) {
            this.status = status;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "userId=" + userId +
                    ", userName='" + userName + '\'' +
                    ", status='" + status + '\'' +
                    '}';
        }
    }
    
  2. 在 net.biancheng.c.dao 包下,创建一个名为 UserDao 的 Dao 接口,代码如下。

    package net.biancheng.c.dao;
    
    import net.biancheng.c.entity.User;
    
    import java.util.List;
    
    public interface UserDao {
        /**
         * 新增一条用户
         *
         * @param user
         * @return
         */
        int addUer(User user);
        /**
         * 更新指定的用户信息
         *
         * @param user
         * @return
         */
        int update(User user);
        /**
         * 删除指定的用户信息
         *
         * @param user
         * @return
         */
        int delete(User user);
        /**
         * 统计用户个数
         *
         * @param user
         * @return
         */
        int count(User user);
        /**
         * 查询用户列表
         *
         * @param user
         * @return
         */
        List<User> getList(User user);
        /**
         * 查询单个用户信息
         *
         * @param user
         * @return
         */
        User getUser(User user);
        /**
         * 批量增加用户
         *
         * @param batchArgs
         */
        void batchAddUser(List<Object[]> batchArgs);
    }
    
  3. 在 net.biancheng.c.dao.impl 包下,创建 UserDao 的实现类 UserDaoImpl,代码如下。

    package net.biancheng.c.dao.impl;
    
    import net.biancheng.c.dao.UserDao;
    import net.biancheng.c.entity.User;
    import org.springframework.jdbc.core.BeanPropertyRowMapper;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
    import org.springframework.stereotype.Repository;
    
    import javax.annotation.Resource;
    import java.util.List;
    
    @Repository("userDao")
    public class UserDaoImpl implements UserDao {
        @Resource
        private JdbcTemplate jdbcTemplate;
        //@Resource
        //private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
    
        @Override
        public int addUer(User user) {
            String sql = "INSERT into `user` (`user`.user_name,`user`.`status`) VALUES(?,?);";
            int update = jdbcTemplate.update(sql, user.getUserName(), user.getStatus());
            return update;
        }
        @Override
        public int update(User user) {
            String sql = "UPDATE `user` SET status=? WHERE user_name=?;";
            return jdbcTemplate.update(sql, user.getStatus(), user.getUserName());
        }
        @Override
        public int delete(User user) {
            String sql = "DELETE FROM `user` where user_name=?;";
            return jdbcTemplate.update(sql, user.getUserName());
        }
        @Override
        public int count(User user) {
            String sql = "SELECT COUNT(*) FROM `user` where `status`=?;";
            return jdbcTemplate.queryForObject(sql, Integer.class, user.getStatus());
        }
        @Override
        public List<User> getList(User user) {
            String sql = "SELECT * FROM `user` where `status`=?;";
            return jdbcTemplate.query(sql, new BeanPropertyRowMapper<User>(User.class), user.getStatus());
        }
        @Override
        public User getUser(User user) {
            String sql = "SELECT * FROM `user` where `user_id`=?;";
            return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), user.getUserId());
        }
        @Override
        public void batchAddUser(List<Object[]> batchArgs) {
            String sql = "INSERT into `user` (`user`.user_name,`user`.`status`) VALUES(?,?);";
            jdbcTemplate.batchUpdate(sql, batchArgs);
        }
    }
    
  4. 在 net.biancheng.c.service 包下,创建一个名为 UserService 的 Service 接口,代码如下。

    package net.biancheng.c.service;
    
    import net.biancheng.c.entity.User;
    
    import java.util.List;
    
    public interface UserService {
        /**
         * 新增用户数据
         *
         * @param user
         * @return
         */
        public int addUser(User user);
        /**
         * 更新用户数据
         *
         * @param user
         * @return
         */
        public int updateUser(User user);
        /**
         * 删除用户数据
         *
         * @param user
         * @return
         */
        public int deleteUser(User user);
        /**
         * 统计用户数量
         *
         * @param user
         * @return
         */
        public int countUser(User user);
        /**
         * 查询用户数据
         *
         * @param user
         * @return
         */
        public List<User> getUserList(User user);
        /**
         * 查询单个用户信息
         *
         * @param user
         * @return
         */
        public User getUser(User user);
        /**
         * 批量添加用户
         *
         * @param batchArgs
         */
        public void batchAddUser(List<Object[]> batchArgs);
    }
    
  5. 在 net.biancheng.c.service.impl 包下,创建 UserService 的实现类 UserServiceImpl,代码如下。

    package net.biancheng.c.service.impl;
    
    import net.biancheng.c.dao.UserDao;
    import net.biancheng.c.entity.User;
    import net.biancheng.c.service.UserService;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    import java.util.List;
    
    @Service("userService")
    public class UserServiceImpl implements UserService {
        @Resource
        private UserDao userDao;
        @Override
        public int addUser(User user) {
            return userDao.addUer(user);
        }
        @Override
        public int updateUser(User user) {
            return userDao.update(user);
        }
        @Override
        public int deleteUser(User user) {
            return userDao.delete(user);
        }
        @Override
        public int countUser(User user) {
            return userDao.count(user);
        }
        @Override
        public List<User> getUserList(User user) {
            return userDao.getList(user);
        }
        @Override
        public User getUser(User user) {
            return userDao.getUser(user);
        }
        @Override
        public void batchAddUser(List<Object[]> batchArgs) {
            userDao.batchAddUser(batchArgs);
        }
        //@Override
        //public int countOfUserByName(User user) {
        //    return userDao.countOfUserByName(user);
        //}
        //@Override
        //public User getUserByUserId(User user) {
        //    return userDao.getUserByUserId(user);
        //}
    }
    
  6. 在 net.biancheng.c 包下,创建一个名为 MainApp 的类,代码如下。

    package net.biancheng.c;
    
    import net.biancheng.c.entity.User;
    import net.biancheng.c.service.UserService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class MainApp {
        public static void main(String[] args) {
            ApplicationContext context2 = new ClassPathXmlApplicationContext("Beans.xml");
            UserService userService = context2.getBean("userService", UserService.class);
            User user = new User();
            user.setUserName("小张");
            user.setStatus("离线线");
            //新增一个用户
            int i = userService.addUser(user);
            System.out.println("新增用户成功!");
            User user1 = new User();
            user1.setUserName("小张");
            user1.setStatus("在线");
            int u = userService.updateUser(user1);
            System.out.println("修改用户成功");
            List<Object[]> batchArgs = new ArrayList<>();
            Object[] o1 = {"小明", "在线"};
            Object[] o2 = {"小龙", "离线"};
            Object[] o3 = {"小林", "在线"};
            Object[] o4 = {"小李", "在线"};
            batchArgs.add(o1);
            batchArgs.add(o2);
            batchArgs.add(o3);
            batchArgs.add(o4);
            userService.batchAddUser(batchArgs);
            System.out.println("批量增加完毕");
            User user2 = new User();
            user2.setStatus("在线");
            int i1 = userService.countUser(user2);
            System.out.println("在线用户的个数为:" + i1);
            List<User> userList = userService.getUserList(user2);
            System.out.println("在线用户列表查询成功!");
            for (User user4 : userList) {
                System.out.println("用户 ID:" + user4.getUserId() + ",用户名:" + user4.getUserName() + ",状态:" + user4.getStatus());
            }
        }
    }
    
  7. 执行 MainApp 中的 main 方法,控制台输出如下。

    新增用户成功!
    修改用户成功
    批量增加完毕
    在线用户的个数为:4
    在线用户列表查询成功!
    用户 ID:6,用户名:小张,状态:在线
    用户 ID:7,用户名:小明,状态:在线
    用户 ID:9,用户名:小林,状态:在线
    用户 ID:10,用户名:小李,状态:在线

13、 整合Mybaits

步骤:

  1. 导入相关的jar包

    • junit
    • mybatis
    • mysql数据库
    • spring相关
    • aop植入
    • mybatis-spring [ new ]
  2. 编写配置文件


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring_studyartifactId>
        <groupId>com.yujiangroupId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>spring_10_mybatisartifactId>

    <dependencies>
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>4.12version>
            <scope>testscope>
        dependency>

        <dependency>
            <groupId>org.mybatisgroupId>
            <artifactId>mybatisartifactId>
            <version>3.5.7version>
        dependency>

        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>8.0.26version>
        dependency>

        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-webartifactId>
            <version>5.3.12version>
        dependency>

        
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-jdbcartifactId>
            <version>5.3.12version>
        dependency>

        <dependency>
            <groupId>org.aspectjgroupId>
            <artifactId>aspectjweaverartifactId>
            <version>1.9.6version>
        dependency>

        
        <dependency>
            <groupId>org.mybatisgroupId>
            <artifactId>mybatis-springartifactId>
            <version>2.0.6version>
        dependency>

    dependencies>

project>
  1. 测试

13.1、 回忆Mybatis

  1. 编写实体类
  2. 编写核心配置文件
  3. 编写接口
  4. 编写Mapper.xml
  5. 测试

13.2、 Mybatis-Spring

  1. 编写数据源配置 class=“org.springframework.jdbc.datasource.DriverManagerDataSource”

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
bean>
  1. SqlSessionFactory class="org.mybatis.spring.SqlSessionFactoryBean

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource">property>
    
    <property name="configLocation" value="classpath:mybatis_config.xml"/>
    <property name="mapperLocations" value="classpath:com/yujian/mapper/*.xml"/>
bean>
  1. SqlSessionTemlate class="org.mybatis.spring.SqlSessionTemplate

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    
    <constructor-arg index="0" ref="sqlSessionFactory"/>
bean>
  1. 需要给接口加实现类
package com.yujian.mapper;

import com.yujian.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;

import java.util.List;

public class UserMapperImpl implements UserMapper{

    //在原来,我们的所有操作,都使用sqlSession来执行
    //现在,我们都使用SqlSessionTemplate
    private SqlSessionTemplate sqlSession;

    public void setSqlSession(SqlSessionTemplate sqlSession) {
        this.sqlSession = sqlSession;
    }

    public List<User> selectUser() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        return mapper.selectUser();
    }
}
  1. 将自己写的实现类,注入到Spring

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

    <import resource="spring-dao.xml"/>

    <bean id="userMapper" class="com.yujian.mapper.UserMapperImpl">
        <property name="sqlSession" ref="sqlSession"/>
    bean>
beans>
  1. 测试
import com.yujian.mapper.UserMapper;
import com.yujian.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.io.IOException;

public class MyTest {

    @Test
    public void selectUser() throws IOException {
         ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");

        UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
        for (User user : userMapper.selectUser()) {
            System.out.println(user);
        }
    }
}

14、 声明式事务

14.1、 事务回顾

  • 把一组业务当作一个业务来做,要么都成功,要么都失败
  • 事务在项目开发中,十分重要,涉及到数据的一致性问题,不能马虎!
  • 确保完整性和一致性!

事务的ACID原则:

  • 原子性
  • 一致性
  • 隔离性
    • 多个业务可能操作同一个资源,互相隔离,防止数据损坏
  • 持久性
    • 事务一旦提交,无论系统发生什么问题,结果都不会被影响,被持久化的写到存储器中

回顾一下:Mybatis-Spring

  1. 编写数据源配置

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
bean>
  1. SqlSessionFactory

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource">property>
    
    <property name="configLocation" value="classpath:mybatis_config.xml"/>
    <property name="mapperLocations" value="classpath:com/yujian/mapper/*.xml"/>
bean>
  1. SqlSessionTemplate

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    
    <constructor-arg index="0" ref="sqlSessionFactory"/>
bean>
  1. 将自己写的实现类,注入到Spring

DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    
    <typeAliases>
        <package name="com.yujian.pojo"/>
    typeAliases>

configuration>
  1. 编写mapper.xml

DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yujian.mapper.UserMapper">

    <select id="selectUser" parameterType="User" resultType="User">
        select * from user;
    select>

    <insert id="addUser" parameterType="User">
        insert into user (id,name,pwd) values (#{id},#{name},#{name});
    insert>

    <delete id="deleteUser" parameterType="int">
        deletes from user where id = #{id}
    delete>

mapper>
  1. 需要给接口加实现类
package com.yujian.mapper;

import com.yujian.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.support.SqlSessionDaoSupport;

import java.util.List;

public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{


    public List<User> selectUser() {

        User user = new User(7, "健儿", "123456");

        UserMapper mapper = getSqlSession().getMapper(UserMapper.class);

        mapper.addUser(user);
        mapper.deleteUser(7);

        return mapper.selectUser();
    }

    public int addUser(User user) {
        UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
        return mapper.addUser(user);
    }

    public int deleteUser(int id) {
        return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
    }
}

<bean id="userMapper" class="com.yujian.mapper.UserMapperImpl">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
bean>
  1. 测试
import com.yujian.mapper.UserMapper;
import com.yujian.pojo.User;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

public class MyTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationcontext.xml");
        UserMapper mapper = context.getBean("userMapper", UserMapper.class);
        List<User> users = mapper.selectUser();
        for (User user : users) {
            System.out.println(user);
        }
    }
}

14.2、 Spring中的事务管理

  • 声明式事务: AOP
  • 编程式事务: 需要在代码中,进行事务管理

思考

为什么需要事务?

  • 如果不配置事务,可能存在数据提交不一致的情况:
  • 如果我们不在Spring中配置声明式事务,我们就需要到代码中手动配置事务
    • 事务在项目开发中十分重要,涉及到数据的一致性和完整性问题,不能马虎。

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
bean>



<tx:advice id="txAdvice" transaction-manager="transactionManager">
    
    
    <tx:attributes>
        
        
        
        
        <tx:method name="*" propagation="REQUIRED"/>
    tx:attributes>
tx:advice>


<aop:config>
    <aop:pointcut id="txPointCut" expression="execution(* com.yujian.mapper.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
aop:config>

你可能感兴趣的:(SSM,spring,代理模式,java)