Spring核心之AOP

Posted mini9264

tags:

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

 

1、什么是AOP

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

  AOP:面向切面编程,是一种在程序运行期间,通过动态代理实现在不修改源代码的情况下给程序动态且统一的添加新功能的一种技术。


目前最流行的 AOP 框架有两个,分别为 Spring AOP 和 AspectJ。

  Spring AOP 使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。

  AspectJ 是一个基于 Java 语言的 AOP 框架,从 Spring 2.0 开始,Spring AOP 引入了对 AspectJ 的支持。AspectJ 扩展了 Java 语言,提供了一个专门的编译器,在编译时提供横向代码的植入。

2、AOP的作用

  AOP 采取横向抽取机制,取代了传统纵向继承体系的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。

  • 日志记录

  • 性能统计

  • 安全控制

  • 事务处理

  • 异常处理

3、使用AOP的好处

  面向切面编程(AOP)和面向对象编程(OOP)类似,也是一种编程模式。Spring AOP 是基于 AOP 编程模式的一个框架,它的使用有效减少了系统间的重复代码,达到了模块间的松耦合目的。

  • 降低模块的耦合度

  • 使系统容易扩展

  • 更好的代码复用性

4、AOP的专业术语

          理解

 

 

 

5、AOP的实现

在不改变源代码的情况下 要求在执行添加 或者修改用户之前 打印一句话 之后打印一句话

此时我们要需要了解动态代理的方式 也就是Spring aop的底层原理

  AOP的实现原理

  • JDK动态代理 : 接口+实现类 获得这个实现类的代理对象

  • CGLIB代理 :对没有实现接口的类产生代理对象 生成这个类的子类

  • 了解点: 动态代理 需要依赖接口 获得是实现类的代理类

  • 实现思路: 接口 实现类 切面类 工厂

5.1、AOP的Spring JDK动态代理实现

1、创建项目,导入Spring支持和依赖的jar包

2、创建Dao层接口以及实现类,CustomerDao和CustomerDaoImpl

/**
*接口
*/
public interface CustomerDao {
    public void add(); // 添加

    public void update(); // 修改

    public void delete(); // 删除

    public void find(); // 查询
}
/**
*实现类
*/
public class CustomerDaoImpl implements CustomerDao {

    @Override
    public void add() {
        System.out.println("添加客户...");
    }

    @Override
    public void update() {
        System.out.println("修改客户...");
    }

    @Override
    public void delete() {
        System.out.println("删除客户...");
    }

    @Override
    public void find() {
        System.out.println("修改客户...");
    }
}
View Code

 

 

3、创建切面类MyAspect

public class MyAspect {
    public void myBefore() {
        System.out.println("方法执行之前");
    }

    public void myAfter() {
        System.out.println("方法执行之后");
    }
}

 

4、创建代理类MyBeanFactory(定义工厂,工厂模式创建对象),在该类中使用 java.lang.reflect.Proxy 实现 JDK 动态代理。

 1 public class MyBeanFactory {
 2 
 3     public static CustomerDao getBean() {
 4         // 准备目标类
 5         final CustomerDao customerDao = new CustomerDaoImpl();
 6         // 创建切面类实例
 7         final MyAspect myAspect = new MyAspect();
 8         // 使用代理类,进行增强
 9       
10      /**第一个参数:类加载器
11       * 获得方式 当前类的.class.getClassLoader()
12       * 第二个参数: 表示动态代理的类要实现的接口(接口是多实现)
13       * 被代理的类的class.getInterfaces()
14       * 第三个参数:InvocationHandler 对象
15       * 表示代理对象执行方法时会调用invoke方法
16       *
17       */
18 
19      CustomerDao customerDaoProxy = (CustomerDao) Proxy.newProxyInstance(
20 
21                 MyBeanFactory.class.getClassLoader(),
22                 CustomerDao.getClass().getInterfaces(),
23                 new InvocationHandler() {
24           // proxy 表示代理对象
25           //method 表示执行的方法
26           // 表示执行的方法
27                     @Override
28             public Object invoke(Object proxy, Method method,Object[] args) throws Throwable {
29                         myAspect.myBefore(); // 前增强
30                         Object obj = method.invoke(customerDao, args);
31                         myAspect.myAfter(); // 后增强
32                         return obj;
33                     }
34                 });
35 
36     return  customerDaoProxy ;
37    } 
38 }

 

 

  上述代码中,定义了一个静态的 getBean() 方法,这里模拟 Spring 框架的 IoC 思想,通过调用 getBean() 方法创建实例,第5 行代码创建了 customerDao 实例。

  第 7 行代码创建的切面类实例用于调用切面类中相应的方法;第 19~34 行就是使用代理类对创建的实例 customerDao 中的方法进行增强的代码,其中 Proxy 的 newProxyInstance() 方法的第一个参数是当前类的类加载器,第二参数是所创建实例的实现类的接口,第三个参数就是需要增强的方法。

  在目标类方法执行的前后,分别执行切面类中的 myBefore() 方法和 myAfter() 方法。

