代理模式与Spring AOP动态代理源码

设计模式

java设计模式之单例模式详解(六种)
java设计模式之工厂模式讲解
java设计模式之建造者模式
java设计模式之原型模式
设计模式模版方法

介绍

  1. 代理模式就是通过代理类控制目标对象,外界通过代理类提供的接口完成对目标对象的访问。通过代理类,可以在不影响目标对象的前提下,扩展一些功能,如权限校验等。
  2. 代理模式主要分为两大类:静态代理和动态代理((JDK 代理、Cglib 代理)
  3. 代理模式是23种设计模式中的一种,属于设计模式中的结构型模式。

组成

抽象角色:通过接口或抽象类声明真实角色实现的业务方法。
代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用

静态代理

静态代理的实现需要代理类和目标对象(被代理类)实现相同的接口

甲方公司招聘一批程序员开发非核心业务,为了降低开支,通过外包公司进行招聘,外包公司就是代理角色,外包程序员就是真实角色,程序员是抽象角色

抽象角色
/**
 * @author yz
 * @version 1.0
 * @date 2020/12/2 16:28
 * 程序员(抽象角色)
 */
public interface Programmer {

    //开始编码
    public void startCoding();
}
真实角色
/**
 * @author yz
 * @version 1.0
 * @date 2020/12/2 16:32
 * 外包程序员(真实角色)
 */
public class OutsourcedProgrammer implements Programmer{
    @Override
    public void startCoding() {
        System.out.println("程序员开始编码");
    }
}
代理角色
/**
 * @author yz
 * @version 1.0
 * @date 2020/12/2 16:33
 * 外包公司(代理角色)
 */
public class OutsourcedCompanyProxy implements Programmer{

    //程序员
    private Programmer programmer;

    public OutsourcedCompanyProxy(Programmer programmer){
        this.programmer=programmer;
    }

    @Override
    public void startCoding() {
        System.out.println("招聘程序员");
        System.out.println("面试");
        System.out.println("签订外包公司劳动合同");
        programmer.startCoding();
    }
}
客户端
/**
 * @author yz
 * @version 1.0
 * @date 2020/12/2 16:36
 */
public class Client {

    public static void main(String[] args) {
        Programmer programmer=new OutsourcedProgrammer();
        OutsourcedCompanyProxy outsourcedCompanyProxy=new OutsourcedCompanyProxy(programmer);
        outsourcedCompanyProxy.startCoding();
    }
}
结果
招聘程序员
面试
签订外包公司劳动合同
程序员开始编码

从代码中可以看出,甲方公司并没有和程序员直接接触,而是通过外包公司,避免了和程序员耦合,降低了福利开支和管理成本

缺点
  1. 因为代理类和目标对象必须实现同一个接口,代理类必须和目标对象数量一致,导致代理类数量过多
  2. 因为采取了硬编码的方式,如果接口增加方法,代理类和目标对象必须进行修改

JDK代理

jdk代理是动态代理的一种,是通过反射实现的,和静态代理不同的是,代理类不再需要实现目标对象的接口

因为甲方公司需要的程序员数量过多,交给一个外包公司风险过大,而是交给了三个外包公司,这样必须创建三个代理类,难以管理。甲方公司后续希望程序员进行测试、运维等工作,就需要修改外包公司和程序员。
通过动态代理可以解决这个问题

抽象角色
/**
 * @author yz
 * @version 1.0
 * @date 2020/12/2 16:28
 * 程序员(抽象角色)
 */
public interface Programmer {

    //开始编码
    public void startCoding();
}
真实角色
/**
 * @author yz
 * @version 1.0
 * @date 2020/12/2 16:32
 * A公司外包程序员(真实角色)
 */
public class OutsourcedAProgrammer implements Programmer {
    @Override
    public void startCoding() {
        System.out.println("A公司外包程序员开始编码");
    }
}
/**
 * @author yz
 * @version 1.0
 * @date 2020/12/2 16:32
 * B公司外包程序员(真实角色)
 */
public class OutsourcedBProgrammer implements Programmer {
    @Override
    public void startCoding() {
        System.out.println("B公司外包程序员开始编码");
    }
}
/**
 * @author yz
 * @version 1.0
 * @date 2020/12/2 16:32
 * C公司外包程序员(真实角色)
 */
public class OutsourcedCProgrammer implements Programmer {
    @Override
    public void startCoding() {
        System.out.println("C公司外包程序员开始编码");
    }
}
代理角色
/**
 * @author yz
 * @version 1.0
 * @date 2020/12/2 16:33
 * 外包公司(代理角色)
 */
public class OutsourcedCompanyProxy {

    //程序员
    private Object target;

    public OutsourcedCompanyProxy(Object target){
        this.target=target;
    }


    //获取代理对象
    public Object getProxyInstance() {
        // newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
        //loader:目标类的类加载器
        //interfaces 目标类所实现的接口
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("招聘程序员");
                        System.out.println("面试");
                        System.out.println("签订外包公司劳动合同");
                        return method.invoke(target, args);
                    }
                });
    }
}
客户端
/**
 * @author yz
 * @version 1.0
 * @date 2020/12/2 15:33
 */
