第3章?Spring AOP


??本章学习内容
* Spring AOP概述;
* 动态代理模式;
* AspectJ表达式;
* 使用XML配置实现AOP;
* 使用注解实现AOP。
  
3.1
Spring AOP概述
  
3.1.1?AOP的概念
  AOP是Spring框架中最核心的一个功能,首先通过一个生活中的案例来学习一下什么是AOP。比如银行系统会有一个取款流程,传统程序的流程如图3-1所示。

图3-1?取款流程
  假设系统还有一个查询并显示余额的流程,如图3-2所示。

图3-2?显示余额流程
  把这两个流程放到一起,会发现两者有一个相同的验证流程,如图3-3所示。

图3-3?验证流程
  在系统开发中,验证用户的功能是相同的,但又在不同的地方出现,可以把这部分代码提取出来写成一个共用的类,这个类就称为切面(Aspect)类。切面类中的方法(验证用户)称为通知(Advice),在程序执行时动态地添加到主业务类的方法(取款和显示余额)前后,这个主业务类的方法称为连接点(Joinpoint)。验证用户的切面类可以在不同的地方重用,这种编程方式叫作AOP(Aspect Oriented Programming),即面向切面编程。
  有了AOP,在编写代码时就不需要把验证用户的步骤写进去,可以完全不考虑验证用户的功能,只编写取款和显示余额的业务代码,而在另一个地方写好验证用户的代码。这个验证用户的代码就是切面代码,以后在执行取款和显示余额时,将验证用户的功能在执行取款和显示余额前调用。
  代码在Spring容器中执行时,通过配置告诉Spring这段代码要添加的地方,Spring就会在执行正常业务流程时把验证代码和取款代码织入在一起。
  AOP的真正目的是让程序员在编写代码时只需考虑主要业务流程,而不用考虑和业务无关却又必须要写的代码。
3.1.2?AOP中类与切面的关系
  AOP的本质是在一系列纵向的控制流程中,把那些相同的子流程(如验证用户)提取成一个横向的面,将分散在主流程中相同的代码提取出来,然后在程序编译或运行时,将这些提取出来的切面代码应用到需要执行的地方。
  例如取款、查询、转账前都要进行用户验证,则验证用户就可以做成切面类。在执行取款、查询、转账的操作时,由Spring容器将验证用户的代码织入在它们的前面,从而达到验证用户的目的。验证用户的代码只需要编写一次,可以让程序员将编程的精力放在取款、查询、转账等主要业务上。切面与主业务类的关系如图?3-4所示。
3.1.3?AOP的应用场景
  面向切面编程的主要场景有如下几种,切面可以分别在类1和类2方法中加入事务、日志、权限控制等功能,如图3-5所示。上面的验证用户就是权限控制的一种。

图3-5?AOP的应用场景
  比如日志记录功能,服务器端重要的操作步骤是需要用日志记录下来,便于以后服务器的管理和维护,所以系统中就会出现类似如下代码。
  
     logger.info("管理员登录");          	//日志记录
     userService.login();               	//业务操作
     logger.info("管理员删除用户");      	//日志记录
     userService.deleteUser();         	//业务操作
     logger.info("管理员退出");          	//日志记录
     userService.logout();              	//业务操作
  
  上面的业务代码和日志记录代码会分布在整个系统中,而且是零散的。
  几乎所有的重要操作方法前面都会加上日志记录代码,这样的代码写起来烦琐,不但占用了开发时间和精力,而且不容易维护。因此可以将日志记录的功能做成切面类,让程序在执行时再动态地将日志与主业务功能织入在一起。
  AOP的核心实现是动态代理模式,下面介绍一下Java中常用的JDK动态代理。
  
3.2
动态代理模式
  
  代理模式的作用是为其他对象提供一种代理以便控制该对象的访问。代理模式可以详细控制访问某个对象的方法,在调用这个方法前做一些前置处理,调用这个方法后也可以做后置处理。代理模式的实现分为静态代理和动态代理,在Spring中多使用动态代理。动态代理的实现可以分为JDK动态代理和CGLIB动态代理,下文重点介绍JDK动态代理。
3.2.1?代理模式对象
  JDK动态代理等代理模式所涉及的对象如图3-6所示,如租客、中介、房东等都是代理对象。