5、创建测试类,JDKProxyTest

public class JDKProxyTest {
    @Test
    public void test() {
        // 从工厂获得指定的内容(相当于spring获得,但此内容时代理对象)
        CustomerDao customerDao = MyBeanFactory.getBean();
        // 执行方法
        customerDao.add();
        customerDao.update();
        customerDao.delete();
        customerDao.find();
    }
}

 

5.2、AOP的Spring CGLIB动态代理实现

  通过Spring JDK动态代理实现案例的学习可以知道,JDK 动态代理使用起来非常简单,但是它也有一定的局限性,这是因为 JDK 动态代理必须要实现一个或多个接口,如果不希望实现接口,则可以使用 CGLIB 代理。

  CGLIB(Code Generation Library)是一个高性能开源的代码生成包,它被许多 AOP 框架所使用,其底层是通过使用一个小而快的字节码处理框架 ASM(Java 字节码操控框架)转换字节码并生成新的类。因此 CGLIB 要依赖于 ASM 的包,但是 Spring3.2.13 版本以后的核心包已经集成了 CGLIB 所需要的包,所以在开发中不需要另外导入 ASM 的 JAR 包了。

1、创建目标类GoodsDao,在类中定义增、删、改、查方法,并在每个方法编写输出语句。

public class GoodsDao {
    public void add() {
        System.out.println("添加商品...");
    }

    public void update() {
        System.out.println("修改商品...");
    }

    public void delete() {
        System.out.println("删除商品...");
    }

    public void find() {
        System.out.println("修改商品...");
    }
}
View Code

 

2、创建代理类MyBeanFactory

 1 public class MyBeanFactory {
 2     public static GoodsDao getBean() {
 3         // 准备目标类
 4         final GoodsDao goodsDao = new GoodsDao();
 5         // 创建切面类实例
 6         final MyAspect myAspect = new MyAspect();
 7         // 生成代理类,CGLIB在运行时,生成指定对象的子类,增强
 8         Enhancer enhancer = new Enhancer();
 9         // 确定需要增强的类
10         enhancer.setSuperclass(goodsDao.getClass());
11         // 添加回调函数
12         enhancer.setCallback(new MethodInterceptor() {
13             // intercept 相当于 jdk invoke,前三个参数与 jdk invoke—致
14             @Override
15             public Object intercept(Object proxy, Method method, Object[] args,
16                     MethodProxy methodProxy) throws Throwable {
17                 myAspect.myBefore(); // 前增强
18                 Object obj = method.invoke(goodsDao, args); // 目标方法执行
19                 myAspect.myAfter(); // 后增强
20                 return obj;
21             }
22         });
23         // 创建代理类
24         GoodsDao goodsDaoProxy = (GoodsDao) enhancer.create();
25         return goodsDaoProxy;
26     }
27 }

  上述代码中,应用了 CGLIB 的核心类 Enhancer。在第 10 行代码调用了 Enhancer 类的 setSuperclass() 方法,确定目标对象。

  第 12 行代码调用 setCallback() 方法添加回调函数;第 15 行代码的 intercept() 方法相当于 JDK 动态代理方式中的 invoke() 方法,该方法会在目标方法执行的前后,对切面类中的方法进行增强;第 24~25 行代码调用 Enhancer 类的 create() 方法创建代理类,最后将代理类返回。

