AOP介绍

面向对象编程(OOP)有一些弊端,当需要为多个不具有继承关系的对象引入同一个公共行为时,例如日志、安全监测等,我们只有在每个对象里引用公共行为,这样程序就产生了大量的重复代码,程序就不便于维护了,所以就有了面向对象编程的补充,即面向方面编程(AOP),AOP所关注的方向是横向的,不同于OOP的纵向。

一 动态AOP

Spring是否支持注解的AOP是由一个配置文件控制的,也就是<aop:aspectj-autoproxy/>,当在配置文件中声明了这句配置的时候,Spring就会支持注解的AOP。

 对于AOP的实现,基本上都是靠AnnototionAwrreAspecrjAutoProxyCreator去完成,它可以根据@Point注解定义的切点来自动代理相匹配的bean。

在类的层级中,我们看到AnnotationAwareAspectjAutoProxyCreator实现了BeanPostProcessor接口,而实现BeanPostProcessor后,当Spring加载这个Bean(AOP解析的bean)时会在实例化前调用其PostProcessAfterlnitialization方法,而我们对于AOP逻辑的分析也由此开始。

处理proxy-target-class以及expose-proxy属性:

①proxy-target-class: SpringAOP部分使用JDK动态代理或者CGLIB来为目标对象创建代理.(建议尽量使用JDK的动态代理).如果被代理的目标对象实现了至少一个接口,则会使用JDK动态代理,所有该目标类型实现的接口都将被代理.若试目标对象没有实现任何接口,则创建一个CGLIB代理.如果你希望强制使用CGLIB代理.(例如希望代理目标对象的所有方法,而不只是实现自接口的方法)那也可以.但是需要考虑以下两个问题:

  无法通知( advise)Final方法,因为它们不能被覆写.

  你需要将CGLIB二进制发行包放在classpath下面。

与之相较.JDK本身就提供了动态代理,强制使用CGLIB代理需要将<aop:config>的

proxy-target-class属性设为true:

    <aop: config proxy-target-class=”true”>….</aop: config>

    当需要使用CGLIB代理和@AspectJ自动代理支持,可以按照以下方式设置<aop:aspectj-autoproxy>的proxy-target-class属性:

    < aop: config proxy-target-class=”true”/>

    而实际使用的过程中才会发现细节问题的差别,

    JDK动态代理:其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理.

    CGLIB代理:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类.CGLIB是高效的代码生成包,底层是依靠ASM(开源的Java字节码编辑类库)操作字节码实现的,性能比JDK高.

②expose-proxy:有时候目标对象内部的自我调用将无法实施切面中的增强,如下示例:

public interface AService{
		public void a();
    public void b();
}
@SerVice
public class AServicelmpl1 implements AService {
    @Transactional (propagation=Propagation. REOUIRED)
    public void a(){
        this.b();
    }
    @Transactional (propagation= Propagation.REQUIRES_NEW)
    public void b(){
    }
}

此处的this指向目标对象,因此调用this.b()将不会执行b事务切面,即不会执行事务增强,因此b方法的事务定义“@Transactional(propagation= Propagation.REQUIRES_NEW)”将不会实施,为了解决这个问题,我们可以这样做:

    <aop: aspectj-autoproxy  expose-proxy=”true”/>

    然后将以上代码中的“this.b();”修改为“((AService) AopContext.currentProxy()).b();”即可。

通过以上的修改便可以完成对a和b方法的同时增强。

2 创建AOP代理

2.1获取增强器

(1)获取所有beanName,这一步骤中所有在beanFacotry中注册的Bean都会被提取出来。

(2)遍历所有beanName.并找出声明AspectJ注饵的类,进行进一步的处理。

(3)对标记为AspectJ注解的类进行增强器的提取。

(4)将提取结果加入缓存。

2.2 创建代理

在获取了所有对应Bean的增强器后,便可以进行代理的创建了。

对于代理类的创建及处理.Spring委托给了ProxyFactory去处理,而在此函致中主要是对ProxyFactory的初始化操作,进而对真正的创建代理做准备,这些初始化操作包括如下内容。

(1)获取当前类中的属性。

(2)添加代理接口。

(3)封装Advisor并加入到ProxyFactory申。

(4)设置要代理的类

(5)当然在Spring中还为子类提供了定制的函数customizeProxyFactory,子类可以在此函数中进行对ProxyFactory的进一步封装。

(6)进行获取代理操作。

