spring-boot集成动态代理

spring-boot集成动态代理

代理模式

​ 代理是什么概念呢,你可以理解就是某东和厂家的概念(你在某东买东西,不用和厂家对接,然后买到你想要的东西,此次某东还给你提供一些额外的服务),代理的概念差不多就这样吧,在不改变原有流程的基础上,对其增加做一些额外操作.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

​ 然后代理又区分动态代理与静态代理,咋个区分呢?也很简单,就是判断你这些额外操作是编译期确定的,还是运行期间确定的,如果是编译器就确认了则为静态代理,反之为动态代理.

试验准备

​ 后面用代码讲一下动态代理.此次以JDK代理讲解,不讲CGLIB代理(2个区别就是前者需要被代理对象实现接口,后者则可以直接代理类).

​ 使用PhoneVendor表示手机的直接生产与贩卖对象,使用BusinessAgent表示代理商,抽象一个卖东西的接口Sell,无论是委托对象,还是代理商都实现这个接口,去卖东西.

​ 以下类与接口只是随便写的,只是为了讲一下动态代理的逻辑,就别在意设计合不合理了 ,哈哈哈哈.

Sell

 /**
     * 获取商品对应的贩卖价格
     * @param product 购买的产品名称
     * @return 商品价格
     */
    double price(String product);

PhoneVendor

public class PhoneVendor implements  Sell{
    /**
     * 不同手机贩卖价格
     */
    private static Map<String,Double> phonePriceMap;
    static {
        phonePriceMap=new HashMap<>();
        phonePriceMap.put("小米10",2900d);
        phonePriceMap.put("小米11",4300d);
        phonePriceMap.put("红米K40",2300d);
    }
    @Override
    public Double price(String phone) {
        Double price= phonePriceMap.get(phone);
        System.out.println(phone+" 出厂价:"+price);
        return price;
    }
}

静态代理

​ 静态代理就是和被代理对象PhoneVendor实现Sell接口,然后使用代理对象去执行price方法,最终还是使用被委托对象去执行方法,只不过在执行前后做了一些额外操作.

public class BusinessAgent implements Sell {
    /**
     * 被委托代理的对象
     */
    private Sell vendor;

    public BusinessAgent(Sell vendor) {
        this.vendor = vendor;
    }

    @Override
    public Double price(String product) {
        double price = vendor.price(product);
        price=1.3*price;
        return price;
    }

    public static void main(String[] args) {
        BusinessAgent agent=new BusinessAgent(new PhoneVendor());
        double price = agent.price("小米10");
        System.out.println("小米10 零售价:"+price);
    }
}

上述代码执行结果集:

小米10 出厂价:2900.0
小米10 零售价:3770.0

动态代理

​ 动态代理的概念就是基于委托类,生成一个代理类,在用户感知不到的情况下,调用原有方法,去做一些额外的操作,而这些额外操作是在运行期间生成的,而不是在编译期.

InvocationHandler接口

凡是实现这个接口的类,在调用这个代理类的时候,必会执行invoke方法.

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

参数说明:

  • proxy - 代理类对象。
  • method - 代理对象所调用的方法对象
  • args - 代理对象调用方法中的参数对象

创建动态代理

​ 这里只是把上面的静态代理转换为动态代理的这种写法,正常写动态代理模式除非你确认你的所有代理对象都只用于同一种模式,要不然不建议写死,根据策略模式去编写,这里只是强行解释一下动态代理的编写方式.

    @Test
    public void  dynamicProxyTest(){
        //创建被代理类
        PhoneVendor target=new PhoneVendor();
         /* 
         	newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
            ClassLoader loader : 类加载器,用于加载代理类。可以通过目标对象获取类加载器
            Class<?>[] interfaces : 代理类实现的接口的字节码对象
            InvocationHandler h : 代理对象的调用处理程序
         */
        //创建代理对象
        Sell proxyInstance = (Sell)Proxy.newProxyInstance(Sell.class.getClassLoader(),
                new Class[]{Sell.class}, ((proxy, method, args) -> {
                    Double price = (Double) method.invoke(target,args);
                    return price * 1.3;
                }));
        //使用代理对象执行
        Double price = proxyInstance.price("小米10");
        System.out.println("小米10 零售价:"+price);
    }

上述代码执行结果集:

小米10 出厂价:2900.0
小米10 零售价:3770.0

​ 其实看到这里,很多人就会有疑问(没错,我就是那个很多人,哈哈哈),这个玩意相对于静态代理,区别无非就是不用创建代理对象类了嘛,但还是得手动去new一个代理对象出来,感觉没啥实质性的作用.

