【Spring学习笔记】AOP

一、何为AOP?

AOP(Aspect-Oriented Programming,面向切面编程)是对传统传统 OOP(Object-Oriented Programming,面向对象编程)的补充,属于一种横向扩展。其将与核心业务无关的代码,如日志记录、性能监控、事务处理等从业务逻辑代码中抽离出来,进行横向排列,从而实现低耦合,提高开发效率。

3

二、AOP相关术语

名称说明
Advice(通知)例如日志记录、性能监控、事务处理等增强的功能。
Joinpoint(连接点)具体执行增强的地方。例如方法调用前、调用后和方法捕获异常后。
Pointcut(切入点)匹配连接点的正则表达式,是实现增强的具体连接点的集合。
Aspect(切面)切入点和通知的结合。
Introduction(引介)一种特殊的通知,可以在运行期为类动态地添加一些方法或 Field。
Proxy(代理)一个类被 AOP 织入增强后,就产生一个结果代理类。
Target(目标)指代理的目标对象。
Weaving(织入)把切面应用到目标对象,并创建代理对象的过程。

三、AOP流行框架比较

当下最流行的AOP框架为Spring AOP 及 AspectJ

比较Spring AOPAspectJ
能力和目标旨在通过Spring IoC提供一个简单的AOP实现。
不是一个完整的AOP解决方案,只能用于被Spring容器管理的bean。
完整的AOP解决方案,比Spirng AOP复杂。
能够被应用于所有的领域对象。
织入时机只能在运行时织入可以在编译时,加载期,编译后(jar包和字节码文件)。
依赖使用JDK动态代理或者VGLIB动态代理。不依赖任何运行时环境,只需要引入编译器AspectJ compiler(ajc)完成织入。
连接点使用代理模式,所以不能作用于final类、static方法、final方法。
只支持方法执行连接点。
没有限制。方法调用,方法执行,构造器调用,构造器执行,静态初始化执行,对象初始化,成员引用,成员赋值,处理器执行,通知执行。
复杂度不需要额外的编译器,只能和Spirng管理的bean一起工作 AOP需要引入AJC编译器并重新打包
性能基于代理的框架,运行时会有目标类的代理对象生成。在应用执行前织入切面到代码中,没有额外的运行时开销。

四、动态代理

基于接口的动态代理
提供者:JDK官方的ava.lang.reflect.Proxy 类。
要求: 被代理类至少实现一个接口。

基于子类的动态代理
提供者:第三方的CGLib, CGLIB 要依赖于 ASM 的包。
要求:被代理类不能用final修饰的类(最终类)。

下面以一个例子来了解两者的差别:

1.创建接口UserDao
public interface UserDao {

    public void add();// 添加

    public void delete();// 删除
}
2.创建实现类 UserDaoImpl
public class UserDaoImpl implements UserDao {
    @Override
    public void add() {
        System.out.println("添加用户");
    }

    @Override
    public void delete() {
        System.out.println("删除用户");
    }
}
3.创建切面类MyAspect
public class MyAspect {
    public void myBefore() {
        System.out.println("方法执行之前...");
    }
    public void myAfter() {
        System.out.println("方法执行之后...");
    }
}
4.创建代理类 JdkBeanFactory

JDK动态代理实现原理

public class JdkBeanFactory {
    public static UserDao getBean() {
        // 准备目标类
        final UserDaoImpl userDao = new UserDaoImpl();
        // 创建切面类实例
        final MyAspect myAspect = new MyAspect();

        // 使用代理类,进行增强
        return (UserDao) Proxy.newProxyInstance(JdkBeanFactory.class.getClassLoader(),
                new Class[]{UserDao.class}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        myAspect.myBefore(); // 前增强
                        Object obj = method.invoke(userDao, args);
                        myAspect.myAfter(); // 后增强
                        return obj;
                    }
                });
    }
}

创建测试类 JDKProxyTests

