【源码解析】SpringBoot日志系统源码分析

news2024/11/23 22:36:08

LoggingApplicationListener

日志组件的处理是LoggingApplicationListener实现的。LoggingApplicationListener#onApplicationEvent,监听事件。如果实现接口GenericApplicationListener,可以允许适配事件类型。

	private static final Class<?>[] EVENT_TYPES = { ApplicationStartingEvent.class,
			ApplicationEnvironmentPreparedEvent.class, ApplicationPreparedEvent.class, ContextClosedEvent.class,
			ApplicationFailedEvent.class };	

	@Override
	public boolean supportsEventType(ResolvableType resolvableType) {
		return isAssignableFrom(resolvableType.getRawClass(), EVENT_TYPES);
	}

	@Override
	public boolean supportsSourceType(Class<?> sourceType) {
		return isAssignableFrom(sourceType, SOURCE_TYPES);
	}

	private boolean isAssignableFrom(Class<?> type, Class<?>... supportedTypes) {
		if (type != null) {
			for (Class<?> supportedType : supportedTypes) {
				if (supportedType.isAssignableFrom(type)) {
					return true;
				}
			}
		}
		return false;
	}

	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationStartingEvent) {
			onApplicationStartingEvent((ApplicationStartingEvent) event);
		}
		else if (event instanceof ApplicationEnvironmentPreparedEvent) {
			onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
		}
		else if (event instanceof ApplicationPreparedEvent) {
			onApplicationPreparedEvent((ApplicationPreparedEvent) event);
		}
		else if (event instanceof ContextClosedEvent
				&& ((ContextClosedEvent) event).getApplicationContext().getParent() == null) {
			onContextClosedEvent();
		}
		else if (event instanceof ApplicationFailedEvent) {
			onApplicationFailedEvent();
		}
	}

startingEvent

LoggingApplicationListener#onApplicationStartingEvent,启动事件。

	private void onApplicationStartingEvent(ApplicationStartingEvent event) {
		this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
		this.loggingSystem.beforeInitialize();
	}

LoggingSystem#get(java.lang.ClassLoader),遍历SYSTEMS,判断logback、log4j2和jdk的log的相关类是否存在,产生对应的实例。

	private static final Map<String, String> SYSTEMS;

	static {
		Map<String, String> systems = new LinkedHashMap<>();
		systems.put("ch.qos.logback.core.Appender", "org.springframework.boot.logging.logback.LogbackLoggingSystem");
		systems.put("org.apache.logging.log4j.core.impl.Log4jContextFactory",
				"org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");
		systems.put("java.util.logging.LogManager", "org.springframework.boot.logging.java.JavaLoggingSystem");
		SYSTEMS = Collections.unmodifiableMap(systems);
	}	

	public static LoggingSystem get(ClassLoader classLoader) {
		String loggingSystem = System.getProperty(SYSTEM_PROPERTY);
		if (StringUtils.hasLength(loggingSystem)) {
			if (NONE.equals(loggingSystem)) {
				return new NoOpLoggingSystem();
			}
			return get(classLoader, loggingSystem);
		}
		return SYSTEMS.entrySet().stream().filter((entry) -> ClassUtils.isPresent(entry.getKey(), classLoader))
				.map((entry) -> get(classLoader, entry.getValue())).findFirst()
				.orElseThrow(() -> new IllegalStateException("No suitable logging system located"));
	}

Log4J2LoggingSystem#beforeInitialize

  • 如果获取到的类是Log4J2LoggingSystem,进行初始化。
  • 调用父类的beforeInitialize,配置SLF4JBridgeHandler
  • 加FILTER,目的是log4j2 没有初始化完毕时是不能使用的,此时所有的处理都是DENY,就不会打印日志了
	@Override
	public void beforeInitialize() {
		LoggerContext loggerContext = getLoggerContext();
		if (isAlreadyInitialized(loggerContext)) {
			return;
		}
		super.beforeInitialize();
		loggerContext.getConfiguration().addFilter(FILTER);
	}

	private LoggerContext getLoggerContext() {
		return (LoggerContext) LogManager.getContext(false);
	}

