AOP 理解

先说问题:

  • * Spring AOP用的是哪种设计模式?
  • * 谈谈你对代理模式的理解?
  • * 静态代理和动态代理有什么区别?
  • * 如何实现动态代理?
  • * Spring AOP中用的是哪种代理技术?

1. 什么是 Spring AOP?

简单的说面向切面编程 ,这句话 可以说毫无意义。

AOP 实现原理就是代理模式。

简单说下代理:

明星都有经纪人,明星最重要的一件事就是唱歌,拍电影,参加综艺,其他事他不用关注,比如唱歌前可能需要和其他人谈合作,还要布置场地,唱歌后还要收钱等等,这些统统交给他对应的经纪人去做。每个人各司其职,灵活组合,达到一种可配置的、可插拔的程序结构。 AOP实现了代理模式。

什么是代理模式?

代理模式的核心是通过代理,控制对象的访问。

设计思路,定义一个抽象类,代理类和真实类都去实现它。

真实类:实现抽类,定义真实类所要实现的业务逻辑,供代理类调用。它只关注真正的业务逻辑,比如歌星唱歌。

代理类:实现抽象类,是真实类的代理,通过真实类的业务逻辑方法来实现抽象方法,并在前后可以附加自己的操作,比如谈合同,布置场地,收钱等等。

代理模式分为静态代理和动态代理。静态代理是我们自己创建一个代理类,而动态代理是程序自动帮我们生成一个代理,我们就不用管了。下面我详细介绍一下这两种代理模式。

3. 静态代理模式

静态代理是采用 代理类和实现类实现接口 的设计思路,静态代理就是事先我们创建好一个代理类。

/**
* 明星接口类
* @date 2018-12-07
*/
public interface Star {

   /**
    * 唱歌方法
    */
   void sing();
}

代理类实现接口

public class ProxyStar implements Star {

    /**
     * 接收真实的明星对象
     */
    private Star star;

    /**
     * 通过构造方法传进来真实的明星对象
     * @param star star
     */
    public ProxyStar(Star star) {
        this.star = star;
    }

    @Override
    public void sing() {
        System.out.println("代理先进行谈判……");
        // 唱歌只能明星自己唱
        this.star.sing();
        System.out.println("演出完代理去收钱……");
    }

 }

真实类实现接口

public class RealStar implements Star {

    @Override
    public void sing() {
        System.out.println("明星本人开始唱歌……");
    }
 }

测试静态代理

public class Client {
    /**
     * 测试静态代理结果
     * @param args args
     */
    public static void main(String[] args) {
        Star realStar = new RealStar();
        Star proxy = new ProxyStar(realStar);

        proxy.sing();
    }
 }

4. JDK 动态代理

动态代理是动态生成代理,JDK动态代理是采用 反射机制,动态生成代理类

public class JdkProxyHandler {
    /**
     * 用来接收真实明星对象
     */
    private Object realStar;

    /**
     * 通过构造方法传进来真实的明星对象
     *
     * @param star star
     */
    public JdkProxyHandler(Star star) {
        super();
        this.realStar = star;
    }

    /**
     * 给真实对象生成一个代理对象实例 Proxy.newProxyInstance()
     * 方法,该方法接收三个参数:第一个参数指定当前目标对象使用的类加载器
     * ,获取加载器的方法是固定的;
     * 第二个参数指定目标对象实现的接口的类型;
     * 第三个参数指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法。
     * @return Object
     */
    public Object getProxyInstance() {
        return Proxy.newProxyInstance(realStar.getClass().getClassLoader(), realStar.getClass().getInterfaces(), (proxy, method, args) -> {
            System.out.println("代理先进行谈判……");
            // 唱歌需要明星自己来唱
            Object object = method.invoke(realStar, args);
            System.out.println("演出完代理去收钱……");

            return object;
            });
    }
}