class JdkProxyTests {

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

执行结果:

方法执行之前...
添加用户
方法执行之后...
方法执行之前...
删除用户
方法执行之后...
5.创建代理类 CglibBeanFactory

CGLIB动态代理实现原理

public class CglibBeanFactory {
    public static UserDao getBean() {
        // 准备目标类
        final UserDaoImpl userDao = new UserDaoImpl();
        // 创建切面类实例
        final MyAspect myAspect = new MyAspect();
        // 生成代理类,CGLIB在运行时,生成指定对象的子类,增强
        Enhancer enhancer = new Enhancer();
        // 确定需要增强的类
        enhancer.setSuperclass(userDao.getClass());
        // 添加回调函数
        enhancer.setCallback(new MethodInterceptor() {
            // intercept 相当于 jdk invoke,前三个参数与 jdk invoke—致
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                myAspect.myBefore();// 前增强
                Object obj = method.invoke(userDao, objects);// 目标方法执行
                myAspect.myAfter();// 后增强
                return obj;
            }
        });
        // 创建代理类
        UserDao userDaoProxy = (UserDao) enhancer.create();
        return userDaoProxy;
    }
}

创建测试类 CglibProxyTests

public class CglibProxyTests {

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

测试结果:

方法执行之前...
添加用户
方法执行之后...
方法执行之前...
删除用户
方法执行之后...

五、使用AspectJ开发AOP

1.创建接口UserDao
public interface UserDao {

    public void add();// 添加

    public void delete();// 删除
}
2.创建实现类 UserDaoImpl
public class UserDaoImpl implements UserDao {
    @Override
    public void add() {
        System.out.println("添加用户");
    }

    @Override
    public void delete() {
        System.out.println("删除用户");
    }
}
3. 创建切面类 XmlAspect
public class XmlAspect {
    // 前置通知
    public void myBefore(JoinPoint joinPoint) {
        System.out.print("前置通知,目标:"+joinPoint.getTarget()+" ");
        System.out.print("方法名称:"+joinPoint.getSignature().getName()+"\n");
    }

    // 后置通知
    public void myAfterReturning(JoinPoint joinPoint) {
        System.out.print("后置通知,目标:"+joinPoint.getTarget()+" ");
        System.out.print("方法名称:"+joinPoint.getSignature().getName());
    }

    // 环绕通知
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint)
            throws Throwable {
        System.out.println("环绕开始"); // 开始
        Object obj = proceedingJoinPoint.proceed(); // 执行当前目标方法
        System.out.println("环绕结束"); // 结束
        return obj;
    }

    // 异常通知
    public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
        System.out.println("异常通知" + "出错了" + e.getMessage());
    }

    // 最终通知
    public void myAfter() {
        System.out.println("最终通知");
    }
}
4.创建基于Xml的配置文件 xmlBean.xml
<?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: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">

    <!--1.配置通知类-->
    <bean id="myAspect" class="com.aop.aspectj.xml.XmlAspect"/>
    <!--2.配置目标类 -->
    <bean id="userDao" class="com.aop.aspectj.dao.impl.UserDaoImpl"/>

    <!--3.声明aop配置 -->
    <aop:config>
        <!--4.配置切面 id:给切面提供一个唯一标识;ref:引用配置好的通知类 bean 的 id。-->
        <aop:aspect id="xmlAspect" ref="myAspect">
            <!-- 5.配置切入点,通知最后增强哪些方法
                expression:用于定义切入点表达式。
                id:用于给切入点表达式提供一个唯一标识-->
            <aop:pointcut id="myPointCut" expression="execution ( * com.aop.aspectj.dao.*.* (..))"/>
            <!-- 6.使用 aop:xxx 配置对应的通知类型
                method:指定通知中方法的名称。
                pointct:定义切入点表达式
                pointcut-ref:指定切入点表达式的引用-->
            <!--6.1 前置通知,关联通知 Advice和切入点PointCut -->
            <aop:before method="myBefore" pointcut-ref="myPointCut"/>
            <!--6.2 后置通知,在方法返回之后执行,就可以获得返回值returning 属性 -->
            <aop:after-returning method="myAfterReturning"
                                 pointcut-ref="myPointCut" returning="returnVal"/>
            <!--6.3 环绕通知 -->
            <aop:around method="myAround" pointcut-ref="myPointCut"/>
            <!--6.4 异常通知:用于处理程序发生异常,可以接收当前方法产生的异常
                *注意:如果程序没有异常,则不会执行增强
                *throwing属性:用于设置通知第二个参数的名称,类型Throwable -->
            <aop:after-throwing method="myAfterThrowing"
                                pointcut-ref="myPointCut" throwing="e"/>
            <!--6.5 最终通知:无论程序发生任何事情,都将执行 -->
            <aop:after method="myAfter" pointcut-ref="myPointCut"/>
        </aop:aspect>
    </aop:config>