Slf4JLoggingSystem#beforeInitialize,移除jdk默认的日志输出器,安装适配jdk的日志的slf4j的输出器。

	@Override
	public void beforeInitialize() {
		super.beforeInitialize();
		configureJdkLoggingBridgeHandler();
	}

	private void configureJdkLoggingBridgeHandler() {
		try {
			if (isBridgeJulIntoSlf4j()) {
				removeJdkLoggingBridgeHandler();
				SLF4JBridgeHandler.install();
			}
		}
		catch (Throwable ex) {
			// Ignore. No java.util.logging bridge is installed.
		}
	}

Slf4JLoggingSystem#removeJdkLoggingBridgeHandler,移除默认的jdk的日志输出器。

	private void removeJdkLoggingBridgeHandler() {
		try {
			removeDefaultRootHandler();
			SLF4JBridgeHandler.uninstall();
		}
		catch (Throwable ex) {
			// Ignore and continue
		}
	}

	private void removeDefaultRootHandler() {
		try {
			Logger rootLogger = LogManager.getLogManager().getLogger("");
			Handler[] handlers = rootLogger.getHandlers();
			if (handlers.length == 1 && handlers[0] instanceof ConsoleHandler) {
				rootLogger.removeHandler(handlers[0]);
			}
		}
		catch (Throwable ex) {
			// Ignore and continue
		}
	}

SLF4JBridgeHandler#install,安装SLF4JBridgeHandler

    public static void install() {
        LogManager.getLogManager().getLogger("").addHandler(new SLF4JBridgeHandler());
    }

EnvironmentPreparedEvent

LoggingApplicationListener#onApplicationEnvironmentPreparedEvent

	private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
		if (this.loggingSystem == null) {
			this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
		}
		initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
	}

	protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) {
		new LoggingSystemProperties(environment).apply();
		this.logFile = LogFile.get(environment);
		if (this.logFile != null) {
			this.logFile.applyToSystemProperties();
		}
		this.loggerGroups = new LoggerGroups(DEFAULT_GROUP_LOGGERS);
		initializeEarlyLoggingLevel(environment);
		initializeSystem(environment, this.loggingSystem, this.logFile);
		initializeFinalLoggingLevels(environment, this.loggingSystem);
		registerShutdownHookIfNecessary(environment, this.loggingSystem);
	}

LogFile#get,获取logging.file.namelogging.file.path属性,构造LogFile

	public static LogFile get(PropertyResolver propertyResolver) {
		String file = propertyResolver.getProperty(FILE_NAME_PROPERTY);
		String path = propertyResolver.getProperty(FILE_PATH_PROPERTY);
		if (StringUtils.hasLength(file) || StringUtils.hasLength(path)) {
			return new LogFile(file, path);
		}
		return null;
	}

LoggingApplicationListener#initializeSystem,初始化Log4J2LoggingSystem

	private void initializeSystem(ConfigurableEnvironment environment, LoggingSystem system, LogFile logFile) {
		LoggingInitializationContext initializationContext = new LoggingInitializationContext(environment);
		String logConfig = environment.getProperty(CONFIG_PROPERTY);
		if (ignoreLogConfig(logConfig)) {
			system.initialize(initializationContext, null, logFile);
		}
		else {
			try {
				system.initialize(initializationContext, logConfig, logFile);
			}
			catch (Exception ex) {
				Throwable exceptionToReport = ex;
				while (exceptionToReport != null && !(exceptionToReport instanceof FileNotFoundException)) {
					exceptionToReport = exceptionToReport.getCause();
				}
				exceptionToReport = (exceptionToReport != null) ? exceptionToReport : ex;
				// NOTE: We can't use the logger here to report the problem
				System.err.println("Logging system failed to initialize using configuration from '" + logConfig + "'");
				exceptionToReport.printStackTrace(System.err);
				throw new IllegalStateException(ex);
			}
		}
	}

Log4J2LoggingSystem#initialize,移除过滤器,进行父类初始化。

	public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
		LoggerContext loggerContext = getLoggerContext();
		if (isAlreadyInitialized(loggerContext)) {
			return;
		}
		loggerContext.getConfiguration().removeFilter(FILTER);
		super.initialize(initializationContext, configLocation, logFile);
		markAsInitialized(loggerContext);
	}