​ 我们写接口的最终目的其实就是想做到:继承与多态,那么有没有一种方式,可以做到我抽象一个公有的接口,代码根据我们的入参帮我们处理一些公有逻辑,并且根据传入的参数帮我自动去选择具体的实现类,从而达到真正的多态,实现new对象的解耦?

将钱装进口袋里

​ 哈哈哈,是把动态代理对象装进IOC容器,我们在使用实现类的时候,直接使用这个动态代理对象,根据invoke方法去挑选具体的实现类.

下面用卖进口奶粉和国产奶粉的例子给大家讲一下如何把动态代理类放进IOC容器,以及如何凭借动态代理实现继承与多态(根据入参,选择具体实现类)

构建业务实体

  • 抽象一个接口:IProviderIdentification,用于在多态挑选具体实现类的时候,根据这个标识挑选

    public interface  IProviderIdentification{
    
        /**
         * 定义一个type类型,后续根据type指定具体实现类
         *
         * @return 类型
         */
        Set<String> type();
    
    }
    
  • 提供一个元类,所有动态代理方法入参对象继承它,后续用这个type去获取指定的实现类

    /**
     * 元类 用于动态代理的时
     * 根据元类中字段挑选具体实现类
     */
    public class MetadataDo {
        /**
         * 该字段与IServiceProviderIdentification#type
         * 进行搭配使用,从而挑选出具体的实现类
         */
        private String type;
    }
    
  • 抽象一个奶粉经销商接口:IDryMilkDealer,后续奶粉经销商去实现它

    /**
     * 奶粉经销商
     */
    public interface IDryMilkDealer extends  IServiceProviderIdentification  {
        /**
         * 返回具体奶粉产品
         * @param param 制造奶粉的参数
         * @return 奶粉产品
         */
        MilkPowderDo getMilkPowder(MilkPowderParam param);
    }
    
    
  • 提供一个国产奶粉经销商类: DomesticMilkPowderDealer,它只卖国产奶粉

    @Service
    public class DomesticMilkPowderDealer implements IDryMilkDealer {
        /**
         * 这里模拟一个数据库存储
         * key为产品名称,value为具体产品信息
         */
        private static Map<String, MilkPowderDo> MilkPowderMap;
    
        static {
            MilkPowderMap = new HashMap<>(2);
            MilkPowderMap.put("梦牛", new MilkPowderDo("呼和浩特", "梦牛", "A奶粉"));
            MilkPowderMap.put("经典", new MilkPowderDo("包头市", "经典", "A奶粉"));
        }
    
        @Override
        public MilkPowderDo getMilkPowder(MilkPowderParam param) {
            //以上做一些定制化处理
            // ……
            ///以上做一些定制化处理
            return MilkPowderMap.get(param.getBrand());
        }
    
        @Override
        public Set<String> type() {
            return Sets.newHashSet(ServiceTypeEnum.DOMESTIC.getType());
        }
    }
    
  • 提供一个进口奶粉经销商类: DomesticMilkPowderDealer,它只卖进口奶粉

    @Service
    public class ImportMilkPowderDealer implements IDryMilkDealer {
        /**
         * 这里模拟一个数据库存储
         * key为产品名称,value为具体产品信息
         */
        private static Map<String,MilkPowderDo> MilkPowderMap;
        static {
            MilkPowderMap=new HashMap<>(2);
            MilkPowderMap.put("梦牛",new MilkPowderDo("新西兰","梦牛","B奶粉"));
            MilkPowderMap.put("经典",new MilkPowderDo("美国","经典","B奶粉"));
        }
        @Override
        public MilkPowderDo getMilkPowder(MilkPowderParam param) {
            //以上做一些定制化处理
            // ……
            ///以上做一些定制化处理
            return MilkPowderMap.get(param.getBrand());
        }
    
        @Override
        public Set<String> type() {
            return Sets.newHashSet(ServiceTypeEnum.IMPORT.getType());
        }
    }
    
  • 奶粉实体对象

    @Data
    @ToString
    @AllArgsConstructor
    public class MilkPowderDo {
        /**
         * 奶粉原产地
         */
        private String placeOfOrigin;
        /**
         * 品牌
         */
        private String brand;
        /**
         * 商品名称
         */
        private  String goods;
    }
    
  • 被代理类方法请求参数

    @Data
    public class MilkPowderParam extends MetadataDo implements Serializable {
    
        /**
         * 奶粉品牌
         */
        private String brand;
    
    }
    

构建动态代理对象

