【转载】面向切面编程(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 的整体架构:

 

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

(完) 

posted @ 2016-11-20 21:59  blcblc  阅读(464)  评论(0编辑  收藏  举报