public class Client {


    public static void main(String[] args) {
        //A外包公司程序员
        Programmer aProgrammer = new OutsourcedAProgrammer();
        Programmer proxyInstanceA = (Programmer) new OutsourcedCompanyProxy(aProgrammer).getProxyInstance();
        proxyInstanceA.startCoding();
        //B外包公司程序员
        Programmer bProgrammer = new OutsourcedBProgrammer();
        Programmer proxyInstanceB=(Programmer)new OutsourcedCompanyProxy(bProgrammer).getProxyInstance();
        proxyInstanceB.startCoding();
        //C外包公司程序员
        Programmer cProgrammer = new OutsourcedCProgrammer();
        Programmer proxyInstanceC=(Programmer)new OutsourcedCompanyProxy(cProgrammer).getProxyInstance();
        proxyInstanceC.startCoding();
    }
}
结果
招聘程序员
面试
签订外包公司劳动合同
A公司外包程序员开始编码
招聘程序员
面试
签订外包公司劳动合同
B公司外包程序员开始编码
招聘程序员
面试
签订外包公司劳动合同
C公司外包程序员开始编码
Cglib代理
  1. jdk动态代理有一个很大的局限性,目标对象必须实现接口,但是有的目标对象并不需要一个接口,这就需要采用Cglib 代理
  2. Cglib代理-子类代理,底层是通过使用字节码处理框架 ASM 来转换字节码并生成子类对象
  3. Cglib代理通过重写intercept方法实现代理类扩展,Spring AOP就是采用Cglib实现动态代理
真实角色
/**
 * @author yz
 * @version 1.0
 * @date 2020/12/2 16:32
 * 外包程序员(真实角色)
 */
public class OutsourcedProgrammer{
    public void startCoding() {
        System.out.println("程序员开始编码");
    }
}
代理角色
/**
 * @author yz
 * @version 1.0
 * @date 2020/12/2 16:33
 * 外包公司(代理角色)
 */
public class OutsourcedCompanyProxy implements MethodInterceptor {

    //程序员
    private Object target;

    public OutsourcedCompanyProxy(Object target){
        this.target=target;
    }

    //获取代理对象
    public Object getProxyInstance() {
        //cglib增强器,用来创建代理对象
        Enhancer enhancer = new Enhancer();
        //设置需要创建的代理对象
        enhancer.setSuperclass(target.getClass());
        //设置回调,所有方法都会被intercept拦截
        enhancer.setCallback(this);
        //创建代理对象
        return enhancer.create();
    }


    //重写intercept方法
    @Override
    public Object intercept(Object arg0, Method method, Object[] args, MethodProxy arg3) throws Throwable {
        System.out.println("招聘程序员");
        System.out.println("面试");
        System.out.println("签订外包公司劳动合同");
        return method.invoke(target, args);
    }
}
客户端
/**
 * @author yz
 * @version 1.0
 * @date 2020/12/2 15:51
 */
public class Client {

    public static void main(String[] args) {
        OutsourcedProgrammer programmer = new OutsourcedProgrammer();
        OutsourcedProgrammer proxyInstance = (OutsourcedProgrammer)new OutsourcedCompanyProxy(programmer).getProxyInstance();
        proxyInstance.startCoding();
     }
}
结果
招聘程序员
面试
签订外包公司劳动合同
程序员开始编码

区别

静态代理JDK代理Cglib代理
实现方式硬编码JDK APICglib
实现接口需要需要不需要

Spring AOP动态代理应用源码

/*
 * Copyright 2002-2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

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
 * @author Sebastien Deleuze
 * @since 12.03.2004
 * @see AdvisedSupport#setOptimize
 * @see AdvisedSupport#setProxyTargetClass
 * @see AdvisedSupport#setInterfaces
 */
@SuppressWarnings("serial")
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

	/**
	 * Whether this environment lives within a native image.
	 * Exposed as a private static field rather than in a {@code NativeImageDetector.inNativeImage()} static method due to https://github.com/oracle/graal/issues/2594.
	 * @see <a href="https://github.com/oracle/graal/blob/master/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/ImageInfo.java">ImageInfo.java</a>
	 */
	private static final boolean IN_NATIVE_IMAGE = (System.getProperty("org.graalvm.nativeimage.imagecode") != null);


	@Override
	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {

		// config.isProxyTargetClass()默认值为false
		// hasNoUserSuppliedProxyInterfaces(config)判断bean是否有接口
		if (!IN_NATIVE_IMAGE &&
				(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.");
			}
			//isInterface 判断是否是一个接口
			//isProxyClass 判断是否是代理类
			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])));
	}

}

createAopProxy方法是创建目标对象的核心方法,可以看出AOP采用了JDK代理和Cglib代理两种方式,如果目标对象实现了接口,将采用JDK代理。没有实现接口,采用Cglib代理

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

回家放羊吧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值