​ 直接实现InvocationHandler接口,构建一个动态代理对象

import com.study.springbootcode.model.MetadataDo;
import com.study.springbootcode.proxy.IProviderIdentification;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;

/**
 * 在这个类里去实现根据特定标识,选择具体的实现类
 */
public class ServiceDynamicProxy implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //获取入参中的第一个参数,强转为MetadataDo 因此后续实现的接口方法,需要将MetadataDo放到第一个参数位置
        MetadataDo metaData = (MetadataDo) args[0];
        //从IOC里获取实现了method.getDeclaringClass()这个接口的所有bean对象
        Map<String, ?> beanMap = SpringApplicationContext.getApplicationContext().getBeansOfType(method.getDeclaringClass());
        IProviderIdentification target=null;
        for (Map.Entry<String, ?> entry : beanMap.entrySet()) {
            //过滤掉代理类
            if (entry.getValue() instanceof Proxy) {
                continue;
            }
            IProviderIdentification instanceBean = (IProviderIdentification) entry.getValue();
            //根据type找到具体的实现类  从而达到解耦,实现真正意义上的多态,不需要认为的去new具体的实现类
            if (instanceBean.type().contains(metaData.getType())) {
                target = instanceBean;
                break;
            }
        }
        //TODO 这里应该要做一些异常机制:如通过type没有获取到实现类,主动抛个异常或者提供默认实现,但由于不是此次重点,就忽略
        //执行方法
        return method.invoke(target,args);
    }
}

将动态代理对象放进IOC

​ 这里要提到三个核心的东西:FactoryBeanClassPathBeanDefinitionScannerImportBeanDefinitionRegistrar,大概流程就是:继承和实现ClassPathBeanDefinitionScannerImportBeanDefinitionRegistrar通过自定义类扫描器完成类的扫描工作,利用FactoryBean去将我们想注入的对象更改,放到IOC里去.

  • FactoryBean [https://juejin.cn/post/6844903954615107597]

    FactoryBean的特殊之处在于它可以向容器中注册两个Bean,一个是它本身,一个是FactoryBean.getObject()方法返回值所代表的Bean。先通过如下示例代码来感受下FactoryBean的用处吧。

    public class ServiceFactoryBean<T> implements FactoryBean<T> {
    
        private Class<T> serviceInterface;
        /**
         * Create a new {@code DependencyServiceFactoryBean} for the given serviceInterface.
         * @param serviceInterface 服务接口Class
         */
        public ServiceFactoryBean(Class<T> serviceInterface) {
            this.serviceInterface = serviceInterface;
        }
    
        @Override
        @SuppressWarnings("unchecked")
        public T getObject()  {
            //将传入的serviceInterface生成一个动态代理对象
            return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(), new Class[]{serviceInterface}, new ServiceDynamicProxy());
        }
    
        @Override
        public Class<?> getObjectType() {
            return serviceInterface;
        }
    
        @Override
        public boolean isSingleton() {
      		//true 表示生成单例对象
            return true;
        }
    }
    
  • 定义一个扫描包路径的注解DependencyServiceScan

    这个接口只是用来控制要扫描哪些包路径,后续可以把这个抽成一个模块,这个模块单独做动态代理

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Import(value = {ServiceScannerRegistrar.class})
    public @interface DependencyServiceScan {
    
        /**
         * 声明扫描包的路径
         * @return 扫描包路径
         */
        String[] basePackages() default {};
    }
    
  • ClassPathBeanDefinitionScanner [https://juejin.cn/post/6869407787760222221]

    继承这个类,相对于自定义一个bean扫描程序,将需要扫描的类通过该this.getRegistry().registerBeanDefinition(String var1, BeanDefinition var2)注入 BeanFactory or ApplicationContext

    @Slf4j
    public class ClassPathServiceScanner  extends ClassPathBeanDefinitionScanner {
    
        public ClassPathServiceScanner(BeanDefinitionRegistry registry) {
            super(registry);
        }
        @Override
        protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
            return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
        }
        @Override
        public Set<BeanDefinitionHolder> doScan(String... basePackages) {
            //捞出这个basePackages路径下所有接口bean,所以你如果要让这个接口增强变为代理类,就需要给接口添加			//@Component
            Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
    
            if (beanDefinitions.isEmpty()) {
                log.warn("Bean  not found in ({}) package path ", Arrays.toString(basePackages));
            } else {
                processBeanDefinitions(beanDefinitions);
            }
    
    
            return beanDefinitions;
        }
        private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
            GenericBeanDefinition genericBeanDefinition;
            String beanName;
            for (BeanDefinitionHolder holder : beanDefinitions) {
                genericBeanDefinition = (GenericBeanDefinition) holder.getBeanDefinition();
                genericBeanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
                String beanClassName = genericBeanDefinition.getBeanClassName();
                genericBeanDefinition.setBeanClass(ServiceFactoryBean.class);
         genericBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
                //重新取个beanName  把它注册到IOC容器里去
                beanName = Introspector.decapitalize(ClassUtils.getShortName(beanClassName).substring(1));
                //key为beanName  value为genericBeanDefinition 放到容器里去
                this.getRegistry().registerBeanDefinition(beanName, genericBeanDefinition);
            }
        }
    }
    
  • ImportBeanDefinitionRegistrar

    ​ Spring官方通过ImportBeanDefinitionRegistrar实现了@Component、@Service等注解的动态注入机制。

    很多三方框架集成Spring的时候,都会通过该接口,实现扫描指定的类,然后注册到spring容器中。 比如Mybatis中的Mapper接口,都是通过该接口实现的自定义注册逻辑。

    public class ServiceScannerRegistrar implements ImportBeanDefinitionRegistrar {
        private static final String BASE_PACKAGES = "basePackages";
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            //捞出DependencyServiceScan中的值
            AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.
                    getAnnotationAttributes(DependencyServiceScan.class.getName()));
            //使用我们自定义的Scanner将我们要注册的bean放进IOC容器
            ClassPathServiceScanner classPathServiceScanner = new ClassPathServiceScanner(registry);
            List<String> basePackages = new ArrayList<>();
            //这里的操作是将DependencyServiceScan中的basePackages值取出来,获取要扫描哪些包路径
            Optional.ofNullable(annoAttrs)
                    .map(item -> item.getStringArray(BASE_PACKAGES))
                    .ifPresent(arr -> { for (String s : arr) { if (StringUtils.hasText(s)) basePackages.add(s); } });
            
            classPathServiceScanner.doScan(StringUtils.toStringArray(basePackages));
        }
    }
    