AbstractLoggingSystem#initialize,获取指定路径下的文件配置,进行重新初始化

	public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
		if (StringUtils.hasLength(configLocation)) {
			initializeWithSpecificConfig(initializationContext, configLocation, logFile);
			return;
		}
		initializeWithConventions(initializationContext, logFile);
	}

	private void initializeWithConventions(LoggingInitializationContext initializationContext, LogFile logFile) {
		String config = getSelfInitializationConfig();
		if (config != null && logFile == null) {
			// self initialization has occurred, reinitialize in case of property changes
			reinitialize(initializationContext);
			return;
		}
		if (config == null) {
			config = getSpringInitializationConfig();
		}
		if (config != null) {
			loadConfiguration(initializationContext, config, logFile);
			return;
		}
		loadDefaults(initializationContext, logFile);
	}

	protected String getSelfInitializationConfig() {
		return findConfig(getStandardConfigLocations());
	}

	@Override
	protected void reinitialize(LoggingInitializationContext initializationContext) {
		getLoggerContext().reconfigure();
	}

Log4J2LoggingSystem#getStandardConfigLocations,根据如下路径加载配置。

	@Override
	protected String[] getStandardConfigLocations() {
		return getCurrentlySupportedConfigLocations();
	}

	private String[] getCurrentlySupportedConfigLocations() {
		List<String> supportedConfigLocations = new ArrayList<>();
		addTestFiles(supportedConfigLocations);
		supportedConfigLocations.add("log4j2.properties");
		if (isClassAvailable("com.fasterxml.jackson.dataformat.yaml.YAMLParser")) {
			Collections.addAll(supportedConfigLocations, "log4j2.yaml", "log4j2.yml");
		}
		if (isClassAvailable("com.fasterxml.jackson.databind.ObjectMapper")) {
			Collections.addAll(supportedConfigLocations, "log4j2.json", "log4j2.jsn");
		}
		supportedConfigLocations.add("log4j2.xml");
		return StringUtils.toStringArray(supportedConfigLocations);
	}

Slf4j适配

常见的使用方式

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
private Logger logger = LoggerFactory.getLogger(LogTest.class);

LoggerFactory#getLogger(),获取日志工厂,执行初始化。初始化之后执行StaticLoggerBinder.getSingleton().getLoggerFactory();

    public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
    }

    public static ILoggerFactory getILoggerFactory() {
        if (INITIALIZATION_STATE == 0) {
            Class var0 = LoggerFactory.class;
            synchronized(LoggerFactory.class) {
                if (INITIALIZATION_STATE == 0) {
                    INITIALIZATION_STATE = 1;
                    performInitialization();
                }
            }
        }

        switch(INITIALIZATION_STATE) {
        case 1:
            return SUBST_FACTORY;
        case 2:
            throw new IllegalStateException("org.slf4j.LoggerFactory in failed state. Original exception was thrown EARLIER. See also http://www.slf4j.org/codes.html#unsuccessfulInit");
        case 3:
            return StaticLoggerBinder.getSingleton().getLoggerFactory();
        case 4:
            return NOP_FALLBACK_FACTORY;
        default:
            throw new IllegalStateException("Unreachable code");
        }
    }

    private static final void performInitialization() {
        bind();
        if (INITIALIZATION_STATE == 3) {
            versionSanityCheck();
        }
    }

LoggerFactory#bind,找到合适的StaticLoggerBinder,进行初始化。

    private final static void bind() {
        try {
            Set<URL> staticLoggerBinderPathSet = null;
            // skip check under android, see also
            // http://jira.qos.ch/browse/SLF4J-328
            if (!isAndroid()) {
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }
            // the next line does the binding
            StaticLoggerBinder.getSingleton();
            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
            reportActualBinding(staticLoggerBinderPathSet);
        } catch (NoClassDefFoundError ncde) {
            String msg = ncde.getMessage();
            if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
                INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
                Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
                Util.report("Defaulting to no-operation (NOP) logger implementation");
                Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
            } else {
                failedBinding(ncde);
                throw ncde;
            }
        } catch (java.lang.NoSuchMethodError nsme) {
            String msg = nsme.getMessage();
            if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
                INITIALIZATION_STATE = FAILED_INITIALIZATION;
                Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
                Util.report("Your binding is version 1.5.5 or earlier.");
                Util.report("Upgrade your binding to version 1.6.x.");
            }
            throw nsme;
        } catch (Exception e) {
            failedBinding(e);
            throw new IllegalStateException("Unexpected initialization failure", e);
        } finally {
            postBindCleanUp();
        }
    }

