如何修改Springboot Application的启动类注解默认值_spring 启动过程修改注解参数_架构师三狼的博客-程序员宅基地

技术标签: spring  spring boot  问题处理  java  application  javabean  

业务背景

微服务拆分,原有的核心业务抽出公共的核心依赖工程,在现有启动类的基础上,封装了很多核心默认的注解配置,避免应用单独使用注解(如Fegin\ComponentScan\ServletComponentScan等)导致核心服务不可用,需要在SpringBoot Application启动时,部分BeanDefinitionRegistrar执行之前把核心的配置信息添加到注解中,做到动态修改注解的效果。

SpringBoot SpringApplicationRunListener

解决在Spring上下文初始化加载之前进行注入,可以采用Springboot新增的类SpringApplicationRunListener,执行过程如下
SpringApplicationRunListener调用流程
SpringApplicationRunListener方法:
started(SpringBoot扫描spring.factorids后立刻执行;
SpringApplication run代码如下

public ConfigurableApplicationContext run(String... args) {
    
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
    
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

业务实现

拿到了SpringBootApplication Context上下文之前的执行方法,直接撸代码,可能需要修改多个注解值,需要在逻辑上解耦,采用模板方法模式,时序图如下
业务时序图
实现代码:
1、DemoApplicationRunListener.class

public class CombApplicationRunListener implements SpringApplicationRunListener {
    
    private SpringApplication application;
    private String[] args;
    public CombApplicationRunListener(SpringApplication application, String[] args) {
    
        this.application = application;
        this.args = args;
    }
    @Override
    public void starting() {
    
        IApplicationAnnotationFilter.ANNOTATION_FILTERS.forEach(filter -> {
    
            filter.run(application, args);
        });
    }

2、IApplicationAnnotationFilter.class

public interface IApplicationAnnotationFilter {
    

    /**
     * 通过package url扫描查找耗时3秒左右,故使用手工注册方式进行填充,后续考虑优化
     **/
    List<IApplicationAnnotationFilter> ANNOTATION_FILTERS = new ArrayList<IApplicationAnnotationFilter>() {
    
        {
    
            add(new MapperScanAnnotationFilter());
            add(new ServletScanAnnotationFilter());
        }
    };

    boolean run(SpringApplication application, String[] args);
}

3、AbstractAnnotationExecutor.class

public abstract class AbstractAnnotationExecutor implements IApplicationAnnotationFilter {
    
    private Logger logger = LoggerFactory.getLogger(AbstractAnnotationExecutor.class);

    @Override
    public boolean run(SpringApplication application, String[] args) {
    
        if (!this.getClass().isAnnotationPresent(Registar.class)) {
    
            throw new FbRuntimeException("默认核心类未添加Register注解,无法识别需要修改的注解信息!");
        }
        Registar registar = this.getClass().getAnnotation(Registar.class);
        if (!application.getMainApplicationClass().isAnnotationPresent(registar.value())) {
    
            return false;
        }

        Annotation annotation = application.getMainApplicationClass().getAnnotation(registar.value());
        InvocationHandler handler = Proxy.getInvocationHandler(annotation);
        try {
    
            Field field = handler.getClass().getDeclaredField("memberValues");
            field.setAccessible(true);
            Map memberValues = (Map) field.get(handler);
            boolean result = editAnnotationValue(memberValues);
            if (registar.defaultPackages() != null && !"".equals(registar.defaultPackages())) {
    
                result = fillDefaultPackages(memberValues, registar.defaultPackages(), registar.value().getSimpleName());
            }
            return result;
        } catch (Exception e) {
    
            logger.error(String.format("获取注解%s属性异常", annotation.getClass().getName()), e);
        }
        return false;
    }

    protected boolean fillDefaultPackages(Map memberValues, String corePackageName, String className) {
    
        Object basePackagesObj = memberValues.get("basePackages");
        if (basePackagesObj != null) {
    
            //此处不要使用直接获取的Arrays.asList进行添加操作,因为Arrays默认的返回list属于内部类,内部类不支持add和romve
            List<String> basePackages = new ArrayList(Arrays.asList((String[]) basePackagesObj));
            if (!basePackages.contains(corePackageName)) {
    
                basePackages.add(corePackageName);
                //不能直接将list直接put到memberValues中,list默认都是Object类型,会导致取出无法强转String
                String[] defaultPackages = new String[basePackages.size()];
                basePackages.toArray(defaultPackages);
                memberValues.put("basePackages", defaultPackages);
            }
            logger.info(String.format("[系统已经默认在%s.class增加默认核心包扫描->%s]", className, basePackages));
        }
        return true;
    }

    /**
     * @param memberValues 入参,修改属性值的map容器
     * @return 修改是否成功.
     */
    protected abstract boolean editAnnotationValue(Map memberValues);
}

4、 提供两种使用模式
1)通过单独注解默认修改basePackages

@Registar(value = ServletComponentScan.class, defaultPackages = "com.god.demo.framework.common.filter")
public class ServletScanAnnotationFilter extends AbstractAnnotationExecutor {
    

    @Override
    protected boolean editAnnotationValue(Map memberValues) {
    
        //如果只是修改basePackages(要同名),使用了默认实现过程
        return true;
    }
}
  1. 自定义修改属性
@Registar(MapperScan.class)
public class MapperScanAnnotationFilter extends AbstractAnnotationExecutor {
    

    @Override
    protected boolean editAnnotationValue(Map memberValues) {
    
        fillDefaultPackages(memberValues, "com.god.demo.framework.common.mapper", "MapperScan");
        Object sqlSessionFactoryRefObj = memberValues.get("sqlSessionFactoryRef");
        if (sqlSessionFactoryRefObj == null || "".equals(sqlSessionFactoryRefObj)) {
    
            memberValues.put("sqlSessionFactoryRef", SQL_FACTORY);
            log.info("[系统已经默认在MapperScan.class增加默认sqlSessionFactory]" + SQL_FACTORY);
        }
        return true;
    }
}

至此,启动公共注解修改完成

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/lxn1023143182/article/details/112555377

智能推荐

Component name “About“ should always be multi-word.(vue/multi-word-component-names)_"component name \"about\" should always be multi-w-程序员宅基地

export default { // eslint-disable-next-line vue/multi-word-component-names name: "Home",}因为起名时没有使用大驼峰和横线拼接单词,所以报错,在idea中,我发现可以在name:“home”,这一句上面添加 // eslint-disable-next-line vue/multi-word-component-names这一句话,双斜杠也必须加上,不要隔行,紧挨着,向上面代码写的那样,这样可以不报错。._"component name \"about\" should always be multi-word vue/multi-word-component-"

Java实现 LeetCode 406 根据身高重建队列_根据身高重建队列java-程序员宅基地

406. 根据身高重建队列假设有打乱顺序的一群人站成一个队列。 每个人由一个整数对(h, k)表示,其中h是这个人的身高,k是排在这个人前面且身高大于或等于h的人数。 编写一个算法来重建这个队列。注意:总人数少于1100人。示例输入:[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]输出:[[5,0], [7,0], [5,2], [6,1], [..._根据身高重建队列java

npm修改全局下载和缓存路径-程序员宅基地

在安装好node.js 和npm包管理工具后。使用npm config ls查看,安装完成后的路径。AppData 是隐藏文件夹, 在控制面板,设置显示隐藏文件,才看得到。为了保留C盘的剩余空间,有必要,把 npm的 global 路径修改到其他盘符下。npm config set cache "D:\Program Files\npm-c..._npm下载安装包时指定缓存目录去下载

ASP.NET中LINQ的基本用法_linq语言,连接asp。-程序员宅基地

此Demo只是一个极其简单的LINQ查询Demo一个类using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace ConsoleApp1{ public class NBA_Star ..._linq语言,连接asp。

关于block内存问题的分析-程序员宅基地

话不多说, 先根据代码结果看block到底在内存的哪个分区:一:MRC下, 声明的block实现部分,没有引入外界的任何局部变量int main(int argc, const char * argv[]) { @autoreleasepool { //定义bolck void(^myBlock)() = ^{

Java实现 LeetCode 819 最常见的单词(暴力)_code着真正单词-程序员宅基地

819. 最常见的单词给定一个段落 (paragraph) 和一个禁用单词列表 (banned)。返回出现次数最多,同时不在禁用列表中的单词。题目保证至少有一个词不在禁用列表中,而且答案唯一。禁用列表中的单词用小写字母表示,不含标点符号。段落中的单词不区分大小写。答案都是小写字母。示例:输入:paragraph = “Bob hit a ball, the hit BALL flew far after it was hit.”banned = [“hit”]输出: “ball”解释:“_code着真正单词

随便推点

差模信号、共模信号、共模抑制比的概念_ccmr是cop compare-程序员宅基地

共模信号与差模信号辨析差模又称串模,指的是两根线之间的信号差值;而共模噪声又称对地噪声,指的是两根线分别对地的噪声。对于一对信号线A、B,差模干扰相当于在A与B之间加上一个干扰电压,共模干扰相当于分别在A与地、B与地之间加上一个干扰电压;像平常看到的用双绞线传输差分信号就是为了消除共模噪声,原理很简单,两线拧在一起,受到的共模干扰电压很接近, Ua - Ub依然没什么变化,当然这是_ccmr是cop compare

Eigen(5)Array类和元素级操作-程序员宅基地

0. 为什么使用Array 相对于Matrix提供的线性代数运算,Array类提供了更为一般的数组功能。Array类为元素级的操作提供了有效途径,比如点加(每个元素加值)或两个数据相应元素的点乘。1. Array Array是个类模板(类似于Matrx),前三个参数是必须指定的,后三个是可选的,这点和Matrix是相同的。 Array&lt;typename Sc...

iOS开发之TextField和TextView限制表情输入_ios textfield对食物emoji输入做限制-程序员宅基地

#一、通过判断当前的输入模式禁止表情输入- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{ if (textField == self.searchText) { if (string.length == 0) return YES; //不支持系统表情的输入 _ios textfield对食物emoji输入做限制

IM系统四大基本特性_im能力-程序员宅基地

1.实时性:保证消息实时触达是互动场景的必备能力。对于一个实时消息系统,“实时”二字很好地表达了这个系统的基本要求。通过微信和你的好友聊天,结果等半天对方才收到,基本上也没有意愿聊了;直播场景下,如果主播的互动消息房间里的粉丝要等很长时间才能收到,也很难让粉丝们有积极参与的欲望。实时性分为:短轮询,长轮询,WebSocket(长链接)。2.可靠性:“不丢消息”和“消息不重复”是系统值得信赖..._im能力

通过网页或者移动设备链接跳转qq(tim)添加好友(群)_vue移动端怎么写跳转qq加群-程序员宅基地

首先需要去qq群官方,然后点记加群组件,然后选择群,复制对应的代码即可登录到QQ群官网点击加群组件选择群,选择网页还是移动设备 复制代码示例:<html><head> <title>加群组件</title></head><body> <div> <a target="_blank" href="tencent://message/?uin=你的QQ号码">添加好友</a&g_vue移动端怎么写跳转qq加群

宝塔mysql管理员初始密码_宝塔面板忘记管理员用户名密码简单有效解决方法_大唐驱魔师的博客-程序员宅基地

有些学做网站学员使用服务器建网站,并且安装了宝塔面板,建设好之后需要登录后台,但是有时会忘记账号和密码,应该怎么办?或者是在使用的过程中忘记了登陆密码,应该怎么解决呢?下面介绍一下宝塔面板管理员密码忘记解决方法。方法/步骤1,忘记密码之后,在阿里云服务器中找到对应的实例,进入‘远程连接’。2. 选择“Workbench远程连接”;3、填写操作系统用户名对应的密码,如果这个密码忘记,可以在“密码/密..._宝塔mysql密码