3、创建测试类CGLIBProxyTest

public class CGLIBProxyTest {
    @Test
    public void test() {
        // 从工厂获得指定的内容(相当于spring获得,但此内容时代理对象)
        GoodsDao goodsDao = MyBeanFactory.getBean();
        // 执行方法
        goodsDao.add();
        goodsDao.update();
        goodsDao.delete();
        goodsDao.find();
    }
}

 

6、AOP框架AspectJ的使用(必须会)

  我们的动态代理虽然说可以实现AOP的一些内容 但是我们只是针对一个UserDao 动态生成了一个代理类 如果要生成的代理类比较多 就不是很优雅 Spring的AOP 采用的是 AspectJ框架完成的

1、创建项目,导入Spring及相关jar包

spring-jcl-5.1.9.RELEASE
spring-expression-5.1.9.RELEASE
spring-core-5.1.9.RELEASE
aspectjweaver-1.9.4
spring-aop-5.1.9.RELEASE
spring-beans-5.1.9.RELEASE
spring-context-5.1.9.RELEASE

2、创建UserDao接口和UserDaoImpl实现类

public interface UserDao {
    //添加用户的方法
    void addUser();
   //修改用户的方法
    void updateUser();

}

public class UserDaoImpl implements UserDao {
    @Override
    public void addUser() {
        System.out.println("用户添加成功");

    }

    @Override
    public void updateUser() {
        System.out.println("用户修改成功");

    }
}
View Code

 

3、编写切面类MyAspect2

/**
 * 切面类 实现方法拦截器
 */
public class MyAspect2 implements MethodInterceptor {


    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {

        System.out.println("执行之前打印的");
        Object proceed = methodInvocation.proceed();
        System.out.println("执行之后打印的");
        return proceed;
    }
}

 

4、编写spring的配置文件

7、切入点表达式

pointcut="execution(* com.shangma.cn.demo.UserDao.addUser())"

 

  • 语法

    execution(修饰符 返回值 包名.类名.方法名(参数) throws 异常)

  • 规则

 

 /**
     修饰符:一般省略 
         public   公共方法
         *       表示任意修饰符都行

     返回值:不能省略 
          Void     表示没有返回值
         String    表示字符串类型返回值
          *        表示任意 有没有返回值都行  有返回值什么类型都行 

     包   可以省略(支持通配符)
         com.shangma.cn   表示固定包名 
         com.shangma.cn.*  表示com.shangma.cn包下的任意子包
          com.shangma.cn..   表示com.shangma.cn包下的任意子包  包含自己 

     类   可以省略(支持通配符)
         UserDao  表示固定的类 
        User*       看清楚这个*前面没有. 不是User.*  是User* 表示以User开头 
           *        表示任意类 

     方法名 不能省略 支持通配符
         AddUser  表示固定的方法
         Add*       add开头的方法
         *        表示任意方法

     参数
         ()   表示无参数
         (int)  表示一个参数 
         (int,int) 表示2个参数
         (..)  表示 参数任意 
     */

 

 

  常用写法 : execution(* com.shangma.cn..(..)) 表示 com.shangma.cn 包下所有的类所有的方法都被增强

8、Spring 的6种通知类型

  • 前置通知 (before):目标方法执行前执行

  • 后置通知(afterReturning): 目标方法执行后执行

  • 环绕通知(around): 目标方法执行前和后执行

  • 最终通知 (after): 目标方法不管出不出异常 都会执行

  • 异常通知(afterThrowding): 目标方法出异常执行

  • 引介通知 (declare):不掌握