roxy.newProxyInstance() 方法,该方法接收三个参数:第一个参数指定当前目标对象使用的类加载器,获取加载器的方法是固定的;第二个参数指定目标对象实现的接口的类型;第三个参数指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法。

测试JDK 动态代理

public class Client {

   /**
    * 测试JDK动态代理结果
    * @param args args
    */
   public static void main(String[] args) {
       Star realStar = new RealStar();
       // 创建一个代理对象实例
       Star proxy = (Star) new JdkProxyHandler(realStar).getProxyInstance();

       proxy.sing();
   }
}

JDK 动态代理做一个简单的总结:相对于静态代理,JDK 动态代理大大减少了我们的开发任务,同时减少了代理类对业务接口的依赖,降低了耦合度。JDK 动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler 生成代理。但是 JDK 动态代理有个缺憾,或者说特点:JDK 实现动态代理需要实现类通过接口定义业务方法

5. CGLIB 动态代理

JDK 实现动态代理需要实现类通过接口定义业务方法,那对于没有接口的类,如何实现动态代理呢,这就需要 CGLIB 了。

CGLIB 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理

CGLIBHandler生成代理

public class CglibProxyHandler implements MethodInterceptor {

    /**
     * 维护目标对象
     */
    private Object target;

    public Object getProxyInstance(final Object target) {
        this.target = target;
        // Enhancer类是CGLIB中的一个字节码增强器,它可以方便的对你想要处理的类进行扩展
        Enhancer enhancer = new Enhancer();
        // 将被代理的对象设置成父类
        enhancer.setSuperclass(this.target.getClass());
        // 回调方法,设置拦截器
        enhancer.setCallback(this);
        // 动态创建一个代理类
        return enhancer.create();
    }

    @Override
    public Object intercept(Object object, Method method, Object[] args,
            MethodProxy methodProxy) throws Throwable {

        System.out.println("代理先进行谈判……");
        // 唱歌需要明星自己来唱
        Object result = methodProxy.invokeSuper(object, args);
        System.out.println("演出完代理去收钱……");
        return result;
    }
 }

使用CGLIB 需要实现 MethodInterceptor 接口,并重写intercept 方法,在该方法中对原始要执行的方法前后做增强处理,该类的代理对象可以使用代码中的字节码增强器来获取

测试CGLib动态代理

public class Client {

    /**
     * 测试Cglib动态代理结果
     * @param args args
     */
    public static void main(String[] args) {
        Star realStar = new RealStar();
        Star proxy = (Star) new CglibProxyHandler().getProxyInstance(realStar);

        proxy.sing();
    }
 }

AOP总结

CGLIB 创建的动态代理对象比 JDK 创建的动态代理对象的性能更高,但是 CGLIB 创建代理对象时所花费的时间却比 JDK 多得多。所以对于单例的对象,因为无需频繁创建对象,用 CGLIB 合适,反之使用JDK方式要更为合适一些。同时由于 CGLIB 由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。

6. Spring AOP 采用哪种代理?

看源码

 * Copyright 2002-2015 the original author or authors.

package org.springframework.aop.framework;

import java.io.Serializable;
import java.lang.reflect.Proxy;

import org.springframework.aop.SpringProxy;

/**
 * Default {@link AopProxyFactory} implementation, creating either a CGLIB proxy
 * or a JDK dynamic proxy.
 *
 * <p>Creates a CGLIB proxy if one the following is true for a given
 * {@link AdvisedSupport} instance:
 * <ul>
 * <li>the {@code optimize} flag is set
 * <li>the {@code proxyTargetClass} flag is set
 * <li>no proxy interfaces have been specified
 * </ul>
 *
 * <p>In general, specify {@code proxyTargetClass} to enforce a CGLIB proxy,
 * or specify one or more interfaces to use a JDK dynamic proxy.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @since 12.03.2004
 * @see AdvisedSupport#setOptimize
 * @see AdvisedSupport#setProxyTargetClass
 * @see AdvisedSupport#setInterfaces
 */
