Spring学习记录 (下篇)


三、AOP原理剖析(代理这块需要继续加强)

Aspect Oriented Programming(AOP),即面向切面编程。对所有对象或一类对象编程。 在不增加代码的基础上,还增加新的功能

3.1 AOP原理

3.1.1 AOP术语介绍

  1. 切面(aspect):要实现的交叉功能,是系统模块化的一个切面或领域。如日志记录。
  2. 通知:切面的实际实现,他通知系统新的行为。如在日志通知包含了实现日志功能的代码,如向日志文件写日志。通知在连接点插入到应用系统中。
  3. 连接点:应用程序执行过程中插入切面的地点,可以是方法调用,异常抛出,或者要修改的字段。
  4. 切入点:定义了通知应该应用在哪些连接点,通知可以应用到AOP框架支持的任何连接点。
  5. 引入:为类添加新方法和属性。
  6. 目标对象:被通知的对象。既可以是你编写的类也可以是第三方类。
  7. 代理对象:将通知应用到目标对象后创建的对象,应用系统的其他部分不用为了支持代理对象而改变。
  8. 织入:将切面应用到目标对象从而创建一个新代理对象的过程。织入发生在目标对象生命周期的多个点上:
    • 编译期:切面在目标对象编译时织入,这需要一个特殊的编译器。
    • 类装载期:切面在目标对象被载入JVM时织入,这需要一个特殊的类载入器。
    • 运行期:切面在应用系统运行时织入。

      3.1.2 AOP原理图

      原理解释

  • 代理对象:仅需要配置在XML配置文件中即可,用于将目标对象和通知进行绑定;
    • 静态代理:使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;静态代理以 AspectJ 为代表。
    • 动态代理:在运行时借助于 JDK 动态代理、CGLIB 等在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。Spring AOP 用的就是 动态代理。
      • Jdk自带Proxy实现动态代理:代理对象和目标对象都实现同一个接口
      • CGLIB实现动态代理:代理对象继承目标对象的类
  • 通知:切面编程的实现,只要在代理对象中配置了目标对象,该通知就可以作用到目标对象上;
  • 目标对象:需要使用通知的对象,例如想要通过通知来写日志,做安全检查等

通知类型

3.1.3 举个栗子

本例中,


  • 有两个接口HelloServiceInterfaceByeServiceInterface,其中类SayingService实现了这两个接口中的方法。
  • 配置文件中,为类SayingService的具体对象配置了四种通知(严格说是五种),分别在该对象调用每一个方法时为其执行这几个通知
  • 注意!!!通知针对的是对象,对象调用几个方法,通知就会执行多少遍,除非配置切入点,使得通知绑定到具体的方法上;
  • 其他解释见代码注释
  • 目录结构
      TestSpring
          |---src
              |---com.netease
              |          |---Advice
              |                |---MyBeforeAdvice (前置通知,实现了接口MethodBeforeAdvice)
              |                |---MyAfterReturningAdvice (后置通知,实现了接口AfterReturningAdvice)
              |                |---MyInterceptAdvice (环绕通知,实现了接口MethodInterceptor)
              |                |---MyThrowableAdvice (异常通知,实现了接口ThrowsAdvice)
              |          |---Service
              |                |---ByeServiceInterface.java (包含方法sayBye)
              |                |---HelloServiceInterface.java (包含方法sayHello)
              |                |---SayingService.java (实现了以上两个接口)
              |          |---Test
              |                |---TestService.java
              |---ApplicationContext.xml
      External Libraries
          |--- spring.jar
          |--- common-logging.jar
          |--- ...
    
  • 代码描述

    public class TestService {
        public static void main(String[] args) {
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
            /*注意这里必须获取代理对象!!否则就白配置了*/
            /*代理类实际上实现了这个接口,因此可以通过强转得到HelloServiceInterface*/
            HelloServiceInterface helloService = (HelloServiceInterface) applicationContext.getBean("proxyFactoryBean");
            helloService.sayHello();
            /*同理,由于代理类同样实现了ByeServiceInterface接口*/
            ByeServiceInterface byeService = (ByeServiceInterface) helloService;
            byeService.sayBye();
        }
    }
    
  • 配置文件

  • <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns=....省略>
    
        <!--配置通知-->
        <bean id="myBeforeAdvice" class="com.netease.Advice.MyBeforeAdvice"/>
        <bean id="myAfterReturningAdvice" class="com.netease.Advice.MyAfterReturningAdvice"/>
        <bean id="myInterceptAdvice" class="com.netease.Advice.MyInterceptAdvice"/>
        <bean id="myThrowableAdvice" class="com.netease.Advice.MyThrowableAdvice"/>
    
        <!--配置切入点(可选)-->
        <!--这里表示将myBeforeAdvice这个通知绑定到sayHello方法,对其他方法失效-->
        <bean id="nameMatchMethodPointcutAdvisor"
              class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
            <property name="advice" ref="myBeforeAdvice"/>
            <property name="mappedName" value="sayHello"/>
        </bean>
    
        <!--配置目标对象-->
        <bean id="helloService" class="com.netease.Service.App">
            <property name="name">
                <value>zhujie</value>
            </property>
        </bean>
    
        <!--配置代理对象-->
        <bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
            <!--配置被代理的接口集合,表示该代理对象服务于哪些接口,即需要实现哪几个接口-->
            <property name="proxyInterfaces">
                <list>
                    <value>com.netease.Service.HelloServiceInterface</value>
                    <value>com.netease.Service.ByeServiceInteface</value>
                </list>
            </property>
            <!--配置被代理的通知-->
            <property name="interceptorNames">
                <list>
                    <!--<value>myBeforeAdvice</value>-->
                    <!--注意这里写的是我们配置的切入点id,如果仍然写前置通知的id,则切入点配置就无效了-->
                    <value>nameMatchMethodPointcutAdvisor</value>
                    <value>myBeforeAdvice</value>
                    <value>myAfterReturningAdvice</value>
                    <value>myInterceptAdvice</value>
                    <value>myThrowableAdvice</value>
                </list>
            </property>
            <!--配置被代理的对象-->
            <!--表示该对象调用方法时会被注入通知-->
            <property name="target" ref="helloService"/>
        </bean>
    </beans>
    

3.1.4 使用注解声明切面

  • 常用注解
    • @Aspect 声明切面
    • @Before @After @Around @AfterReturning @AfterThrowing(切点表达式) 声明通知
    • @Pointcut 声明频繁使用的切点表达式
  • 如何让Spring知道这些事切面和通知
    • 如果使用的是javaconfig配置 加入注解@EnableAspectJAutoProxy即可
    • 如果使用的是XML配置,声明aop命名空间后,在中加入,并且声明该切面的bean

3.1.5 使用XML声明切面

  • 把adviceBean中的adviceMethod作用到targetMethod方法调用之前

    <aop:config>
    <aop:aspect ref="adviceBean">
      <aop:before
      pointcut="execution(* packageName.className.targetMethod(para...))"
      method="adviceMethod"/>
    </aop:config>
    
  • 另一种引用切点表达式的方法

    <aop:config>
    <aop:aspect ref="adviceBean">
      <aop:pointcut
            id="performance"
            experession="execution(* packageName.className.targetMethod(para...))"/>
      <aop:before
      pointcut-ref="performance"
      method="adviceMethod"/>
    </aop:config>
    
本文来自网易实践者社区,经作者朱洁 授权发布。