微热山丘,探索 IoC、AOP 实现原理(二) AOP 实现原理

AOP 实现原理

一、简介

AOP 是 Aspect Oriented Programming 的简写,面向切面编程。主要的作用是以一种统一的方式对程序的逻辑进行修改、增强处理。可以在编译时也可以在运行时实现。编译时处理一般是通过字节码处理技术,运行时进行的一般是通过动态代理技术实现。

二、AOP 核心概念

  • Concerns, 关注:有两类,核心关注–是关于业务逻辑的;横切关注–是一些通用的逻辑,比如日志、缓存。
  • Joinpoint, 连接点:是执行时的切入点。可以是字段访问也可以是被调用方法。基于动态代理技术实现的一般只支持方法调用的连接点。
  • Target, 目标:是一个被切入的地方,一般是一个业务逻辑。比如是对一个业务逻辑的方法的调用。
  • Pointcut, 切入点:并不是所有的连接点都需要切入,切入点用于指定哪些连接点需要切入。
  • Advice, 建议:定义了 Aspect 的任务和什么时候执行它,是在核心关注之前还是之后。
  • Aspect, 方面:Advice 和 pointcut 定义一个方面。Advice 定义 Aspect 的任务和什么时候执行它,而切入点 pointcut 定义在哪里具体地方切入。就是说 Aspect 定义了它是什么东西、什么时候切入和在哪里切入。
  • Weaving, 织入:织入是一个把横切方面混合到业务目标对象的过程。可以是在编译时间,也可以是在运行时使用类加载机制,Spring AOP 是在运行时生成 bean 时处理。

下面是在 Sping 的 XML 里定义一个 AOP 的配置,注意其中个元素间的关系:

<!-- 一个 weaving 的定义 -->
<aop:config>
    <!-- 定义 aspect, ref 指向 任务的定义 -->
    <aop:aspect id="aspectCommonLogHandler" ref="commonLogHandler">
        <!-- 定义 pointcut -->
        <aop:pointcut id="commonLogPointcut" expression="execution( * net.coderbee.*.controller..*.*(..))" />

        <!-- 定义 advice, around 表示在目标的前/后执行 -->
        <aop:around method="inceptor" pointcut-ref="commonLogPointcut" />
    </aop:aspect>
</aop:config>

三、AOP 实现

1. 各组件定义配置

warnhill 的 AOP 实现没有采用 Spring 那样的配置方式,而是采用 bean 定义的形式组织起来:

<!-- 定义织入逻辑的实现 -->
<bean id="aspectJAutoProxyCreator" class="net.coderbee.warmhill.aop.AspectJAutoProxyCreator" />

<!-- 定义 pointcut -->
<bean id="pointcut" class="net.coderbee.warmhill.aop.AspectJExpressionPointcut" >
    <property name="expression" value="execution(* net.coderbee..*.*(..))" />
</bean>

<!-- 定义 任务 -->
<bean id="timerInterceptor" class="net.coderbee.warmhill.aop.TimerInterceptor" />
<bean id="anotherInterceptor" class="net.coderbee.warmhill.aop.AnotherInterceptor" />

<!-- 定义 aspect -->
<bean id="testAdvisor" class="net.coderbee.warmhill.aop.AspectJExpressionPointcutAdvisor">
    <property name="advice" ref="timerInterceptor" />
    <property name="pointcut" ref="pointcut" />
</bean>
<bean id="testAdvisor2" class="net.coderbee.warmhill.aop.AspectJExpressionPointcutAdvisor">
    <property name="advice" ref="anotherInterceptor" />
    <property name="pointcut" ref="pointcut" />
</bean>

继续阅读

微热山丘,探索 IoC、AOP 实现原理(一) IoC 实现原理

一. 简介及项目设定

1.1 微热山丘 介绍

warmhill(微热山丘)是一个参考 Spring 实现 IoC、AOP 特性的小项目。

相比于 Spring 庞杂的分类、层层继承、抽象,warmhill(微热山丘) 里都是简单直接的类、方法调用,核心在于简洁地展现实现原理。

1.2 项目设定及限制

有如下的限定:
1. 所有的 bean 都是单例。
2. bean 只有一个唯一的标识符 id,没有名字、别名。
3. 所有的 bean 都是立即初始化的,不支持延迟初始化。
4. 对于 BeanPostProcessor 的应用是基于声明的先后顺序。
5. 对于 AOP 的切入点,只支持对方法调用的拦截,不细分 Before/After/Around/Throw 等。
6. 对于 AOP 的配置也是基于 bean 定义的,不支持 <aop:config> 标签。
7. 目前只支持从 XML 方式配置 bean 。
8. bean 的属性目前只支持 String 类型和对其他 bean 的引用,只支持 setter 方法的依赖注入。

二. IoC、AOP 基本概念介绍

  • Resource: 资源。可存放在任意位置,只有一个方法: InputStream getStream();

  • BeanDefinition: bean 的定义信息。

  • BeanDefitionLoader: bean 定义加载类,只有一个方法: List<BeanDefinition> load();

  • BeanFactory: bean 工厂,负责根据 bean 定义创建 bean 的实例。

继续阅读

Spring AOP 与 事务实现

1. AOP 与方法调用

对于 接口 interface,可以通过 java.lang.reflect.Proxy 来生成动态代理对象;
对于 类 class,可以通过字节码技术,如 CBLIB 来生成一个子类作为代理对象,此时类不能声明为 final 。

当 Spring 把 Bean 注入到另一个 Bean 时,其实注入的是它生成的代理对象,进行方法调用时,首先调用的是代理对象上的方法,代理对象的方法最终再调目标对象的方法,也就是开发人员编写的方法。Spring 通过在调用目标方法前后做处理来实现一些特性,例如事务管理、缓存等。

Spring 的 AOP 是基于对代理对象的方法调用的拦截的,只能拦截外部对象 对 某个对象的方法的调用,对象的方法调用同一个类的方法是不会被拦截的。

@Service
public class SpringTxService {
    private static final Logger LOGGER = LoggerFactory
            .getLogger(SpringTxService.class);

    @Transactional(propagation = Propagation.REQUIRED)
    public void add() {
        LOGGER.info("do add");
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void requireNew() {
        LOGGER.info("do requireNew");
    }

    public void composite() {
        add();
        requireNew();
    }
}

上面的代码是基于注解进行事务控制的,composite 方法没有声明事务属性,它会调用 add、 requireNew
方法。当把 SpringTxService 注入到另一个 BeanB:
1. 在 BeanB 的方法里调用代理对象 composite 时,最终执行 add、 requireNew 是没有在事务里执行的,只是普通的方法调用。
2. 在 BeanB 的方法里调用代理对象的 add、 requireNew 方法时,这两个方法都会分别在相应的事务里执行。

当发现基于 AOP 实现的特性没有预期的效果时,一定要看看是不是在代理对象上调用还是目标类内部的方法调用。

继续阅读