LoggerFactory#findPossibleStaticLoggerBinderPathSet,寻找当前系统中路径是org/slf4j/impl/StaticLoggerBinder.class的文件。log4j2和logback分别都有对应的适配实现。

    static Set<URL> findPossibleStaticLoggerBinderPathSet() {
        // use Set instead of list in order to deal with bug #138
        // LinkedHashSet appropriate here because it preserves insertion order
        // during iteration
        Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
        try {
            ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
            Enumeration<URL> paths;
            if (loggerFactoryClassLoader == null) {
                paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
            } else {
                paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
            }
            while (paths.hasMoreElements()) {
                URL path = paths.nextElement();
                staticLoggerBinderPathSet.add(path);
            }
        } catch (IOException ioe) {
            Util.report("Error getting resources from path", ioe);
        }
        return staticLoggerBinderPathSet;
    }

log4j2适配slf4j

log4j2的适配,获取日志工厂Log4jLoggerFactory,实现了ILoggerFactory

public final class StaticLoggerBinder implements LoggerFactoryBinder {
    public static String REQUESTED_API_VERSION = "1.6";
    private static final String LOGGER_FACTORY_CLASS_STR = Log4jLoggerFactory.class.getName();
    private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
    private final ILoggerFactory loggerFactory = new Log4jLoggerFactory();

    private StaticLoggerBinder() {
    }

    public static StaticLoggerBinder getSingleton() {
        return SINGLETON;
    }

    public ILoggerFactory getLoggerFactory() {
        return this.loggerFactory;
    }
}

logback适配slf4j

logback初始化,获取ILoggerFactory,返回的是LoggerContextLoggerContext实现ILoggerFactory

public class StaticLoggerBinder implements LoggerFactoryBinder {

    /**
     * Declare the version of the SLF4J API this implementation is compiled
     * against. The value of this field is usually modified with each release.
     */
    // to avoid constant folding by the compiler, this field must *not* be final
    public static String REQUESTED_API_VERSION = "1.7.16"; // !final

    final static String NULL_CS_URL = CoreConstants.CODES_URL + "#null_CS";

    /**
     * The unique instance of this class.
     */
    private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder();

    private static Object KEY = new Object();

    static {
        SINGLETON.init();
    }

    private boolean initialized = false;
    private LoggerContext defaultLoggerContext = new LoggerContext();
    private final ContextSelectorStaticBinder contextSelectorBinder = ContextSelectorStaticBinder.getSingleton();

    private StaticLoggerBinder() {
        defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME);
    }

    public static StaticLoggerBinder getSingleton() {
        return SINGLETON;
    }

    /**
     * Package access for testing purposes.
     */
    void init() {
        try {
            try {
                new ContextInitializer(defaultLoggerContext).autoConfig();
            } catch (JoranException je) {
                Util.report("Failed to auto configure default logger context", je);
            }
            // logback-292
            if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) {
                StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
            }
            contextSelectorBinder.init(defaultLoggerContext, KEY);
            initialized = true;
        } catch (Exception t) { // see LOGBACK-1159
            Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
        }
    }

    public ILoggerFactory getLoggerFactory() {
        if (!initialized) {
            return defaultLoggerContext;
        }

        if (contextSelectorBinder.getContextSelector() == null) {
            throw new IllegalStateException("contextSelector cannot be null. See also " + NULL_CS_URL);
        }
        return contextSelectorBinder.getContextSelector().getLoggerContext();
    }

    public String getLoggerFactoryClassStr() {
        return contextSelectorBinder.getClass().getName();
    }

}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/472743.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

升级OpenAi/ChatGPT为收费账号绑定API全过程

