Spring AOP(面向切面编程)

Spring AOP

1. AOP概述

AOP(Aspect Oriented Programing) 翻译过来就是面向切面编程。

可以先来了解一下面向xx编程都是什么意思:

  • POP (Producer Oriented Programing) 面向过程(方法、函数):以过程为基本单位的程序开发,通过过程间的彼此协同,相互调用,完成程序的构建
  • OOP (Object Oritened Programing) 面向对象编程 :以对象为基本单位的程序开发,通过对象间的彼此协同,相互调用,完成程序的构建

那么AOP (Aspect Oriented Programing) 面向切面编程 = Spring动态代理开发以切⾯为基本单位的程序开发,通过切面间的彼此协同,相互调用,完成程序的构建。
切面 = 切入点 + 额外功能

AOP的本质就是Spring得动态代理开发,通过代理类为原始类增加额外功能。
好处:利于原始类的维护

在这里插入图片描述

2. 切入点详解

切入点是组成切面的单位,简单来讲,切入点就是我们要去对其进行代理,增加额外功能的点。这个点可以是方法。

程序员需要在配置文件中进行切入点的配置,面向切面编程的过程大致为以下步骤:

  1. 创建原始类对象
  2. 实现相关接口
  3. 配置文件中定义切入点
切⼊点决定额外功能加⼊位置(方法)
<aop:pointcut id="pc" expression="execution(* *(..))"/>
exection(* *(..)) ---> 匹配了所有⽅法 a b c
1. execution() 切⼊点函数
2. * *(..) 切⼊点表达式

2.1 切入点表达式

2.1.1 方法切入点表达式

在这里插入图片描述

* *(..) --> 所有⽅法
* ---> 修饰符 返回值
* ---> ⽅法名
()---> 参数表
..---> 对于参数没有要求 (参数有没有,参数有⼏个都⾏,参数是什么类型的都⾏)
  • 定义login方法作为切入点

    * login(..)
    
  • 定义login方法且login方法有两个字符串类型的参数 作为切入点

    * login(String,String)
    
    # 注意:非java.lang包下的内容需要写全限定名,如:
    * register(com.jc.bean.User)
    
    # .. 可以与类型参数连用
    * login(String,..) 可以匹配:
    login(String)
    login(String,String)
    login(String,com.jc.bean.User)
    
  • 精准方法切入点限定

    修饰符/返回值 包.类.⽅法(参数)
    * com.jc.service.UserServiceImpl.login(..)
    * com.jc.service.UserServiceImpl.login(String,String)
    
2.1.2 类切入点表达式

指定特定类作为切入点(额外功能加入的位置),自然这个类中的所有方法,都会加上对应的额外功能。

  • 语法1

    # 类中的所有⽅法加⼊了额外功能
    * com.jc.service.UserServiceImpl.*(..)
    
  • 语法2

    # 忽略包
    1. 类只存在⼀级包 com.UserServiceImpl
    * *.UserServiceImpl.*(..)
    2. 类存在多级包 com.jc.service.UserServiceImpl
    * *..UserServiceImpl.*(..)
    
2.1.3 包切入点表达式

指定包作为额外功能加入的位置,自然包中的所有类及其方法都会加入额外的功能

  • 语法1

    # 切入点包中的所有类,必须在service包中,不能在service子包中
    * com.jc.service.*.*(..)
    
  • 语法2

    # 切入点包中的所有类,必须在service包中,但是可以在service包的子包中
    * com.jc.servce..*.*(..)
    

2.2 切入点函数

切入点函数:用于执行切入点表达式

2.2.1 execution
最为重要的切⼊点函数,功能最全。
执⾏ ⽅法切⼊点表达式 类切⼊点表达式 包切⼊点表达式

弊端: execution执⾏切⼊点表达式 ,书写麻烦
	  execution(* com.baizhiedu.proxy..*.*(..))

注意:其他的切⼊点函数 简化是execution书写复杂度,功能上完全⼀致
2.2.2 args
作⽤:主要⽤于函数(⽅法) 参数的匹配

例如:
切⼊点:⽅法参数必须得是2个字符串类型的参数
execution(* *(String,String)) 等同于 args(String,String)
2.2.3 within
作⽤:主要⽤于进⾏类、包切⼊点表达式的匹配

切⼊点: UserServiceImpl这个类

execution(* *..UserServiceImpl.*(..))

within(*..UserServiceImpl)

execution(* com.baizhiedu.proxy..*.*(..))

within(com.baizhiedu.proxy..*)
2.2.4 @annotation

作用:为具有特殊注解的方法加入额外功能

<aop:pointcut id="pc" expression="@annotation(com.annotation.MyAnnotation)"/>

自定义注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}
2.2.5 切入点函数的逻辑运算

指的是整合多个切入点函数⼀起配合工作,进而完成更为复杂的需求

  • and与操作

    案例: login 同时 参数 2个字符串
    1. execution(* login(String,String))
    2. execution(* login(..)) and args(String,String)
    
    注意:与操作不同⽤于同种类型的切⼊点函数
    
    案例: register⽅法 和 login⽅法作为切⼊点
    execution(* login(..)) or execution(* register(..))
    
  • or或操作

    案例: register⽅法 和 login⽅法作为切⼊点
    execution(* login(..)) or execution(* register(..))
    

3. AOP编程的开发

通过Spring动态代理要实现额外功能,也就是我们要使用代理模式添加的功能。