图3-6?代理模式示意图
  在代理模式中有以下4种角色。
  (1)真实角色:需要实现抽象角色的接口,定义了真实角色所要实现的业务逻辑,即真正的业务逻辑。
  (2)代理角色:相当于真实角色的一个代理角色,可以改写真实角色的方法或对真实角色的方法进行拦截,并可以附加自己的操作。
  (3)抽象角色:指代理角色和真实角色对外提供的公共方法,定义了真实角色的行为,一般为一个抽象类或接口。
  (4)调用者:使用真实角色的调用者,不属于代理模式中的一部分。 


  
3.2.2?JDK动态代理
  JDK动态代理必须借助于一个接口才能产生代理对象。因此,对于使用业务接口的类,Spring默认使用JDK动态代理实现AOP。
  1. 动态代理的特点
  (1)程序在执行过程中动态生成代理对象,不用手动编写代理对象。
  (2)不需要重写目标对象中所有同名的方法,只关注需要代理的方法即可。
  2. 动态代理类相应的API
  1)Proxy类
  
     static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 
  
  在JDK的API中存在一个Proxy类,其中有一个生成动态代理对象的方法newProxyInstance()。
  该类的参数说明如下。
  (1)loader:真实对象的类加载器。
  (2)interfaces:真实对象所有实现的接口数组。
  (3)h:具体的代理操作,InvocationHandler是一个接口,需要传入一个实现了此接口的实现类。
  (4)返回值:生成的代理对象。
  2)InvocationHandler接口
  
     Object invoke(Object proxy, Method method, Object[] args)
  
  在这个方法中实现对真实方法的增强或拦截,其参数说明如下。
  (1)proxy:即newProxyInstance()方法返回的代理对象,该对象一般不在invoke方法中使用,容易出现递归调用。
  (2)method:真实对象的方法对象,会进行多次调用,每次调用method对象都不同。
  (3)args:代理对象调用方法时传递的参数。
  (4)返回值:真实对象方法的返回值。
  下面通过一个案例演示如何使用JDK动态代理实现Spring AOP。
  假设有一个出租的接口,其代码如下。
  
     package com.ssmbook2020;
     /**
      * 出租的接口
      */
     public interface Lease {
         /**
          * 定义出租的行为
          * @param money 租金
          */
         void rentOut(int money);
     }
  
  真实角色是房东,其代码如下。
  
     /**
      * 房东:真实角色
      */
     public class Landlord implements Lease {
         @Override
         public void rentOut(int money){
             System.out.println("房东出租房子,收取租金:" + money);
         }
     }
    
  该房屋出租案例的开发流程如下。
  (1)直接创建真实对象,并调用真实对象的方法。
  (2)通过Proxy类创建代理对象,并调用代理对象的方法。
  (3)分别输出真实对象和代理对象的实现类。
  
     package com.ssmbook2020;
     import java.lang.reflect.InvocationHandler;
     import java.lang.reflect.Method;
     import java.lang.reflect.Proxy;
     /**
      * 租客:使用代理对象的调用者
      */
     public class Tenant {
         public static void main(String[] args){
             //(1)直接找房东租房(创建真实对象)
             Lease landlord = new Landlord();
             //调用真实对象的方法
             landlord.rentOut(2000);
             System.out.println("真实对象:" + landlord.getClass());
             //输出一条线分隔开
             System.out.println("==========");
             //(2)找中介租房 (创建代理对象)
             Lease agent = (Lease)
             Proxy.newProxyInstance(landlord.getClass(). getClassLoader();
      //真实对象的类加载器
                     landlord.getClass().getInterfaces();
     //获取真实对象所有实现的接口
                     new InvocationHandler() { //代理的实现
                         /**
                          * proxy:即newProxyInstance()方法返回的代理对象 
                          * method: 真实对象的方法对象,会被调用多次,每次调用method对象
                          * 都不同
                          * args:代理对象调用方法时传递的参数
                          */
                         @Override
             public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
                             //如果是出租的方法
                             if (method.getName().equals("rentOut")){
                                 //对真实方法进行代理,但不修改原来类的方法
     System.out.println("中介出租房子,收取中介费:200");
                             }
                             //调用真实对象的方法
                             return method.invoke(landlord, args);
                         }
                     });
             //调用代理对象的方法
             agent.rentOut(2000);
             //输出代理对象
             System.out.println("代理对象:" + agent.getClass());
         }
     
     } 
  
  在以上代码中,代理角色是程序在执行过程中动态生成的,在没有修改真实对象的前提下对真实对象进行了代理,并添加了新的功能。
  该案例的代码执行效果如下。
  
     房东出租房子,收取租金:2000
     真实对象:class com.ssmbook2020.Landlord
     ==========
     中介出租房子,收取中介费:200
     房东出租房子,收取租金:2000
     代理对象:class com.sun.proxy.$Proxy0
  
  可以看到,代理对象实现类的名字是com.sun.proxy.$Proxy0,在后面的Spring AOP中还会再用到。
  
