[Spring]AOP之JDK实现动态代理以及Cglib实现动态代理

目录

前言:

1. AOP

1.1 AOP相关概念

1.2 静态AOP与动态AOP

1.3 JDK的动态代理?

1.4 基于Cglib的动态代理?

1.5 Spring AOP原理为什么用2种实现方式?JDKProxy和Cglib?

1.6 spring aop 注意事项

1.7 spring在做AOP时候选择哪种?

1.8 DefaultAopProxyFactory 与 CglibAopProxy 与 JdkDynamicAopProxy

1.9 AOP如何判断使用哪种代理方式(源码)


前言:

提起AOP,那么更多会提到切面注解和设计模式(代理模式)

Spring AOP用的是哪种设计模式?

谈谈你对代理模式的理解?

静态代理和动态代理有什么区别?

如何实现动态代理?

Spring AOP中用的是哪种代理技术?

好吧,这些问题,逐个攻破,毕竟动态代理模式也是必问的东西。

之前写过一个:

[AOP]java自定义登录切面;

https://blog.csdn.net/pmdream/article/details/99983571

不过业务代码写的多了,对于这种切面也都是小case了,很熟悉了。

这边涉及到了用户权限/权限的问题,后续再补。

这篇就纯写对AOP的理解

1. AOP

在运行时,动态的将代码切入到类的制定方法,指定位置上面的思想就是面向切面编程。

比如在我业务中,有用到在controller 切面log,切面登录和权限

在service中也有切面动态数据源。

它可以对整个类进行切面,也可以对指定的某个接口进行切面。

AOP就是面向切面编程,是OOP(面向对象)的编程。

其实AOP也是在解耦代码,对于重复而复杂得东西提出来,只用一个注解就可以注入进去了。

与系统相关的东西,比如权限,日志,事务。

可以独立编码然后AOP注入到系统里面。

1.1 AOP相关概念

(1)Aspect :切面,切入系统的一个切面。

(2)Join point :连接点,也就是可以进行横向切入的位置;

(3) Advice :通知,切面在某个连接点执行的操作(分为: Before advice , After returning advice , After throwing advice , After (finally) advice , Around advice );

(4)Pointcut :切点,符合切点表达式的连接点,也就是真正被切入的地方;

1.2 静态AOP与动态AOP

静态AOP是指AspectJ实现的AOP,他是将切面代码直接编译到Java类文件中。

动态AOP是指将切面代码进行动态织入实现的AOP。

Spring的AOP为动态AOP,实现的技术为: JDK提供的动态代理技术 和 CGLIB(动态字节码增强技术) 。尽管实现技术不一样,但 都是基于代理模式 , 都是生成一个代理对象 。

1.3 JDK的动态代理?

package com.dk.learndemo.aop.jdk;


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author :pmdream
 * @description : MyJDKProxy
 *                 要求被代理的类必须实现一个接口,核心是InvocationHandler接口和Proxy类
 * @create : 2020/03/18
 */
public class MyJDKProxy implements InvocationHandler {

    //需要被代理的目标对象
    private Object proxyObject;

    public MyJDKProxy() {
    }

    public MyJDKProxy(Object proxyObject) {
        this.proxyObject = proxyObject;
    }