前言 自从4月1日开始&#xff0c;第一批用户的API就已经过期了&#xff0c;大家可能重新注册新的账号来免费获取ChatGPT/OpenAi的API额度&#xff0c;需要没有18美元额度&#xff0c;但5美元也能调用不少了&#xff0c;一个账号不够&#xff0c;两个&#xff0c;三个&#xff…

NetSuite 负库存控制功能包

目录 1. 前言 2. 功能说明 2.1概述 2.2控制逻辑说明 3. 安装 4. 设置 4.1角色设置 4.2参数设置 4.3负库存追踪记录设置 5. 视频链接 1. 前言 在NetSuite的项目实践中&#xff0c;NetSuite既有功能未能解决用户所面临的负库存问题。参看NetSuite知识会的前期文章&…

《Netty》从零开始学netty源码(四十六)之PooledByteBuf

PooledByteBuf Netty中一大块内存块PoolChunk默认大小为4MB&#xff0c;为了尽可能充分利用内存会将它切成很多块PooledByteBuf&#xff0c;PooledByteBuf的类关系图如下&#xff1a; PooledUnsafeDirectByteBuf与PooledUnsafeHeapByteBuf直接暴露对象的底层地址。 PooledByt…

连续三年增长,徐福记为什么越战越勇?

30年&#xff0c;一个零食品牌能发生什么变化&#xff1f;对徐福记来说&#xff0c;这是一场漫长的拉力赛。 这个过程&#xff0c;是研究消费者喜好变迁的过程&#xff0c;是孜孜不倦创新原料、产品、生产工艺和先进技术的过程&#xff0c;更是徐福记证明自身品牌价值的过程—…

Nero Platinum Suite 2023 白金套装DVD刻录软件 -您强大的无忧包

为什么选择 Nero Platinum&#xff1f; 相信市场领导者&#xff1a;使用这7个搭配和谐的程序&#xff0c;您可以轻松应对多媒体日常。Nero Platinum Suite – 您强大的无忧包 最佳准备 超过 200 种功能为每一项多媒体应用提供解决方案。技术上始终处于最新状态 品质卓越 针…

大数据技术ELK实时检索

一 elasticsearch简介 ElasticSearch是一个高性能&#xff0c;基于Lucene的全文检索服务&#xff0c;是一个分布式的Restful风格的搜索和数据分析引擎&#xff0c;也可以作为NoSQL数据库使用。 对Lucene进行了扩展 原型环境和生产环境可无缝切换 能够水平扩展 支持结构化和非结…

医院导诊指示线路图制作平台,智慧医院专业地图服务

随着国家医疗水平不断进步&#xff0c;配套设施设备日渐完善&#xff0c;医院也进行了不同程度的扩建和新建&#xff0c;为满足人们的医疗需求&#xff0c;无论综合大楼、科室、诊室及住院区域都变得宽敞舒适&#xff0c;患者在诊区内经常找不到自己要去的就诊位置&#xff0c;…

【Mybatis plus】使用分页查询,报错 Parameter ‘xxx‘ not found. Available parameters are xxx

今天工作遇见Mybatis plus 分页查询遇到的错误&#xff0c;提示参数未绑定&#xff0c;现在记录一下。以下数据全部是MOCK信息 0 先给出错误场景 0.0 查询 Mapper java entity 实体类 用 user 代替实体类信息 Data class User {private Long id;private String name;priva…

一文带你了解MySQL数据库InnoDB_Buffer_Pool

前言 上篇文章介绍了MySQL中的存储引擎层主要负责数据的写入和读取&#xff0c;与底层的文件进行交互。MySQL在5.5 版本以后&#xff0c;MySQL默认存储引擎为 InnoDB&#xff0c;他的主要特性有&#xff1a; DML 操作(增、删、改)遵循 ACID(事务安全表) 模型&#xff0c;支持事…

造车十余年,创维从商用车向乘用车冲刺

近日&#xff0c;创维汽车在2023焕新升级发布会发布了3款最新车型。虽然很多人对于创维的印象&#xff0c;仍然停留在电视机概念上&#xff0c;但事实上&#xff0c;这家彩电大王早在13年前即跨界新能源车制造&#xff0c;其旗下公司纯电动客车销量连年稳居国内前三甲。 携成熟…

