理解SpringAOP-菜鸟新手入门

SpringFrameworkAOP学习笔记

  • 某一天
    • 笔记背景
    • 什么是AOP
      • JDK实例
    • 为什么AOP
    • java 动态代理
      • CGLib动态代理实例
    • JDK 动态代理与CGLib动态代理
    • 术语
    • AspectJ

某一天

文章内容部分来自于
https://gitee.com/zhongfucheng/Java3y#spring%E5%AE%B6%E6%97%8F
http://c.biancheng.net/view/4241.html

笔记背景

本人在学习Spring玩转全家桶时,发现自己对AOP以及切片编程等基础Spring知识有些模糊不清,故写翻看网上各种文章,网上文章有的抽象有的例子鲜明但是喧宾夺主,故还是我自己写个文章梳理一下我自己学到的内容

什么是AOP

面向切面编程(AOP)和面向对象编程(OOP)类似,也是一种编程模式,全称是“Aspect Oriented Programming”,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率。
目前最流行的 AOP 框架有两个,分别为 Spring AOP 和 AspectJ

举例说明

JDK实例

在包下 com.mengma.dao 创建CustomerDao 、CustomerDaoImpl的两个类文件

package com.mengma.dao;
public interface CustomerDao {
    public void add(); // 添加
    public void update(); // 修改
    public void delete(); // 删除
    public void find(); // 查询
}


package com.mengma.dao;
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("修改客户...");
    }
}

在包 com.mengma.jdk 下,分别创建MyAspect、MyBeanFactory 、JDKProxyTest三个类

package com.mengma.jdk;
public class MyAspect {
    public void myBefore() {
        System.out.println("方法执行之前");
    }
    public void myAfter() {
        System.out.println("方法执行之后");
    }
}



package com.mengma.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.mengma.dao.CustomerDao;
import com.mengma.dao.CustomerDaoImpl;
public class MyBeanFactory {
    public static CustomerDao getBean() {
        // 准备目标类
        final CustomerDao customerDao = new CustomerDaoImpl();
        // 创建切面类实例
        final MyAspect myAspect = new MyAspect();
        // 使用代理类,进行增强
        return (CustomerDao) Proxy.newProxyInstance(
                MyBeanFactory.class.getClassLoader(),
                new Class[] { CustomerDao.class }, new InvocationHandler() {
                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {
                        myAspect.myBefore(); // 前增强
                        Object obj = method.invoke(customerDao, args);
                        myAspect.myAfter(); // 后增强
                        return obj;
                    }
                });
    }
}



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

程序要想运行同样需要上篇文章列出的5个spring包
结果如下
理解SpringAOP-菜鸟新手入门_第1张图片
其中 Proxy 的 newProxyInstance() 方法的第一个参数是当前类的类加载器,第二参数是所创建实例的实现类的接口,第三个参数就是需要增强的方法

上述例子是,JDK 动态代理,是通过 JDK 中的 java.lang.reflect.Proxy 类实现的

为什么AOP

Spring AOP主要做的事情就是:「把重复的代码抽取,在运行的时候往业务方法上动态植入“切面类代码”」

通过动态代理,我们可以把对象「增强」,将非业务代码写在要「增强」的逻辑上

完了以后,我们就可以通过「增强后的对象」去调用方法,最终屏蔽掉「重复代码」

AOP编程可以简单理解成:在执行某些代码前,执行另外的代码

其实Spring AOP的底层原理就是动态代理!Spring AOP使用纯Java实现,它不需要专门的编译过程,也不需要特殊的类装载器,它在运行期通过代理方式向目标类织入增强代码。在Spring中可以无缝地将Spring AOP、IoC和AspectJ整合在一起。

java 动态代理

在Java中动态代理有两种方式:

JDK动态代理
上述例子就是jdk的动态代理,下面再举例子说明CGLib动态代理

CGLib动态代理实例

在 com.mengma.dao 包下创建类 GoodsDao