3.3
AOP的实现
  
3.3.1?AOP的常用增强类型
  AOP 的实现原理其实就是使用代理模式,由 AOP 框架动态生成一个代理对象,该对象可作为代替目标对象使用。Spring 中的AOP代理由Spring的IoC容器负责生成、管理,因此AOP 代理可以直接使用容器中的其他Bean实例作为目标,这种关系可由 IoC 容器的依赖注入提供。
  常用的AOP代理增强主要包括前置增强、后置增强、环绕增强、异常增强等。
  (1)前置增强(Before Advice):在某连接点之前执行的增强,但这个增强不能阻止连接点之前的执行流程(除非它抛出一个异常)。
  (2)后置增强(After Returning Advice):在某连接点正常完成后执行的增强。例如,一个方法没有抛出任何异常,正常返回。
  (3)异常增强(After Throwing Advice):在方法抛出异常退出时执行的增强。
  (4)最终增强(After (Finally) Advice):当某连接点退出时执行的增强(无论是正常返回还是异常退出)。
  (5)环绕增强(Around Advice):包围一个连接点的增强,如方法调用。这是最强大的一种增强类型。环绕增强可以在方法调用前后完成自定义的行为,它会选择是否继续执行连接点、直接返回它自己的返回值或抛出异常来结束执行。
  在内部调用这5种增强类型的组织方式如下。
  
     try { 
     调用前置增强 
     环绕前置处理 
     调用目标对象方法 
     环绕后置处理 
     调用后置增强 
     } catch(Exception e) { 
     调用异常增强 
     } finally { 
     调用最终增强 
     }
  
3.3.2?AspectJ表达式
  AspectJ表达式又称为切入点表达式,它其实是一组规则,指定哪些包下的哪些类和方法织入通知代码。
  1. 切点函数
  常用的切点函数有以下3个,如表3-1所示。
表3-1  切点函数
切点函数
作??用
execution
细粒度函数,可以精确到类中的某个方法
within
粗粒度函数,只能精确到类
bean
粗粒度函数,只能精确到类,它是通过id从容器中获取对象
  2. 表达式语法
  图3-7是Spring官方文档对表达式语法的介绍,其中?号表示出现0次或1次。
  切点函数中共有6个参数可以指定,只有“返回类型”、“方法名”和“参数列表”是必须指定的,参数中可以使用通配符,不同位置的通配符的含义有所区别。

图3-7?切点函数的语法
* 方法中参数个数通配符写法:
  ()  表示没有参数。
  (*) 表示1个任意类型的参数。
  (..) 表示0个或多个参数。
* 类全名的包通配符写法:
  .. 表示当前包和子包。
  3. 举例说明
  (1)最精确的写法。
  
     execution(public void com.ssmbook2020.service.impl.AccountServiceImpl.
save())
  
  (2)匹配Service的包和子包下面所有的类和方法,方法参数是String类型。
  
     execution(* com.ssmbook2020.service..*.*(String))
  
  (3)覆盖最全的写法,匹配所有的类和方法。
  
     execution(* *(..))
  
  (4)匹配方法名是save或update的方法。匹配时也可以使用&&符号,虽然语法是正确的,但没有意义。
  
     execution(* save(..)) || execution(* update(..))
  
  (5)除了方法名是save的所有方法。
  
     !execution(* save(..))
  
  (6)匹配包和子包中的所有类,只精确到类。
  
     within(com.itheima..*)
  
  (7)从容器中获取一个id为accountService的类中的所有方法,只精确到类。
  
     bean(accountService)
  
  (8)从容器中获取所有Service的方法,只精确到类。
  
     bean(*Service) 


