专栏首页专注于主流技术和业务SpringBoot实践-BeanPostProcessor的作用和妙用

SpringBoot实践-BeanPostProcessor的作用和妙用

BeanPostProcessor的用法

BeanPostProcessor也称为Bean后置处理器,它是Spring中定义的接口,在Spring容器的创建过程中(具体为Bean初始化前后)会回调BeanPostProcessor中定义的两个方法。BeanPostProcessor的源码如下:

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;    
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

其中postProcessBeforeInitialization方法会在每一个bean对象的初始化方法调用之前回调;postProcessAfterInitialization方法会在每个bean对象的初始化方法调用之后被回调。

提出的问题

在Spring开发过程中,存在同一个接口有多个实现类的情况,根据不同的应用场景,通常在具体调用的地方来选择不同的接口实现类,虽然可以在抽象出一个工厂方法,但是还是感觉不够优雅。如果通过注解的方式解决这个问题呢,Spring的BeanPostProcessor是个好的选择。

具体实现

声明接口

public interface HelloService{
    public void sayHello();
}

接口实现类1

@Service
public class HelloServiceImpl1 implements HelloService {
    @Override
    public void sayHello() {
        System.out.println("你好我是HelloServiceImpl1");
    }
}

接口实现类2

@Service
public class HelloServiceImpl2 implements HelloService {
    @Override
    public void sayHello() {
        System.out.println("你好我是HelloServiceImpl2");
    }
}

自定义注解

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface RountingInjected {
    String value() default "helloServiceImpl1";
}

自定义的BeanPostProcessor类

@Component
public class HelloServiceInjectProcessor implements BeanPostProcessor {

    @Autowired
    private ApplicationContext applicationContext;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Class<?> targetCls = bean.getClass();
        Field[] targetFld = targetCls.getDeclaredFields();
        for (Field field : targetFld) {
            //找到制定目标的注解类
            if (field.isAnnotationPresent(RountingInjected.class)) {
                if (!field.getType().isInterface()) {
                    throw new BeanCreationException("RoutingInjected field must be declared as an interface:" + field.getName()
                            + " @Class " + targetCls.getName());
                }
                try {
                    this.handleRoutingInjected(field, bean, field.getType());
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        return bean;
    }

    /**
     * @param field
     * @param bean
     * @param type
     * @throws IllegalAccessException
     */
    private void handleRoutingInjected(Field field, Object bean, Class type) throws IllegalAccessException {
        Map<String, Object> candidates = this.applicationContext.getBeansOfType(type);
        field.setAccessible(true);
        if (candidates.size() == 1) {
            field.set(bean, candidates.values().iterator().next());
        } else if (candidates.size() == 2) {
            String injectVal = field.getAnnotation(RountingInjected.class).value();
            Object proxy = RoutingBeanProxyFactory.createProxy(injectVal, type, candidates);
            field.set(bean, proxy);
        } else {
            throw new IllegalArgumentException("Find more than 2 beans for type: " + type);
        }
    }
}

代理实现类

public class RoutingBeanProxyFactory {

    private final static String DEFAULT_BEAN_NAME = "helloServiceImpl1";

    public static Object createProxy(String name, Class type, Map<String, Object> candidates) {
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setInterfaces(type);
        proxyFactory.addAdvice(new VersionRoutingMethodInterceptor(name, candidates));
        return proxyFactory.getProxy();
    }

    static class VersionRoutingMethodInterceptor implements MethodInterceptor {
        private Object targetObject;

        public VersionRoutingMethodInterceptor(String name, Map<String, Object> beans) {
            this.targetObject = beans.get(name);
            if (this.targetObject == null) {
                this.targetObject = beans.get(DEFAULT_BEAN_NAME);
            }
        }

        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            return invocation.getMethod().invoke(this.targetObject, invocation.getArguments());
        }
    }
}

SpringBoot入口类

@SpringBootApplication
@MapperScan("com.lx.mapper")
public class MlxcApplication {
    public static void main(final String[] args) {
        try (ConfigurableApplicationContext applicationContext = SpringApplication.run(MlxcApplication.class, args)) {
            HelloServiceTest helloService = applicationContext.getBean(HelloServiceTest.class);
            helloService.testSayHello();
        }
    }
}

总结

上述是整个解决方案的示例流程,其核心思想就是根据自定义注解拦截要注入的接口实现类,运用java反射和代理的知识点来进行有效的实现类注入。

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

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Linux上搭建Git服务,客户端在Windows

    服务器 CentOS Linux release 7.4.1708 (Core) + git(version 1.8.3.1) 客户端 Windows7 + ...

    飞狗
  • 使用Swagger2Markup实现导出API文档

    在学会了如何使用Swagger之后,我们已经能够轻松地为Spring MVC或SpringBoot的Web项目自动构建出API文档了。但是,构建的文档必须通过在...

    飞狗
  • Remix在CentOS7上的安装和问题解决

    ERR! stack Error: Can't find Python executable "python",

    飞狗
  • Git与SVN的区别

    鉴于最近某些公司,某些人用着git做着svn的模式,觉得有意思,就随便找了篇帖子拿出来

    用户3765803
  • “犯罪心理”解读 Mybatis 拦截器

    Mybatis拦截器执行过程解析 文章写过之后,我觉得 “Mybatis 拦截器案件”背后一定还隐藏着某种设计动机,里面大量的使用了 Java 动态代理手段,它...

    用户4172423
  • MongoDB查询文档踩坑记!

    我们如果需要查询同时满足两个以上条件,需要使用$and操作符将条件进行关联 (相当于SQL的and).

    JavaEdge
  • Guava-1.18类Preconditions

    com.google.common.base ** Preconditions**

    悠扬前奏
  • libev源码解析——监视器(watcher)结构和组织形式

            在《libev源码解析——总览》中,我们介绍了libev的一些重要变量在不同编译参数下的定义位置。由于这些变量在多线程下没有同步问题,所以我们将...

    方亮
  • 什么是策略模式

    策略模式应该是Java设计模式中最简单的一种模式, 它的核心思想是,一个类的行为可以在运行时动态改变,有不同的实现逻辑。

    PhoenixZheng
  • 数据结构|用java自己手写实现一个栈

    本网站记录了最全的各种JavaDEMO ,保证下载,复制就是可用的,包括基础的, 集合的, spring的, Mybatis的等等各种,助力你从菜鸟到大牛,记得...

    微笑的小小刀

扫码关注云+社区

领取腾讯云代金券