package com.mengma.dao;
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("修改商品...");
    }
}

在 com.mengma.cglib 包下创建类 MyBeanFactory和CGLIBProxyTest

package com.mengma.cglib;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import com.mengma.dao.GoodsDao;
import com.mengma.jdk.MyAspect;
public class MyBeanFactory {
    public static GoodsDao getBean() {
        // 准备目标类
        final GoodsDao goodsDao = new GoodsDao();
        // 创建切面类实例
        final MyAspect myAspect = new MyAspect();
        // 生成代理类,CGLIB在运行时,生成指定对象的子类,增强
        Enhancer enhancer = new Enhancer();
        // 确定需要增强的类
        enhancer.setSuperclass(goodsDao.getClass());
        // 添加回调函数
        enhancer.setCallback(new MethodInterceptor() {
            // intercept 相当于 jdk invoke,前三个参数与 jdk invoke—致
            @Override
            public Object intercept(Object proxy, Method method, Object[] args,
                    MethodProxy methodProxy) throws Throwable {
                myAspect.myBefore(); // 前增强
                Object obj = method.invoke(goodsDao, args); // 目标方法执行
                myAspect.myAfter(); // 后增强
                return obj;
            }
        });
        // 创建代理类
        GoodsDao goodsDaoProxy = (GoodsDao) enhancer.create();
        return goodsDaoProxy;
    }
}



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

上述代码中,应用了 CGLIB 的核心类 Enhancer

intercept() 方法相当于 JDK 动态代理方式中的 invoke() 方法,该方法会在目标方法执行的前后,对切面类中的方法进行增强;之后调用 Enhancer 类的 create() 方法创建代理类,最后将代理类返回

JDK 动态代理与CGLib动态代理

动态代理也有个约束:目标对象一定是要有接口的,没有接口就不能实现动态代理……----->因此出现了cglib代理

cglib代理也叫子类代理,从内存中构建出一个子类来扩展目标对象的功能

Spring AOP默认是使用JDK动态代理,如果代理的类没有接口则会使用CGLib代理

如果是单例的我们最好使用CGLib代理,如果是多例的我们最好使用JDK代理

JDK在创建代理对象时的性能要高于CGLib代理,而生成代理对象的运行性能却比CGLib的低。

如果是单例的代理,推荐使用CGLib

看到这里我们就应该知道什么是Spring AOP(面向切面编程)了:将相同逻辑的重复代码横向抽取出来,使用动态代理技术将这些重复代码织入到目标对象方法中,实现和原来一样的功能

现在应用更多更流行的是另一种方式@AspectJ注解驱动的切面

术语

连接点(Join point):

能够被拦截的地方:Spring AOP是基于动态代理的,所以是方法拦截的。每个成员方法都可以称之为连接点~

切点(Poincut):

具体定位的连接点:上面也说了,每个方法都可以称之为连接点,我们具体定位到某一个方法就成为切点。

增强/通知(Advice):

表示添加到切点的一段逻辑代码,并定位连接点的方位信息。

简单来说就定义了是干什么的,具体是在哪干

Spring AOP提供了5种Advice类型给我们:前置、后置、返回、异常、环绕给我们使用!

org.springframework.aop.MethodBeforeAdvice(前置通知) 在方法之前自动执行的通知称为前置通知,可以应用于权限管理等功能。
org.springframework.aop.AfterReturningAdvice(后置通知) 在方法之后自动执行的通知称为后置通知,可以应用于关闭流、上传文件、删除临时文件等功能。
org.aopalliance.intercept.MethodInterceptor(环绕通知) 在方法前后自动执行的通知称为环绕通知,可以应用于日志、事务管理等功能。
org.springframework.aop.ThrowsAdvice(异常通知) 在方法抛出异常时自动执行的通知称为异常通知,可以应用于处理异常记录日志等功能。
org.springframework.aop.IntroductionInterceptor(引介通知) 在目标类中添加一些新的方法和属性,可以应用于修改旧版本程序(增强类)。