3.3.3?使用XML配置方式实现AOP
  在Spring中,AOP编程可以使用配置或注解两种实现方式,下面对此分别进行介绍。首先通过一个案例来介绍XML配置方式,该案例实现的功能是:当向数据库中保存账户时,使用日志记录这次保存操作。
  1. 开发流程
  该案例的开发步骤如下。
  (1)开发业务类,主业务方法为添加账户。
  (2)开发切面类,用于在每个主业务方法执行前添加记录日志的功能。
  (3)使用AOP将业务类与切面类织入在一起,实现需求的功能。
  2. 案例结构
  整个案例的工程结构如图3-8所示。

图3-8?XML配置方式的案例结构
  3. 业务接口和实现类
  (1)建立账户业务接口并保存账户。
  
     package com.ssmbook2020.service;
     /**
      * 账户业务接口
      */
     public interface AccountService {
         /**
          * 保存账户
          */
         void save();
     }
  
  (2)实现业务接口类,输出“保存账户”。
  
     package com.ssmbook2020.service.impl;
     import com.ssmbook2020.service.AccountService;
     /**
      * 实现类
      */
     public class AccountServiceImpl implements AccountService {
         @Override
         public void save() {
             System.out.println("保存账户");
         }
     }
  4. 记录日志的工具类
     package com.ssmbook2020.utils;
     import java.sql.Timestamp;
     /**
      * 记录日志功能的类:切面类 = 切入点(规则)+通知(方法)
      */
     public class LogAspect {
         /**
          * 记录日志
          */
         public void printLog() {
             System.out.println(new Timestamp(System.currentTimeMillis()) + " 记录日志");
         }
     }
  5.??AOP的配置
  1)AOP配置结构图
  图3-9为Spring AOP的配置结构图。在一个项目中只需配置一次。读者在编写XML配置时可以参照图3-9进行编写,避免出错。

图3-9?AOP的配置结构
  2)配置文件
  接下来在xml配置文件中编写AOP的配置,主要步骤如下。
  (1)配置日志记录类,包括切面类LogAspect。
  (2)配置正常的业务类AccountServiceImpl。
  (3)进行AOP配置,配置流程参考图3-9。需要注意的是,XML的Schema需要导入AOP的命名空间。
  
     <?xml version="1.0" encoding="UTF-8"?>
     <beans
         xmlns="http://www.springframework.org/schema/beans"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns:p="http://www.springframework.org/schema/p"
         xmlns:aop="http://www.springframework.org/schema/aop"
         xsi:schemaLocation="http://www.springframework.org/schema/beans 
         http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
         http://www.springframework.org/schema/aop 
         https://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
          <!--正常业务对象-->
         <bean class="com.ssmbook2020.service.impl.AccountServiceImpl" id=
"accountService"/>
         <!-- 切面类:日志记录对象 -->
         <bean class="com.ssmbook2020.utils.LogAspect" id="logAspect"/>
     
         <!-- 编写Aop的配置,要导入AOP的命名空间 -->
         <aop:config>
             <!--
                 配置切入点,通过切入点表达式来配置
                  id:给表达式定义唯一标识
                  expression: 使用切入点函数定义表达式,语法为“访问修饰符 返回类型 包名.类名.方法名(参数类型) 抛出异常类型” -->
             <aop:pointcut id="pt" expression="execution(public void 
com.ssmbook2020.service.impl.AccountServiceImpl.save())"/>
     
             <!-- 切面配置, ref引用切面对象id -->
             <aop:aspect ref="logAspect">
                 <!-- 使用什么类型的通知:前置通知,后置通知等
                 method:表示切面中的方法名字 pointcut-ref:引用上面的切入点表达式 -->
                 <aop:before method="printLog" pointcut-ref="pt"/>
             </aop:aspect>
         </aop:config>
     </beans>
  
  3)测试类
  配置完成AOP后,开始编写测试类,其步骤如下。
  (1)调用业务方法。
  (2)输出业务类的getClass(),查看输出的代理类对象。
  
     package com.ssmbook2020.test;
     import org.springframework.context.support.ClassPathXmlApplicationContext;
     import com.ssmbook2020.service.AccountService;
     public class TestAop {
         public static void main(String[] args){
             ClassPathXmlApplicationContext context = new 
ClassPathXmlApplicationContext("applicationContext.xml");
             //从容器中获取对象
             AccountService accountService = context.getBean(AccountService.class);
             //得到的是代理对象
             System.out.println(accountService.getClass());
             //调用业务方法
             accountService.save();
             //关闭容器
             context.close();
         }
     }
  
  测试类的运行结果如下。
  
     class com.sun.proxy.$Proxy5
     2021-01-20 12:22:11.373 记录日志
     保存账户
  
  AOP的配置本质上是使用代理模式实现功能增强,不需要自己编写代理模式,而通过配置就可以实现。由此可以看到,此时的AccountService的实现类其实是个代理对象。
  4)执行流程分析
  因为配置了AOP,分开编写的切面类LogAspect和正常业务类AccountServiceImpl被Spring AOP框架在执行时织入在一起,生成了一个代理对象,如图3-10所示。