其中,封装Advisor并加入到ProxyFactory中以及创建代理是两个相对繁琐的过程,可以通过ProxyFactory提供的addAdvisor方法直接将增强器置入代理创建工厂中,但是将拦截器封装为增强器还是需要一定的逻辑的。

下面是对JDK与Cglib方式的总结。

    如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP.

    如果目标对象实现了接口,可以强制使用CGLIB实现AOP.

    如果目标对象没有实现了接口,必须采用CGLIB库,Spring会白动在JDK动态代理和CGLIB之间转换.

    如何强制使用CGLIB实现AOP?

    (1)添加CGLIB库,Spring_HOME/cglib/*.jar。

    (2)在Sprin8配置文件中加入<aop:aspectj-autoproxy proxy-target-class=”true”/>

    JDK动态代理和CGLIB字节码生成的区别?

1 JDK动态代理只能对实现了接口的类生成代理,而不能针对类.CGLIB是计时类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以该类或方法最好不要声明成final.

2.JDK和Cglib都是在运行期生成字节码,JDK是直接写Class字节码,Cglib使用ASM框架写Class字节码,Cglib代理实现更复杂,生成代理类比JDK效率低(效率问题不确定)。

3.JDK调用代理方法,是通过反射机制调用,Cglib是通过FastClass机制直接调用方法,Cglib执行效率更高。(Cglib动态代理执行代理方法效率之所以比JDK的高是因为Cglib采用了FastClass机制,它的原理简单来说就是:为代理类和被代理类各生成一个Class,这个Class会为代理类或被代理类的方法分配一个index(int类型)。这个index当做一个入参,FastClass就可以直接定位要调用的方法直接进行调用,这样省去了反射调用,所以调用效率比JDK动态代理通过反射调用高。)

CGLIB方式实现代理的intercept方法与JDK方式实现代理的invoke方法大同小异,都是首先构造链,然后封装此链进行串联调用。

2.3 JDK方式动态代理示例

创建业务接口,业务对外提供的接口,包含业务可以对外提供的功能

public interface UserService {
    public abstract void add();
}

创建业务接口实现类

public class UserServiceImpl implements UserService{
    public void add(){
        System.out.println("---------------------------add----------------");
    }
}

创建自定义的InvacationHandler,用于对接口提供的方法进行增强

public class MyInvocationHandler implements InvocationHandler {
        //目标对象
        private Object target;
        //构造方法
        public MyInvocationHandler(Object target) {
            super();
            this.target = target;
    }
    //执行目标对象方法
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
        System.out.println("--------------before--------------");
        Object result=method.invoke(target,args);
        System.out.println("--------------after--------------");
        return result;
    }
    //获取目标对象的代理对象
    public Object getProxy(){
        return     Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),target.getClass().getInterfaces(),this);
    }
}

2.4 CGLIB方式动态代理示例

CGLIB方式实现动态代理,示例如下:

public class EnhancerDemo {
    public static  void main(String args[]) {
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\xml_data");
        Enhancer enhancer=new Enhancer();
        enhancer.setSuperclass(EnhancerDemo.class);
        enhancer.setCallback(new MethodInterceptorImpl());

        EnhancerDemo demo=(EnhancerDemo) enhancer.create();
        demo.test();
    }
    public void test(){
        System.out.println("EnhancerDemo test");
        //System.out.println(this);
    }
    private static class MethodInterceptorImpl implements MethodInterceptor {
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable{
            System.out.println("Before invoke "+method);
            Object result=proxy.invokeSuper(obj,args);
            System.out.println("After invoke "+method);
            return  result;
        }
    }
}

二 静态AOP

如果想从动态代理的方式改成静态代理的方式需要做如下改动:

(1)Spring全局配置文件的修改,加入LWT开关。

<context:load-time-weaver/>

(2)加入aop.xml

<aspectj>

      <weaver>

          <include within="test.*">

      </weaver>

      <aspects>

          <aspect name="test.AspectJTest"/>

      </aspects>

  </aspectj>

(3)加入启动参数。如果是在Eclipse中启动的话需要加上启动参数。

AOP的静态代理主要是在虚拟机启动时通过改变目标对象字节码的方式来完成对日标对象的增强,它与动态代理相比具有更高的效率,因为在动态代理实现的过程中,还需要一个动态创建代理类并代理目标对象的步骤,而静态代理则是在启动时便完成了字节码增强,当系统再次调用同标类时与调用正常的类并无差别,所以在效率上会相对高些。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值