</beans>
5. 创建测试方法 testXml()
    @Test
    public void testXml() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                "XmlBean.xml");
        // 从spring容器获取实例
        UserDao userDao = (UserDao) applicationContext
                .getBean("userDao");
        // 执行方法
        userDao.add();
    }

测试结果:

前置通知,目标:com.aop.aspectj.dao.impl.UserDaoImpl@5b78fdb1 方法名称:add
环绕开始
添加用户
最终通知
环绕结束
后置通知,目标:com.aop.aspectj.dao.impl.UserDaoImpl@5b78fdb1 方法名称:add

测试异常通知:
在UserDaoImpl中add()添加 int i= 1/0 ;

前置通知,目标:com.aop.aspectj.dao.impl.UserDaoImpl@5b78fdb1 方法名称:add
环绕开始
最终通知
异常通知出错了/ by zero
6.创建切面类 XmlAspect
@Aspect
@Component
public class AnnotationAspect {
    // 用于取代:<aop:pointcut expression="execution(*com.aop.aspectj.dao..*.*(..))" id="myPointCut"/>
    // 要求:方法必须是private,且没有值,名称可自定义,没有参数
    @Pointcut("execution(* com.aop.aspectj.dao.*.*(..))")
    private void myPointCut() {
    }
    // 前置通知
    @Before("myPointCut()")
    public void myBefore(JoinPoint joinPoint) {
        System.out.print("前置通知,目标:"+joinPoint.getTarget()+" ");
        System.out.print("方法名称:"+joinPoint.getSignature().getName()+"\n");
    }
    // 后置通知
    @AfterReturning(value = "myPointCut()")
    public void myAfterReturning(JoinPoint joinPoint) {
        System.out.print("后置通知,目标:"+joinPoint.getTarget()+" ");
        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("最终通知");
    }
}
7.创建基于注解的配置文件 AnnotationBean.xml
<?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: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.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <!--扫描含com.aop-->
    <context:component-scan base-package="com.aop"/>
    <!-- 使切面开启自动代理 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
8.创建测试方法 testAnnotation()
    @Test
    public void testAnnotation() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                "AnnotationBean.xml");
        // 从spring容器获取实例
        UserDao userDao = (UserDao) applicationContext
                .getBean("userDao");
        // 执行方法
        userDao.delete();
    }

测试结果:

环绕开始
前置通知,目标:com.aop.aspectj.dao.impl.UserDaoImpl@245a060f 方法名称:delete
删除用户
后置通知,目标:com.aop.aspectj.dao.impl.UserDaoImpl@245a060f 方法名称:delete
最终通知
环绕结束

测试异常通知:
在UserDaoImpl中add()添加 int i= 1/0 ;

环绕开始
前置通知,目标:com.aop.aspectj.dao.impl.UserDaoImpl@1ded7b14 方法名称:delete
异常通知出错了/ by zero
最终通知

六、AOP相关细节

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

2.1 语法格式

execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名]([参数列表]))

2.2 实例分析

表达式含义
execution(* com.spring.iop.dao.*(..))dao接口中声明的所有方法。
第一个*代表任意修饰符及任意返回值。
第二个*代表任意方法。
..匹配任意数量、任意类型的参数。
execution(public * dao.*(..))dao接口的所有公有方法
execution(public double dao.*(..))dao接口中返回double类型数值的方法
execution(public double dao.*(double, ..))第一个参数为double类型,
.. 匹配任意数量、任意类型的参数。
execution(public double dao.*(double, double))参数类型为double,double类型的方法

特别地,在AspectJ中,切入点表达式可以通过 “&&”、“||”、“!”等操作符结合起来。

表达式含义
execution (* *.add(int,..))任意类中第一个参数为int类型的add方法或sub方法。
3. Annotation 注解
名称说明
@Aspect用于定义一个切面。
@Before用于定义前置通知,相当于 BeforeAdvice。
@AfterReturning用于定义后置通知,相当于 AfterReturningAdvice。
@Around用于定义环绕通知,相当于MethodInterceptor。
@AfterThrowing用于定义抛出通知,相当于ThrowAdvice。
@After用于定义最终final通知,不管是否异常,该通知都会执行。
@DeclareParents用于定义引介通知,相当于IntroductionInterceptor(不要求掌握)。

参考资料

Spring AOP概念理解
比较Spring AOP与AspectJ
AOP(面向切面编程)
Spring-aop 全面解析(从应用到原理)
JDK动态代理实现原理
CGLIB动态代理实现原理

  • 1
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值