图3-10?织入的流程
  Spring AOP其实使用了两种动态代理实现方式,如果一个类并没有实现任何的接口,则无法使用上面所说的JDK动态代理,这时需要使用CGLIB代理。CGLIB代理的本质是对原有类的继承,子类重写相应的方法,其生成过程与JDK类似,这里不再赘述。
3.3.4?使用注解方式实现AOP
  通过3.3.3节的案例可以知道,使用XML的方式配置AOP是比较烦琐的,如何简化XML的配置呢?可以使用注解的方式。
  对Spring AOP中常用的Aspect注解进行如下介绍。
  (1)@Aspect:放在类的上面,表示这是一个切面类。
  (2)@Before:前置增强。它有以下两个参数。
* value:该成员用于定义切点。
* execution:切点函数,告诉Spring在哪些地方进行前置增强的织入。
  (3)@AfterReturning:后置增强。它有以下4个参数。
* value: 该成员用于定义切点。
* pointcut:表示切点的信息。如果指定pointcut值,将覆盖value的值,可以理解为它们的作用是相同的。
* returning:将目标对象方法的返回值绑定给增强的方法,返回值的名字要与实际返回的变量名相同。
* argNames:同returning。
  (4)@Around:环绕增强。它有value和argNames两个参数,其含义同上。
  (5)@AfterThrowing:异常增强,有以下4个参数。
* value、pointcut、argNames同上。
* throwing:将抛出的异常绑定到增强方法中。
  (6)@After:最终增强。不管是抛出异常或是正常退出,该增强都会得到执行,它有Value和arg Names两个参数,其含义同上。
  1. 案例需求
  案例的业务需求描述如下。
  在登录的方法前面输出日志。用户开始登录,在登录方法的后面输出提示日志:用户登录成功或失败。
  在业务实现过程中,使用注解定义前置增强和后置增强,从而实现日志功能。
  2. 开发步骤
  该案例的主要步骤如下。
  (1)创建Java项目,添加Spring框架,注解方式的项目结构如图3-11所示。
  (2)编写各个类的代码如下,注意代码注释。
* User.java用户实体类。
  
     package com.ssmbook2020.entity;
     /**

图3-11?注解方式的项目结构
      * 用户实体类对象
      */
     public class User {
         private int id; 			  //主键
         private String name; 	  //用户名
         private String password;	  //密码
     
         public User(int id, String name, String password) { //带全参的构造方法
             super();
             this.id = id;
             this.name = name;
             this.password = password;
         }
     
         public User() { 			  //默认无参的构造方法
             super();
         }
         //省略了get和set方法
     }
  
* UserService.java业务接口类。
  
     package com.ssmbook2020.service;
     import com.ssmbook2020.entity.User;
     /**
      * 用户的业务接口
      */
     public interface UserService {
         /**
          * 登录的方法
          * @param name 用户名
          * @param password 密码
          * @return 登录成功返回User对象,登录失败返回null
          */
         public User login(String name,String password);
     }
  
* UserServiceImpl.java业务实现类。
  
     package com.ssmbook2020.service.impl;
     import com.ssmbook2020.entity.User;
     import com.ssmbook2020.service.UserService;
     /**
      * 用户业务类的实现
     */
     public class UserServiceImpl implements UserService {
         @Override
         public User login(String name, String password){
             System.out.println("业务方法login运行,正在登录...");
             //登录成功
             if ("newboy".equals(name) && "520".equals(password)){
                 return new User(100, "newboy", "520");
             }
             //登录失败
             return null;
         }
     }
  