8.1、Spring配置通知之xml的方式

1、使用的类用上面编写的UserDao和UserDaoImpl

2、新建切面类

public class MyAspect3 {

    public void before(JoinPoint joinPoint) {
        System.out.println("前置通知" + joinPoint.getSignature().getName());
    }

    public void afterReturning(JoinPoint joinPoint) {
        System.out.println("后置通知" + joinPoint.getSignature().getName());
    }

    public void around(ProceedingJoinPoint proceedingJoinPoint) {
        System.out.println("方法执行前");

        try {
            proceedingJoinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }

        System.out.println("方法执行后");
    }

    public void afterThrowing() {
        System.out.println("异常通知");
    }

    public void after(JoinPoint joinPoint) {
        System.out.println("最终通知");
    }
}

 

3、编写配置文件

    <bean id="personService" class="com.shangma.cn.demo1.UserDaoImpl"/>

    <bean id="myAspect3" class="com.shangma.cn.demo3.MyAspect3"/>

    <!--    配置AOP-->
    <aop:config>
        <!--    配置通知之编写XML的方式-->
        <aop:aspect id="myAspect2" ref="myAspect3">
            <!--    切入点表达式语法:execution(修饰符 返回值 包名 类名 方法名(参数列表) throws 异常)-->
            <aop:pointcut id="myCut" expression="execution(* com.shangma.cn.demo1.*.*(..))"/>
            <!--前置通知-->
            <aop:before method="before" pointcut-ref="myCut"/>
            <!--后置通知-->
            <aop:after-returning method="afterReturning" pointcut-ref="myCut"/>
            <!--环绕通知-->
            <aop:around method="around" pointcut-ref="myCut"/>
            <!--异常通知-->
            <aop:after-throwing method="afterThrowing" pointcut-ref="myCut"/>
            <!--最终通知-->
            <aop:after method="after" pointcut-ref="myCut"/>

        </aop:aspect>

    </aop:config>

 

8.2、Spring配置通知之注解的方式

1、修改UserDaoImpl类,在类体上加注解@Component

2、编写切面类MyAspect4

@Component
@Aspect
public class MyAspect4 {


    @Before("myPointCut()")
    public void before(JoinPoint joinPoint) {
        System.out.println("前置通知" + joinPoint.getSignature().getName());
    }

    @AfterReturning(pointcut = "myPointCut()")
    public void afterReturning(JoinPoint joinPoint) {
        System.out.println("后置通知" + joinPoint.getSignature().getName());
    }

    @Around("myPointCut()")
    public void around(ProceedingJoinPoint proceedingJoinPoint) {
        System.out.println("方法执行前");

        try {
            proceedingJoinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }

        System.out.println("方法执行后");
    }

    @AfterThrowing("myPointCut()")
    public void afterThrowing() {
        System.out.println("异常通知");
    }

    @After("myPointCut()")
    public void after(JoinPoint joinPoint) {
        System.out.println("最终通知");
    }

    /**
     * 统一设置切入点
     */
    @Pointcut("execution(* com.shangma.cn.demo1.UserDao.addUser())")
    public void myPointCut(){

    }
}

 

3、编写配置文件

    <!-- 开启包扫描-->
    <context:component-scan base-package="com.shangma.cn"/>
    <!-- 开启AOP注解-->
    <aop:aspectj-autoproxy/>

 

9、Spring集成Junit测试

1、导入jar包

 

 2、编写测试类

 

以上是关于Spring核心之AOP的主要内容,如果未能解决你的问题,请参考以下文章

Spring框架系列 - 深入浅出Spring核心之面向切面编程(AOP)

Spring核心之AOP

#yyds干货盘点# Spring核心之面向切面编程(AOP)

Java实战之03Spring-03Spring的核心之AOP(Aspect Oriented Programming 面向切面编程)

Spring之AOP(核心思想:代理模式)

Spring 4 官方文档学习核心技术之Spring AOP