    /**
     *
     * 通过invoke来进行的动态代理
     * 三个参数分别是代理实例、调用的方法、方法的参数列表。
     * */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("JDK动态代理,监听开始!");
        Object result = method.invoke(proxyObject, args);
        System.out.println("JDK动态代理,监听结束!");
        return result;
    }

    /**
     *
     * 1. JDK动态代理只能针对实现了接口的类进行代理,newProxyInstance 函数所需参数就可看出
     *  第二个参数 是我们提供给代理对象的接口,那么我们这个代理对象就会去实现这个接口,
     *  这种情况下,我们当然可以将这个代理对象强制转化成提供的接口类型。
     * 2. 这边的This指代proxyObject,可以用myJDKProxy来代替
     * 3. Proxy.newProxyInstance创建的代理对象是在JVM运行时动态生成的一个对象,这个对象不是我们知道的任何一个对象,
     *   而是运行时动态生成的,并且命名方式都是以$Proxy这种类型的。看运行结果也看出来 $Proxy0就是实际代理类。
     *   所以才是动态代理。
     *
     * */
    private Object getProxyInstance(Object targetObject) {
        //为目标对象target赋值
        this.proxyObject = targetObject;
        MyJDKProxy myJDKProxy = new MyJDKProxy(targetObject);

        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader()
                , targetObject.getClass().getInterfaces(), this);
    }

    public static void main(String[] args) {
        //实例化JDKProxy对象
        MyJDKProxy myJDKProxy = new MyJDKProxy();
        //获取代理对象,UserServiceImpl倒不如说是代理的最终生成的目标,动态代理会获取这个类所实现的接口,然后创建一个动态的类来实现这个接口
        UserService userService = (UserService)myJDKProxy.getProxyInstance(new UserServiceImpl());
        System.out.println("下面的方法才是调用的方法,应该通过invoke来实现的,所以上面的userService是相当于一个实现了UserService接口的代理类");
        //代理类调用方法会用到invoke
        userService.delUser("hehe");
        System.out.println(userService.getClass());
    }
}
package com.dk.learndemo.aop.jdk;

/**
 * @author :pmdream
 * @description : UserService
 *                 JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口,核心是InvocationHandler接口和Proxy类
 * @create : 2020/03/18
 */
public interface UserService {
    void addUser(String userName,String password);
    String delUser(String userName);
}
package com.dk.learndemo.aop.jdk;

/**
 * @author :pmdream
 * @description : UserServiceImpl
 * @create : 2020/03/18
 */
public class UserServiceImpl implements UserService {

    //重写新增用户方法
    @Override
    public void addUser(String userName, String password) {
        System.out.println("调用了新增的方法!");
        System.out.println("传入参数为 userName: "+userName+" password: "+password);
    }
    //重写删除用户方法
    @Override
    public String delUser(String userName) {
        System.out.println("调用了删除的方法!");
        System.out.println("传入参数为 userName: "+userName);
        return "删除成功";
    }
}

1.4 基于Cglib的动态代理?

package com.dk.learndemo.aop.cglib2;


import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author :zhudakang
 * @description : CglibProxy
 * @create : 2020/03/18
 */
public class CglibProxy implements MethodInterceptor {


    private Object target;
    private Class clazz;

    /**
     * o 为代理的最终目标
     * clazz 是代理的目标的类型
     * */
    public CglibProxy(Object o, Class clazz) {
        this.target = o;
        this.clazz = clazz;
    }

    public Object getNewProxy() {
        Enhancer enhancer = new Enhancer();
        //要动态生成一个子类
        enhancer.setSuperclass(clazz);
        //通过回调指定代理类。
        enhancer.setCallback(CglibProxy.this);
        return enhancer.create();
    }

    /**
     * intercept [ˌɪntərˈsept] 拦截
     * */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("intercept方法invoke前");
        Object e = method.invoke(target, objects);
        System.out.println("intercept方法invoke后");
        return e;
    }

    public static void main(String[] args) {
        Work work = new Work();
        CglibProxy cglibProxy = new CglibProxy(work, Work.class);
        Work workProxy = (Work) cglibProxy.getNewProxy();
        workProxy.goToWork();
        workProxy.eatLunch();
        workProxy.getOffWork();
        //我们可以通过class来判断AOP是使用的哪种方式的代理。
        //class com.dk.learndemo.aop.cglib2.Work$$EnhancerByCGLIB$$b3dafa56
        System.out.println(workProxy.getClass());
    }
}
package com.dk.learndemo.aop.cglib2;

/**
 * @author :zhudakang
 * @description : DayWork
 * @create : 2020/03/18
 */
public class Work {
    public void goToWork() {
        System.out.println("上班");
    }
    public void eatLunch() {
        System.out.println("吃午饭");
    }
    public void getOffWork() {
        System.out.println("下班");
    }
}

 