Vue组件-非单文本组件

非单文本组件(用的少) 在vue中&#xff0c;组件是有两种编写格式的&#xff0c;第一种格式叫非单文本组件&#xff0c;第二种格式叫单文本组件 非单文本组件&#xff1a;一个文件中含有多个组件&#xff0c;也叫多文本组件&#xff0c;比如demo.html里面包含js,css… 单文本…

PCL学习一:点云与PCL基础

参考引用 黑马机器人 | PCL-3D点云PCL(Point Cloud Library)学习记录 1. 点云概述 点云&#xff08;Point Cloud&#xff09;是三维空间中&#xff0c;表达目标空间分布和目标表面特性的点的集合点云通常可以从深度相机或激光雷达中直接获取&#xff0c;也可以从 CAD 等软件中…

JavaEE4(4/27)

目录 1.加锁 2.锁死和重入 3.线程安全的类 4.volatile 1.加锁 当两个线程同时对一个对象进行加锁的时候,会产生竞争 2.锁死和重入 如果一个线程对一个对象加了一次锁,在加一次出现死锁,就是不可重入,否则就是可重入 锁死:对同一个锁再加锁出现的死循环 实际上开发JVM的工程师…

守正创新,核心业务系统助推财务公司数字化转型

为落实国资委加快建设世界一流财务管理体系&#xff0c;推进财务公司数字化转型工作要求&#xff0c;交流总结财务公司同业工作经验&#xff0c;由中国电子主办、中电金信承办的“新核心 新动能——财务公司数字化转型专题研讨会”在京召开。会上中电金信发布了财务公司核心业务…

马斯克们叫停 GPT-5,更像是场行为艺术

目录 01 联名信说了什么&#xff1f; 02 发起方是谁&#xff1f; 03 谁签署了联名信&#xff1f; 04 联名信有哪些问题&#xff1f;三巨头的另外两位 Sam Altman 的表态 其他值得关注的署名者 比如马斯克。 另一个位于前列的署名者是 Stability AI 的创始人 Emad Most…

(剪花布条、客似云来)笔试强训

博主简介&#xff1a;想进大厂的打工人博主主页&#xff1a;xyk:所属专栏: JavaEE初阶 两道编程题~~~ 目录 文章目录 一、[编程题]客似云来 二、[编程题]剪花布条 一、[编程题]客似云来 链接&#xff1a;客似云来__牛客网 来源&#xff1a;牛客网 NowCoder开了一家早餐店&am…

线性回归原理与使用

1 回归 预测年薪 0.5 * 工作年限 0.7 * 学历数值 回归的目的就是预测 数值型的目标值。 求解回归方程式 系数 &#xff08;0.5 &#xff0c;0.7&#xff09;的过程就是 回归。 2 简单线性回归 样本特征只有一个的线性回归 &#xff0c;称为简单线性回归。 举例…

想搞懂 API ,先了解这些技术

在学习 API 相关技术之前&#xff0c;我们需要理解什么是 API。API&#xff08;Application Programming Interface&#xff0c;应用程序编程接口&#xff09;是为了帮助不同的应用程序之间实现数据和功能交换而设计的一组软件接口。使用 API&#xff0c;开发者可以访问底层数据…

c# 数据保存为PDF(一) (spire pdf篇)

文章目录 前言了解 Spire使用Spire.PDF1 创建简单的PDF文档2 创建带有格式的PDF文档&#xff08;使用Draw&#xff09;头部信息页眉页脚测试数据完整的代码 3 创建带有格式的PDF文档&#xff08;使用Gird&#xff09;小结 先上一个效果图 前言 项目中需要将一些数据转存为PDF …

unity GI Shader 实现

之前分享了一篇对unity全局光照的解析&#xff0c;里面提到了一些东西&#xff0c;需要在Shader内实现&#xff0c;在这一篇补上。 要实现对全局GI的shader实现&#xff0c;我们可以通过对unity内置的Lit进行解析查看。 烘焙的方式有很多种&#xff0c;选择合适的方式烘焙和使…