@SuppressWarnings("serial")
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

    @Override
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
            Class<?> targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: " +
                        "Either an interface or a target is required for proxy creation.");
            }
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                return new JdkDynamicAopProxy(config);
            }
            return new ObjenesisCglibAopProxy(config);
        }
        else {
            return new JdkDynamicAopProxy(config);
        }
    }

    /**
     * Determine whether the supplied {@link AdvisedSupport} has only the
     * {@link org.springframework.aop.SpringProxy} interface specified
     * (or no proxy interfaces specified at all).
     */
    private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
        Class<?>[] ifcs = config.getProxiedInterfaces();
        return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
    }

}

是否使用 CGLIB 是在代码中进行判断的:

判断条件是 config.isOptimize()、config.isProxyTargetClass() 和 hasNoUserSuppliedProxyInterfaces(config)。

config.isOptimize() 与 config.isProxyTargetClass()默认返回都是 false

hasNoUserSuppliedProxyInterfaces(config)的结果决定了。

hasNoUserSuppliedProxyInterfaces(config) 就是在判断代理的对象是否有实现接口,有实现接口的话直接走 JDK 分支,即使用 JDK 的动态代理。

所以基本上可以总结出 Spring AOP 中的代理使用逻辑了:如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理实现 AOP;如果目标对象没有实现了接口,则采用 CGLIB 库,Spring 会自动在 JDK 动态代理和 CGLIB 动态代理之间转换。

本文分享自微信公众号 - 程序员开发者社区(gh_016ffe40d550),作者:王小明

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-12-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 如何动态生成一个Java类

    开发者编写Java 代码,调用 javac 编译生成 class 文件。然后通过类加载机制载入 JVM ,即成为了应用可以使用的 Java 类。

    王小明_HIT
  • 谈谈反射机制,动态代理基于什么原理

    反射机制是Java语言提供的一种基础功能,赋予程序在运行时自省(introspect,官方用语)的能力。通过反射我们可以直接操作类或者对象,比如获取某个对象的类...

    王小明_HIT
  • synchronized 和 ReentrantLock 有什么区别?

    Synchronized 是 Java 内建的同步机制,所以也有人称其为 Intrinsic Locking,它提供了互斥的语义和可见性,当一个线程已经获取...

    王小明_HIT
  • 求求你,下次面试别再问我什么是 Spring AOP 和代理了!

    我们知道,Spring 中 AOP 是一大核心技术,也是面试中经常会被问到的问题,最近我在网上也看到很多面试题,其中和 Spring AOP 相关的就有不少,这...

    Java团长
  • Java 动态代理详解

    动态代理在Java中有着广泛的应用,比如Spring AOP、Hibernate数据查询、测试框架的后端mock、RPC远程调用、Java注解对象获取、日志、用...

    小旋锋
  • Java高级编程细节-动态代理-进阶高级开发必学技能

    对于调用者来说,需要把构造好的实例传给代理,然后就可以用代理来替代操作真正的实例了。

    慕容千语
  • 轻松学,Java 中的代理模式及动态代理【面试+工作】

    代理是英文 Proxy 翻译过来的。我们在生活中见到过的代理,大概最常见的就是朋友圈中卖面膜的同学了。

    Java帮帮
  • 【Java入门提高篇】Day12 Java代理——Cglib动态代理

      今天来介绍另一种更为强大的代理——Cglib动态代理。   什么是Cglib动态代理?   我们先回顾一下上一篇的jdk动态代理,jdk动态代理是通过接口来...

    弗兰克的猫
  • 常用设计模式——代理模式

    使用代理模式创建代理对象,让代理对象控制目标对象的访问,目标对象可以是远程对象、创建开销大的对象、需要安全控制的对象。

    用户5325874
  • Spring知识点(五)代理模式

    使用代理模式的目的是为了将原来类生成一个代理类,由代理类来执行原来类的一些增强方法,但是也不影响原来类中方法的执行。

    虞大大

扫码关注云+社区

领取腾讯云代金券