1.5 Spring AOP原理为什么用2种实现方式?JDKProxy和Cglib?

当目标类为接口时用JDKProxy,否则用Cglib。为什么不直接用一种技术?

JDK动态代理只能代理接口类,所以很多人设计架构的时候会使用
XxxService, XxxServiceImpl的形式设计,一是让接口和实现分离,二是也有助于代理。

为什么不都使用Cgilb代理:?
因为JDK动态代理不依赖其他包,Cglib需要导入ASM包,对于简单的有接口的代理使用JDK动态代理可以少导入一个包。

JDKProxy只能代理接口,这种局限性导致了必须要引入Cglib

Jdk代理:基于接口的代理,一定是基于接口,会生成目标对象的接口的子对象。

Cglib代理:基于类的代理,不需要基于接口,会生成目标对象的子对象。

jdk代理对象实质上是目标对象接口的实现类

Cglib代理对象实质上是目标对象的子类

JDK的动态代理依靠接口实现,如果有些类并没有实现接口,则不能使用JDK代理。这个时候就需要使用Cglib在字节码上做代理。
其实还是JDK本身的局限性导致的。

首先并不是目标类为接口时使用JDK 反之用Cglib,确切的表述应该,如果代理类的方法不是实现接口的方法,那必须使用Cglib,否之两者都可以。

因为JDK动态代理的原理就是实现和目标对象同样的接口, 因此只能调用那些接口方法。Cglib则没有此限制,因为它所创建出来的代理对象就是目标类的子类,因此可以调用目标类的任何方法。

撸完代码之后,对于这两种其实就极为清晰了。

但是对于JDK7和JDK8,使用JDK代理并不比cglib慢多少了,已经完全追上。

1.6 spring aop 注意事项

如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP 如果目标对象实现了接口,也可以强制使用CGLIB实现AOP 如果目标对象没有实现接口,必须采用CGLIB的动态代理,spring会自动在两种模式之间转换 不管哪一种方式都不能用private和final做修饰词

1.7 spring在做AOP时候选择哪种?

(1)当Bean实现接口时,Spring就会用JDK的动态代理

(2)当Bean没有实现接口时,Spring使用CGlib是实现  

(3)可以强制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)

1.8 DefaultAopProxyFactory 与 CglibAopProxy 与 JdkDynamicAopProxy

他们的包都是org.springframework.aop.framework;
public class CglibAopProxy implements AopProxy, Serializable {}
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {}
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {}

 CglibAopProxy 和jdkDynamicAopProxy 实现了AopProxy的接口。

AOP中对这两种代理的支持都是从ProxyFactoryBean中的getObject方法开始。

    public Object getObject() throws BeansException {
        initializeAdvisorChain();
        if (isSingleton()) {
            return getSingletonInstance();
        }
        else {
            if (this.targetName == null) {
                logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +
                        "Enable prototype proxies by setting the 'targetName' property.");
            }
            return newPrototypeInstance();
        }
    }

最终是通过 调用ProxyCreatorSupport 这个类(实现AopProxyFactory接口)中的createAopProxy来创建代理类。

1.9 AOP如何判断使用哪种代理方式(源码)

 最终的createAopProxy实现类就是上面的DefaultAopProxyFactory来实现的。所以最终决定调用那种代理方式的依据是在DefaultAopProxyFactory这个类的createAopProxy方法中

 

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.aop.framework;

import java.io.Serializable;
import java.lang.reflect.Proxy;
import org.springframework.aop.SpringProxy;

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
    public DefaultAopProxyFactory() {
    }

    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
            return new JdkDynamicAopProxy(config);
        } else {
            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.");
            } else {
                return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
            }
        }
    }

    private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
        Class<?>[] ifcs = config.getProxiedInterfaces();
        return ifcs.length == 0 || ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0]);
    }
}

 

参考:

https://blog.csdn.net/weixin_38362455/article/details/91055939

https://blog.csdn.net/bicheng4769/article/details/80030266
https://www.cnblogs.com/bigmonkeys/p/7823268.html

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值