Spring中主要实现方式有两种:

  • 实现MethodBeforeAdvice接口
  • 实现 MethodInterceptor接口

3.1 开发步骤

  1. 提供原始类对象
  2. 编写额外功能(实现相关接口)
  3. 配置切入点
  4. 组装切面 (额外功能 + 切入点)

3.2 MethodBeforeAdvice接口

额外的功能书写在接行的实现中,运行在原始方法执行之前运行额外功能。 方法名翻译过来叫做“方法前置通知”。

代码实现:

  • 实现接口

    import org.springframework.aop.MethodBeforeAdvice;
    
    import java.lang.reflect.Method;
    
    public class Before implements MethodBeforeAdvice {
        @Override
        public void before(Method method, Object[] objects, Object o) throws Throwable {
            System.out.println("---------proxy处理--------");
        }
    }
    
    <bean id="before" class="com.jc.proxy.Before"/>
    
  • 定义切入点并整合

    <aop:config>
        <!--定义切入点-->
        <aop:pointcut id="pc" expression="execution(* *(..))"/>
        <aop:advisor advice-ref="before" pointcut-ref="pc"></aop:advisor>
    </aop:config>
    
  • 调用

    @Test
    public void test1() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 此时根据id获取对象自动获取的是代理类对象
        UserService userService = (UserService) ctx.getBean("userService");
        userService.login("tom", "123456");
        userService.register(new User("jerry", "123123"));
    }
    

3.3 MethodInterceptor(方法法拦截器) 接口

methodinterceptor接口:额外功能可以根据需要运行在原始方法执行前、后、前后。

这种方式更加泛用,实际使用的也是最多的。

代码实现:

  • 实现接口

    import org.aopalliance.intercept.MethodInterceptor;
    import org.aopalliance.intercept.MethodInvocation;
    
    
    public class Around implements MethodInterceptor {
        @Override
        public Object invoke(MethodInvocation methodInvocation) throws Throwable {
            System.out.println("---前置处理---");
            Object ret = methodInvocation.proceed();
            System.out.println("---后置处理---");
            return ret;
        }
    }
    
    <bean id="around" class="com.jc.proxy.Around"/>
    
  • 定义切入点并整合

    <aop:config>
        <!--定义切入点-->
        <aop:pointcut id="pc" expression="execution(* *(..))"/>
        <aop:advisor advice-ref="around" pointcut-ref="pc"></aop:advisor>
    </aop:config>
    
  • 调用

    @Test
    public void test1() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 此时根据id获取对象自动获取的是代理类对象
        UserService userService = (UserService) ctx.getBean("userService");
        userService.login("tom", "123456");
        userService.register(new User("jerry", "123123"));
    }
    

4. Spring动态代理细节分析

  • Spring创建的动态代理类在哪里?

    1. Spring框架在运⾏时,通过动态字节码技术,在JVM创建的,运⾏在JVM内部,等程序结束后,会和JVM⼀起消失
    2. 什么叫动态字节码技术:通过第三个动态字节码框架,在JVM中创建对应类的字节码,进⽽创建对象,当虚拟机结束,动态字节码跟着消失。
    3. 结论:动态代理不需要定义类⽂件,都是JVM运⾏过程中动态创建的,所以不会造成静态代理,类⽂件数量过多,影响项⽬管理的问题。
    
  • 动态代理编程简化代理的开发

    在额外功能不改变的前提下,创建其他⽬标类(原始类)的代理对象时,只需要指定原始(⽬标)对象即可。
    
  • 动态代理额外功能的维护性大大增强

5. AOP底层实现原理

问题:

1. AOP如何创建动态代理类(动态字节码技术)
2. Spring⼯⼚如何加⼯创建代理对象
		通过原始对象的id值,获得的是代理对象

5.1 Spring工厂如何加工原始对象

AOP底层实现实际上就是使用了动态代理。

那么我们在Spring中配置了原始类的信息,但是通过Spring工厂获取的确实代理类对象是怎么实现的呢?

Spring工厂创建一个对象的流程是这样的:

  1. 从配置文件中读取数据,代用类的构造器创建对象
  2. 注入属性
  3. 调用Bean初始化的前置方法(BeanPostProcessor接口中定义)
  4. 执行初始化方法(可以通过实现InitializingBean接口自定义)
  5. 调用Bean初始化的前置方法(BeanPostProcessor接口中定义)
  6. 返回创建的对象

在这里插入图片描述

5.1 自定义原始对象加工

  • 编码

    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanPostProcessor;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class ProxyBeanPostProcessor implements BeanPostProcessor {
    
        /*
            前置不做处理直接返回bean
         */
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            return bean;
        }
    
        /*
            后置处理中创建动态代理对象,返回代理类对象
         */
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    
            InvocationHandler h = new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("--- log begin ---");
                    Object ret = method.invoke(bean, args);
                    System.out.println("--- log end ---");
                    return ret;
                }
            };
    
            Object proxyInstance = Proxy.newProxyInstance(bean.getClass().getClassLoader(), bean.getClass().getInterfaces(), h);
            return proxyInstance;
        }
    }
    
  • Spring配置文件:

    <bean id="userService" class="com.jc.factory.impl.UserServiceImpl"/>
    
    <bean id="proxyBeanPostProcessor" class="com.jc.factory.ProxyBeanPostProcessor"/>
    
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神烦狗闯入了你的博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值