织入(Weaving):

将增强/通知添加到目标类的具体连接点上的过程。

引入/引介(Introduction):

引入/引介允许我们向现有的类添加新方法或属性。是一种特殊的增强!

切面(Aspect):

切面由切点和增强/通知组成,它既包括了横切逻辑的定义、也包括了连接点的定义

AspectJ

使用 AspectJ 开发 AOP 通常有两种方式:
基于 XML 的声明式
基于 Annotation 的声明式

xml由于随着bean增多,会特别臃肿,所以主流更多采用注解

xml存在错误,不知道怎么回事
理解SpringAOP-菜鸟新手入门_第2张图片

注释掉就可以正常运行了,不过注掉的事务代码不了
理解SpringAOP-菜鸟新手入门_第3张图片

所以下面是注解的样例

package com.mengma.dao;
public interface CustomerDao {
    public void add(); // 添加
    public void update(); // 修改
    public void delete(); // 删除
    public void find(); // 查询
}


package com.mengma.dao;
import org.springframework.stereotype.Repository;

@Repository("customerDao")
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("修改客户...");
    }
}



package com.aspectJ.annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
//切面类
@Aspect
@Component
public class MyAspect {
    // 用于取代:
    // expression="execution(*com.mengma.dao..*.*(..))" id="myPointCut"/>
    // 要求:方法必须是private,没有值,名称自定义,没有参数
    @Pointcut("execution(* com.mengma.dao..*.*(..))")
    private void myPointCut() {
    }
    // 前置通知
    @Before("myPointCut()")
    public void myBefore(JoinPoint joinPoint) {
        System.out.print("前置通知,目标:");
        System.out.print(joinPoint.getTarget() + "方法名称:");
        System.out.println(joinPoint.getSignature().getName());
    }
    // 后置通知
    @AfterReturning(value = "myPointCut()")
    public void myAfterReturning(JoinPoint joinPoint) {
        System.out.print("后置通知,方法名称:" + joinPoint.getSignature().getName());
    }
    // 环绕通知
    @Around("myPointCut()")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint)
            throws Throwable {
        System.out.println("环绕开始"); // 开始
        Object obj = proceedingJoinPoint.proceed(); // 执行当前目标方法
        System.out.println("环绕结束"); // 结束
        return obj;
    }
    // 异常通知
    @AfterThrowing(value = "myPointCut()", throwing = "e")
    public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
        System.out.println("异常通知" + "出错了" + e.getMessage());
    }
    // 最终通知
    @After("myPointCut()")
    public void myAfter() {
        System.out.println("最终通知");
    }
}



package com.aspectJ.annotation;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.mengma.dao.CustomerDao;

public class AnnotationTest {
    @Test
    public void test() {
        String xmlPath = "com/aspectJ/annotation/applicationContext.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                xmlPath);
        System.out.println(applicationContext);
        // 从spring容器获取实例
        CustomerDao customerDao = (CustomerDao) applicationContext
                .getBean("customerDao");
        // 执行方法
        customerDao.add();
    }
}

<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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    
    <context:component-scan base-package="com.mengma.dao"/>
    <context:component-scan base-package="com.aspectJ.annotation"/>

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

上述代码中, @Aspect 注解用于声明这是一个切面类,该类作为组件使用,所以要添加 @Component 注解才能生效。 @Poincut 注解用于配置切入点,取代 XML 文件中配置切入点的代码。

在每个通知相应的方法上都添加了注解声明,并且将切入点方法名“myPointCut”作为参数传递给要执行的方法,如需其他参数(如异常通知的异常参数),可以根据代码提示传递相应的属性值。

导入了 AOP 命名空间及其配套的约束,使切面类中的 @Aspect注解能够正常工作;

添加了扫描包,使注解生效;切面开启自动代理。

你可能感兴趣的:(spring,aop)