• 【转载】面向切面编程(AOP)学习


    看到这篇文章,学习一下:http://www.ciaoshen.com/2016/10/28/aop/

    想理清一下从“动态代理”,到 “注释”,到“面向切面编程”这么一个技术演进的脉络

    只想讲清楚两个问题:

    1. 什么是面向切面编程?为什么要面向切面?
    2. 动态代理技术是怎么实现面向切面的?注释在其中扮演了什么角色?

    提到另一篇文章:https://my.oschina.net/huangyong/blog/161338

    目前最知名最强大的 Java 开源项目就是 AspectJ 了。Rod Johnson(老罗)写了一个叫做 Spring 框架,从此一炮走红,成为了 Spring 之父。

    他在自己的 IOC 的基础之上,又实现了一套 AOP 的框架,后来仿佛发现自己越来越走进深渊里,在不能自拔的时候,有人建议他还是集成 AspectJ 吧,他在万般无奈之下才接受了该建议。于是,我们现在用得最多的想必就是 Spring + AspectJ 这种 AOP 框架了。

    Spring AOP:抛出增强

    程序报错,抛出异常了,一般的做法是打印到控制台或日志文件中,这样很多地方都得去处理,有没有一个一劳永逸的方法呢?那就是 Throws Advice(抛出增强),它确实很强:

    @Component
    public class GreetingImpl implements Greeting {
    
        @Override
        public void sayHello(String name) {
            System.out.println("Hello! " + name);
    
            throw new RuntimeException("Error"); // 故意抛出一个异常,看看异常信息能否被拦截到
        }
    }

    下面是抛出增强类的代码:

    @Component
    public class GreetingThrowAdvice implements ThrowsAdvice {
    
        public void afterThrowing(Method method, Object[] args, Object target, Exception e) {
            System.out.println("---------- Throw Exception ----------");
            System.out.println("Target Class: " + target.getClass().getName());
            System.out.println("Method Name: " + method.getName());
            System.out.println("Exception Message: " + e.getMessage());
            System.out.println("-------------------------------------");
        }
    }

    8. Spring AOP:引入增强

    这个功能确实太棒了!但还有一个更厉害的增强。如果某个类实现了 A 接口,但没有实现 B 接口,那么该类可以调用 B 接口的方法吗?如果您没有看到下面的内容,一定不敢相信原来这是可行的!

    以上提到的都是对方法的增强,那能否对类进行增强呢?用 AOP 的行话来讲,对方法的增强叫做 Weaving(织入),而对类的增强叫做 Introduction(引入)。而 Introduction Advice(引入增强)就是对类的功能增强,它也是 Spring AOP 提供的最后一种增强。

    定义了一个新接口 Apology(道歉):

    public interface Apology {
    
        void saySorry(String name);
    }

    借助 Spring 的引入增强。

    @Component
    public class GreetingIntroAdvice extends DelegatingIntroductionInterceptor implements Apology {
    
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            return super.invoke(invocation);
        }
    
        @Override
        public void saySorry(String name) {
            System.out.println("Sorry! " + name);
        }
    }

    如何配置

    <?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: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/context
           http://www.springframework.org/schema/context/spring-context.xsd">
    
        <context:component-scan base-package="aop.demo"/>
    
        <bean id="greetingProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
            <property name="interfaces" value="aop.demo.Apology"/>          <!-- 需要动态实现的接口 -->
            <property name="target" ref="greetingImpl"/>                    <!-- 目标类 -->
            <property name="interceptorNames" value="greetingIntroAdvice"/> <!-- 引入增强 -->
            <property name="proxyTargetClass" value="true"/>                <!-- 代理目标类(默认为 false,代理接口) -->
        </bean>
    
    </beans>

    客户端代码

    public class Client {
    
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("aop/demo/spring.xml");
            GreetingImpl greetingImpl = (GreetingImpl) context.getBean("greetingProxy"); 
          // 注意:转型为目标类,而并非它的 Greeting 接口 greetingImpl.sayHello("Jack"); Apology apology = (Apology) greetingImpl;
          // 将目标类强制向上转型为 Apology 接口(这是引入增强给我们带来的特性,也就是“接口动态实现”功能) apology.saySorry("Jack"); } }

    以上的示例代码都已经下载:

    /Users/baidu/Documents/Data/Work/Code/Demo_Code/aop_demo

    下面这篇是续集:

    https://my.oschina.net/huangyong/blog/161402

    9. Spring AOP:切面

    只需要拦截特定的方法就行了,没必要拦截所有的方法。借助了 AOP 的一个很重要的工具,Advisor(切面),来解决这个问题。它也是 AOP 中的核心。

    这里提到这个“拦截匹配条件”在 AOP 中就叫做 Pointcut(切点),其实说白了就是一个基于表达式的拦截条件。

    归纳一下,Advisor(切面)封装了 Advice(增强)与 Pointcut(切点 )

    下面要做的就是拦截这两个新增的方法,而对 sayHello() 方法不作拦截。

    @Component
    public class GreetingImpl implements Greeting {
    
        @Override
        public void sayHello(String name) {
            System.out.println("Hello! " + name);
        }
    
        public void goodMorning(String name) {
            System.out.println("Good Morning! " + name);
        }
    
        public void goodNight(String name) {
            System.out.println("Good Night! " + name);
        }
    }

    Spring AOP 中,提供了许多切面类了,这些切面类就有基于正则表达式的切面类。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans ...">
    
        <context:component-scan base-package="aop.demo"/>
    
        <!-- 配置一个切面 -->
        <bean id="greetingAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
            <property name="advice" ref="greetingAroundAdvice"/>            <!-- 增强 -->
            <property name="pattern" value="aop.demo.GreetingImpl.good.*"/> <!-- 切点(正则表达式) -->
        </bean>
    
        <!-- 配置一个代理 -->
        <bean id="greetingProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
            <property name="target" ref="greetingImpl"/>                <!-- 目标类 -->
            <property name="interceptorNames" value="greetingAdvisor"/> <!-- 切面 -->
            <property name="proxyTargetClass" value="true"/>            <!-- 代理目标类 -->
        </bean>
    
    </beans>

    注意以上代理对象的配置中的 interceptorNames,它不再是一个增强,而是一个切面,因为已经将增强封装到该切面中了。此外,切面还定义了一个切点(正则表达式),其目的是为了只将满足切点匹配条件的方法进行拦截。

     除了 RegexpMethodPointcutAdvisor 以外,在 Spring AOP 中还提供了几个切面类,比如:

    DefaultPointcutAdvisor:默认切面(可扩展它来自定义切面)
    
    NameMatchMethodPointcutAdvisor:根据方法名称进行匹配的切面
    
    StaticMethodMatcherPointcutAdvisor:用于匹配静态方法的切面

    10. Spring AOP:自动代理(扫描 Bean 名称)

    能否让 Spring 框架为我们自动生成代理呢?Spring AOP 提供了一个可根据 Bean 名称来自动生成代理的工具,它就是 BeanNameAutoProxyCreator。是这样配置的:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans ...>
    
        ...
    
        <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
            <property name="beanNames" value="*Impl"/>                       <!-- 只为后缀是“Impl”的 Bean 生成代理 -->
            <property name="interceptorNames" value="greetingAroundAdvice"/> <!-- 增强 -->
            <property name="optimize" value="true"/>                         <!-- 是否对代理生成策略进行优化 -->
        </bean>
    
    </beans>

    关于CGLib和JDK动态代理:

    根据多年来实际项目经验得知:CGLib 创建代理的速度比较慢,但创建代理后运行的速度却非常快,而 JDK 动态代理正好相反。
    如果在运行的时候不断地用 CGLib 去创建代理,系统的性能会大打折扣,所以建议一般在系统初始化的时候用 CGLib 去创建代理,
    并放入 Spring 的 ApplicationContext 中以备后用。

    11. Spring AOP:自动代理(扫描切面配置)

    Spring AOP 基于切面也提供了一个自动代理生成器:DefaultAdvisorAutoProxyCreator。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans ...>
    
        ...
    
        <bean id="greetingAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
            <property name="pattern" value="aop.demo.GreetingImpl.good.*"/>
            <property name="advice" ref="greetingAroundAdvice"/>
        </bean>
    
        <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
            <property name="optimize" value="true"/>
        </bean>
    
    </beans>

    这里无需再配置代理了,因为代理将会由 DefaultAdvisorAutoProxyCreator 自动生成。也就是说,这个类可以扫描所有的切面类,并为其自动生成代理。

    12. Spring + AspectJ(基于注解:通过 AspectJ execution 表达式拦截方法)

    @Aspect
    @Component
    public class GreetingAspect {
    
        @Around("execution(* aop.demo.GreetingImpl.*(..))")
        public Object around(ProceedingJoinPoint pjp) throws Throwable {
            before();
            Object result = pjp.proceed();
            after();
            return result;
        }
    
        private void before() {
            System.out.println("Before");
        }
    
        private void after() {
            System.out.println("After");
        }
    }

    方法的参数中包括一个 ProceedingJoinPoint 对象,它在 AOP 中称为 Joinpoint(连接点),可以通过该对象获取方法的任何信息,例如:方法名、参数等。

    总结如下图:

    再来一张表格,总结一下各类增强类型所对应的解决方案:

    增强类型 基于 AOP 接口 基于 @Aspect 基于 <aop:config>
    Before Advice(前置增强) MethodBeforeAdvice @Before <aop:before>
    AfterAdvice(后置增强) AfterReturningAdvice @After <aop:after>
    AroundAdvice(环绕增强) MethodInterceptor @Around <aop:around>
    ThrowsAdvice(抛出增强 ThrowsAdvice @AfterThrowing <aop:after-throwing>
    IntroductionAdvice(引入增强) DelegatingIntroductionInterceptor @DeclareParents <aop:declare-parents>

    最后给一张 UML 类图描述一下 Spring AOP 的整体架构:

     以上,需要结合例子深入学习。

    (完) 

  • 相关阅读:
    第一个博客——python通过值传递函数参数
    JAVA并发体系-1.1-终结任务或线程
    JAVA并发体系-1.4-线程池
    JAVA并发体系-1.3-线程之间的协作
    JAVA并发体系-2-锁机制
    并发实现机制-1-综述
    JAVA并发体系-3-并发容器
    并发实现机制-2-互斥实现
    并发实现机制-3-死锁和饥饿
    JAVA持有对象
  • 原文地址:https://www.cnblogs.com/charlesblc/p/6083687.html
Copyright © 2020-2023  润新知