* LoggerAdvice.java切面类。
  
     package com.ssmbook2020.utils;
     import java.sql.Timestamp;
     import org.apache.log4j.Logger;
     import org.aspectj.lang.JoinPoint;
     import org.aspectj.lang.annotation.AfterReturning;
     import org.aspectj.lang.annotation.Aspect;
     import org.aspectj.lang.annotation.Before;
     
     /** 需要织入的日志切面类 */
     @Aspect
     public class LoggerAdvice {
         //log4j日志类
         Logger logger = Logger.getLogger(LoggerAdvice.class);
     
         //后置增强
         @AfterReturning(pointcut = "execution(* com.ssmbook2020..*.*(..))", 
returning = "ret")
         public void afterReturning(JoinPoint join, Object ret){
             String method = join.getSignature().getName();
             Object[] args = join.getArgs();
             if ("login".equals(method)){
                 if (ret != null){
                     logger.info(new Timestamp(System.currentTimeMillis()) + " " + args[0] + "登录成功");
                 } else {
                     logger.info(new Timestamp(System.currentTimeMillis()) + " " + args[0] + "登录失败");
                 }
             }
         }
     
         //前置增强
         @Before(value = "execution(* com.ssmbook2020..*.*(..))")
         public void methodBefore(JoinPoint join){
             String method = join.getSignature().getName();
             Object[] args = join.getArgs();
             if ("login".equals(method)){
                 logger.info(new Timestamp(System.currentTimeMillis()) + " " + args[0] + "开始登录");
             }
         }
     }
  
  (3)配置applicationContext.xml文件,在XML文件头部添加AOP命名空间,以使用与AOP相关的标签。同时因为代码中用到了p的方式注解,所以也添加了p命名空间。
  
     <?xml version="1.0" encoding="UTF-8"?>
     <beans xmlns="http://www.springframework.org/schema/beans"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns:p="http://www.springframework.org/schema/p"
         xmlns:aop="http://www.springframework.org/schema/aop"
         xsi:schemaLocation="http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
     http://www.springframework.org/schema/aop
     http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
         <!-- 日志记录类 -->
         <bean id="loggerAdvice" class="com.ssmbook2020.utils.LoggerAdvice" />
         <!-- 业务类 -->
         <bean id="userService" class="com.ssmbook2020.service.impl.
UserServiceImpl" />
         <!-- 织入使用注解定义的增强,需要引入AOP命名空间 -->
         <aop:aspectj-autoproxy />
     </beans>
  
  上面的配置将所有的JavaBean加入Spring容器中,其中最重要的是<aop:aspectj- autoproxy />,表示所有的AOP自动代理,通过注解的方式织入。
  (4)构建测试类,其代码如下。
  
     package com.ssmbook2020.test;
     import org.springframework.context.support.ClassPathXmlApplicationContext;
     import com.ssmbook2020.service.UserService;
     public class TestAop {
         public static void main(String[] args){
             ClassPathXmlApplicationContext context = new 
ClassPathXmlApplicationContext("applicationContext.xml");
             //得到业务类
             UserService userService = (UserService) context.getBean("userService");
             //运行业务登录方法
             userService.login("newboy", "520");
             context.close();
         }
     }
  
  测试类代码的运行结果如下。
  
     INFO - 2021-01-20 12:48:22.617 newboy开始登录
     业务方法login运行,正在登录...
     INFO - 2021-01-20 12:48:22.618 newboy登录成功
  
  如果把userService.login("newboy", "520")换成userService.login("Lina", "1314"),则运行结果如下。
  
     INFO - 2021-01-20 12:48:05.503 Lina开始登录
     业务方法login运行,正在登录...
     INFO - 2021-01-20 12:48:05.504 Lina登录失败
  
  在业务类的代码运行时,该方法的前后各输出了日志的内容,这就是代码的织入,也称为前置或后置增强。
  通过使用注解,使得Spring AOP的配置被极大地简化。如果把业务类和切面类在Spring IoC中的配置也改成注解,则可以进一步简化XML的配置内容。
  
3.4
本 章 小 结
  
  本章介绍了Spring的另一个重要特性AOP,它是Spring框架最核心、最基础的技术之一。Spring AOP的实现原理是使用了动态代理模式,在此重点介绍了JDK的代理模式。
  本章分别讲解了XML配置和注解两种方式实现Spring AOP,注解的方式相对代码量更少,后期使用比较多。对Spring AOP的学习只是Spring框架学习的开始,它是一个庞大的框架,其目标是让一切Java EE的开发都变得更简洁。


  

习?题?3
  
  1. 什么是Spring AOP?
  2.??Spring AOP的实现原理是什么?
  3. 分别使用XML配置和注解方式实现本章案例。
  
  

  

  
50

  
51