测试

@SpringBootApplication
//在springboot的启动类上 标注我们要扫描哪些包路径
@DependencyServiceScan(basePackages = {"com.study.springbootcode.proxy"})
public class SpringbootCodeApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootCodeApplication.class, args);
    }
}

定义一个ProxyController,用于测试传入不同值,使用不同的实现类

@RestController
@Slf4j
public class ProxyController {
	//这里之所以要指定dryMilkDealer  原因是上面我们在自定义扫描器里注册bean使用的beanName导致的,你们自定义啥名
    //字就需要指定啥名字,要不然不会走代理
    @Resource(name = "dryMilkDealer")
    IDryMilkDealer dealer;
    
    //这里就是传入一些参数  然后根据传入的参数  返回对应的奶粉产品,如果能够根据我们传入的不同参数返回不同的代理商,
    //就表明我们使用的动态代理生效 能根据标识,实现真正的多态,达到解耦
    @PostMapping("/testProxy")
    public ResponseEntity<MilkPowderDo> autoMountMaterial(@RequestBody MilkPowderParam params) {
        //忽略一些参数校验、接口入参日志打印
        return  ResponseEntity.ok(dealer.getMilkPowder(params));
    }
}

使用POST测试

先传type为进口,看看原产地是不是外国的,如果是,就表示调用的进口代理商实现类.

  • 接口路径: ip:port/testProxy

  • 接口方法: POST

  • 接口参数

    {
      "brand": "梦牛", 
      "type": "import"
    }
    
  • 接口返回值

    {
        "placeOfOrigin": "新西兰",
        "brand": "梦牛",
        "goods": "B奶粉"
    }
    

再type为进口,看看原产地是不是中国,如果是表示就是调用的国产代理商的实现类.

  • 接口路径: ip:port/testProxy

  • 接口方法: POST

  • 接口参数

    {
      "brand": "梦牛",
      "type": "domestic"
    }
    
  • 接口返回值

    {
        "placeOfOrigin": "呼和浩特",
        "brand": "梦牛",
        "goods": "A奶粉"
    }
    

    通过接口测试,已经得到我们想要的结果了,根据我们传入的不同type,程序可以自动帮我们调选具体的实现类,达到真正意义上的多态。

除了上述的ImportBeanDefinitionRegistrar方法,也可以显式的通过Config的方式将接口注入到spring容器中,示例如下:

@Bean
public IDryMilkDealer dryMilkDealer(){
	return new ServiceFactoryBean<>(IDryMilkDealer.class).getObject();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值