【史上最详细Maven源码讲解(一)】

Maven源码阅读

Maven源码阅读

Main函数在哪里?

在apache-maven-3.8.3\apache-maven\src\bin\mvn的109行

108 CLASSWORLDS_JAR=`echo "${MAVEN_HOME}"/boot/plexus-classworlds-*.jar`
109 CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher

org.codehaus.plexus.classworlds.launcher.Launcher(这个是我们梦开始的地方:main函数就在这里)

org.codehaus.plexus.classworlds.launcher.Launcher#main
    public static void main( String[] args )
    {
        try
        {
            int exitCode = mainWithExitCode( args ); // TODO 核心方法

            System.exit( exitCode );
        }
        catch ( Exception e )
        {
            e.printStackTrace();

            System.exit( 100 );
        }
    }

如何启动?

说明: idea开发工具

JVM参数

说明: 具体参数可以参考idea启动参数

-Dclassworlds.conf=E:\#learning\apache-maven-3.8.3\apache-maven\src\bin\m2.conf -Dmaven.home=D:\apache-maven-3.8.3 -Dmaven.conf=D:\apache-maven-3.8.3\conf -Dclassworlds.conf=D:\apache-maven-3.5.4\bin\m2.conf -Dmaven.multiModuleProjectDirectory=E:\#adt\audit-trail-ms -Duser.dir=E:\#adt\audit-trail-ms

自己的疑惑:

maven.multiModuleProjectDirectory和user.dir参数有什么区别, 默认user.dir是我们源码工程路径maven.multiModuleProjectDirectory没有默认值,必须填写一个,我们mvn命令执行的路径是user.dir配置的, 为什么必须配置两个,这个地方还需要深入理解,有知道的伙伴评论区给出你的理解,谢谢!

程序参数示例

clean package

脚本启动的原理

maven本质是执行命令,因为maven构建框架是固定的,变化的是插件,因此一级命令(我们通常看到的mvn -v查看maven版本,就是这个mvn)是一致的:

一级命令取决于脚本文件的名称,毫无疑问,脚本文件的逻辑含义是maven核心之一,接下来深入看看这些脚本干了啥,以mvn.cmd为例(与mvn相比文件除了语法差异,其他并无差别)进行解析。

一次你完全可以修改mvn名称,改变命令: 示例 mvn -> mvn -副本

D:\apache-maven-3.8.3\bin>mvn - 副本 -v
Apache Maven 3.8.3 (ff8e977a158738155dc465c6a97ffaf31982d739)
Maven home: D:\apache-maven-3.8.3
Java version: 1.8.0_151, vendor: Oracle Corporation, runtime: D:\Java\jdk1.8.0_151\jre
Default locale: zh_CN, platform encoding: GBK
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"

命令行启动cmd命令

for %%i in ("%MAVEN_HOME%"\boot\plexus-classworlds-*) do set CLASSWORLDS_JAR="%%i"
@REM 指定执行命令的jar
set CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher

"%JAVACMD%" ^
  %JVM_CONFIG_MAVEN_PROPS% ^
  %MAVEN_OPTS% ^
  %MAVEN_DEBUG_OPTS% ^
  -classpath %CLASSWORLDS_JAR% ^
@REM classworlds 配置文件地址
  "-Dclassworlds.conf=%MAVEN_HOME%\bin\m2.conf" ^
@rem maven home
  "-Dmaven.home=%MAVEN_HOME%" ^
@rem jansi 的位置
  "-Dlibrary.jansi.path=%MAVEN_HOME%\lib\jansi-native" ^
@rem 命令执行的位置, 默认是maven工程目录
  "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
@rem 自动调用classworlds.launcher.Launcher的main方法, 并吧命令行参数传给main的args
  %CLASSWORLDS_LAUNCHER% %MAVEN_CMD_LINE_ARGS%

脚本主要干了四件事

  1. 关联配置m2.conf;
  2. 关联入口plexus-classworlds-2.6.0.jar(去版本关联)及入口方法;
  3. 写入配置参数;
  4. 截取命令输入作为参数执行2中方法;

这里我们通过源码直接启动java代码方式调试代码

org.codehaus.plexus.classworlds.launcher.Launcher#main

启动的第一行代码, 梦开始的地方

mainWithExitCode

launcher.configure( is ); 加载配置

launcher.launch( args ); 梦想起航

    public void launch( String[] args )
        throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, NoSuchMethodException,
        NoSuchRealmException
    {
        try
        {
            launchEnhanced( args ); // 增强启动, 主要看这个

            return;
        }
        catch ( NoSuchMethodException e )
        {
            // ignore
        }

        launchStandard( args ); // 异常后使用标准启动
    }

launchEnhanced: 增强启动

Object ret = mainMethod.invoke( mainClass, new Object[]{args, getWorld()} ); 反射调用

MavenCli#main(String[], ClassWorld): Maven 命令行

doMain: 核心流程

public int doMain(CliRequest cliRequest) {
        PlexusContainer localContainer = null; // 丛容器
        try {
            // 以下注释来自: https://www.codenong.com/cs106269680/, 如有侵权联系删除
            // 两个核心路径,都有默认值
  			// 完善工作路径workingDirectory,若为空则默认为当前用户路径,多模块项目路径 multiModuleProjectDirectory
			// 若为空,则取maven.multiModuleProjectDirectory环境变量
            // 这两个参数只能通过命令参数取设置,因为没有set方法和其他赋值地方
            // maven.home解析绝对路径
            initialize(cliRequest); // 初始化目录: TODO 重点看下
            // 完善commandLine,解析命令,匹配程序内置的命令-v, -h
            // 判断commandLine是否为-v或者-h,是就在控制台打印出相应的信息并退出
            cli(cliRequest); // 解析命令
			//将系统参数和用户参数写入到请求当中systemProperties以及userProperties属性的设置
            //1.设置系统参数System.getenv(),一些系统级别的参数变量
            //2.设置运行相关参数System.getProperties(),主要是java相关的属性
            //3.设置maven构建的版本信息,主要从/org/apache/maven/messages/build.properties
            //  里获取构建信息:在maven-core的messages包下获取,就是一些版本及发布者信息等
            properties(cliRequest); // 合并属性
			//完善执行请求中的日志属性配置,-X,-q,-e 设置MavenExecutionRequest日志级别,实际上是执行插件的日志级别
            //设置打印日志的消息字体颜色是否启用,其底层是用org.fusesource.jansi.Ansi来控制多色输出;
            //若命令中包含-b和-l部分,则控制台输出字体颜色为默认颜色
            //对于-l file命令,重定向日志输出到file里,通过PrintStream将System日志写入,包含setOut及setErr;
            logging(cliRequest); // 设置日志
            informativeCommands(cliRequest); // 异常提示
            //debug模式下解析解析到-V后打印版本信息
            version(cliRequest); // 打印版本
            //构建Maven.class,负责执行maven指定的构建行为,组装了一些参数
            // maven = container.lookup(Maven.class); DefaultMaven后续处理pom会用到
            localContainer = container(cliRequest); // 构建容器
            //打印一些设置信息是否开启,诸如错误日志开启,CHECKSUM_POLICY_FAIL是否开启
            commands(cliRequest);
            //EventSpyDispatcher初始化及设置,-s,-gs命令解析,设置用户配置文件及全局配置文件
            // 设置参数, 解析settings.xml, TODO 核心逻辑
            configure(cliRequest);
            //-t,-gt命令解析,设置toolchains
            toolchains(cliRequest);
			//1.校验将会废弃的maven命令,{ "up", "npu", "cpu", "npr" },并打印警告信息
            //2. 解析-b(批处理),-nsu(进展快照更新),-N(不递归到子项目中),-ff(在构建响应堆中首次失败时停止构建)
            //-fae(仅仅是当前失败的构建才会置为失败,其余不受影响的构建会继续),-fn(所有的构建失败都会被忽略,无论个中构建任务失败与否)[-ff,-fae,-fn是顺序检测取第一个]
            //-o(设置线下操作标识),-U(标识是否更新快照)
            //-C与-c(互斥出现),当checksum不匹配的时候,使用什么处理策略,默认是-c(警告)-C会使得checksum不匹配后使构建失败退出
            //-P xxx,xxxx(用以引入配置文件list,以逗号间隔,这里不涉及配置文件的格式及解析过程)
            //-ntp(在上传或者下载时不显示进度信息,这个在进度显示优先级最高),然后如果有-l xxxx.log等日志文件,
            //就使用Slf4jMavenTransferListener监听并写入到日志文件,如果没有且日志级别为debug的,使用ConsoleMavenTransferListener监听进度
            //-f xxxx/pom.xml,加载pom.xml文件到执行参数里,如果没有设置,默认为基础路径下的pom文件,如果有父文件则进行加载和引用
            //-rf xxxxx(当构建失败后,重新执行从某个特定模块重新执行,选项后可跟随[groupId]:artifactId)
            //-pl xxxxxx(project list,以逗号间隔多个模块的相对路径,或者模块以[groupId]:artifactId的形式表示),后面跟的每个参数项,会以+,-,!,或其他符号开始,+或其他符号为头标会被exclude,其余则被include
            //-am xxx,-amd xxx,xxx表示模块,设置编译行为或者编译作用域,前者表示同时编译选定模块所依赖的模块(上游make),后者表示编译依赖选定模块的部分(下游make),根据实际需求选定,若没有指定,则make时会默认把上游和下游模块都加载进来
            //依次在用户配置和系统配置(用户配置没有取到)里加载maven.repo.local,加载本地仓库路径到运行参数里
            //-T xxx(设置构建并发的并行线程数,可以在数字之后跟C,表示这个线程数需要跟当前运行时jvm可用的processor数量相乘,即针对每个内核都派发那么多线程,通过Runtime.getRuntime().availableProcessors()获取)
            //-b xxxxxx(指定构建器的id,默认是multithreaded构建器(builder))
            populateRequest(cliRequest); // 填充一系列命令参数
            //-emp xxx(单独的工具,对master密码进行加密并打印),不参与构建过程
            //-ep xxx(单独的工具,对服务器密码进行加密并打印),不参与构建过程
            encryption(cliRequest); // 加密参数处理
			//通过两种方式判断是否使用maven2老版本的本地库而不使用远程仓库:
            //-llr(即legacy-local-repository,设置使用老版本参数)
            //系统配置maven.legacyLocalRepo为true,则使用老版本maven库
            repository(cliRequest); // 处理遗留本地仓库
            //执行命令入参
            return execute(cliRequest); // 执行
        } catch (ExitException e) {
            return e.exitCode; // exit, 系统退出
        } catch (UnrecognizedOptionException e) {
            // pure user error, suppress stack trace : 纯用户错误,抑制堆栈跟踪
            return 1;
        } catch (BuildAbort e) {
            CLIReportingUtils.showError(slf4jLogger, "ABORTED", e, cliRequest.showErrors);
            return 2;
        } catch (Exception e) {
            CLIReportingUtils.showError(slf4jLogger, "Error executing Maven.", e, cliRequest.showErrors);
            return 1;
        } finally {
            if (localContainer != null) {
                localContainer.dispose();
            }
        }
    }

MavenCli#execute: maven执行的核心入口

  • MavenCli#execute: maven
private int execute(CliRequest cliRequest) throws MavenExecutionRequestPopulationException { // 获取本地仓库 DefaultMavenExecutionRequestPopulator DefaultMavenExecutionRequest 填充器
    MavenExecutionRequest request = executionRequestPopulator.populateDefaults(cliRequest.request);

    eventSpyDispatcher.onEvent(request); // 请求事件通知
    // TODO 核心执行逻辑
    MavenExecutionResult result = maven.execute(request); // maven命令执行, 核心中的核心

    eventSpyDispatcher.onEvent(result); // 结果时间通知

    eventSpyDispatcher.close(); // 监控事件调度器关闭
}
  • org.apache.maven.DefaultMaven#execute

org.apache.maven.DefaultMaven#doExecute

MavenExecutionResult result = new DefaultMavenExecutionResult();

org.apache.maven.DefaultMaven#doExecute

private MavenExecutionResult doExecute(MavenExecutionRequest request, MavenSession session,
            MavenExecutionResult result, DefaultRepositorySystemSession repoSession) {
        eventCatapult.fire(ExecutionEvent.Type.ProjectDiscoveryStarted, session, null); // 开始处理工程了 TODO主要是通知事件
        // 构建pom
        Result<? extends ProjectDependencyGraph> graphResult = buildGraph(session, result); // 所有pom
        try {
            session.setProjectMap(getProjectMap(session.getProjects())); // 设置项目地图: 项目的pom位置
        } catch (DuplicateProjectException e) {
            return addExceptionToResult(result, e);
        }

        WorkspaceReader reactorWorkspace;
        try { // 反应堆, 获取反应堆
            reactorWorkspace = container.lookup(WorkspaceReader.class, ReactorReader.HINT);
        } catch (ComponentLookupException e) {
            return addExceptionToResult(result, e);
        }
        // 设置工作空间阅读器
        repoSession.setWorkspaceReader(
                ChainedWorkspaceReader.newInstance(reactorWorkspace, repoSession.getWorkspaceReader()));

        repoSession.setReadOnly();

        graphResult = buildGraph(session, result); // todo: 核心 工程pom信息

        try {
            result.setProject(session.getTopLevelProject());

            validatePrerequisitesForNonMavenPluginProjects(session.getProjects());

            lifecycleStarter.execute(session); // todo: 核心, 生命周期构建

            validateActivatedProfiles(session.getProjects(), request.getActiveProfiles());
        } finally {
        }
        return result;
    }
  • org.apache.maven.lifecycle.internal.LifecycleStarter#execute

    builder.build( session, reactorContext, projectBuilds, taskSegments, reactorBuildStatus );
    
  • org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder#build

    还有一个多线程的,类似

    public void build(MavenSession session, ReactorContext reactorContext, ProjectBuildList projectBuilds,
            List<TaskSegment> taskSegments, ReactorBuildStatus reactorBuildStatus) {
        for (TaskSegment taskSegment : taskSegments) {
            for (ProjectSegment projectBuild : projectBuilds.getByTaskSegment(taskSegment)) {
                try { // todo: 核心 构建工程
                    lifecycleModuleBuilder.buildProject(session, reactorContext, projectBuild.getProject(),
                            taskSegment); // 生命周期构建
                    if (reactorBuildStatus.isHalted()) {
                        break;
                    }
                } catch (Exception e) {
                    break; // Why are we just ignoring this exception? Are exceptions are being used for flow control
                }
            }
        }
    }
    
  • org.apache.maven.lifecycle.internal.LifecycleModuleBuilder#buildProject

    核心中的核心
    注释参考: https://www.codenong.com/cs106269680/ 如有侵权联系删除

  1. 设置构建的初始化属性
  2. 校验本地依赖库的可访问性
  3. 创建RepositorySystemSession
  4. 创建MavenSession
  5. 执行AbstractLifecycleParticipant.afterSessionStart(session)
  6. 获取校验pom对象error的对象
  7. 创建ProjectDependencyGraph用以调整–projects和reactor模式(确保所有传递到ReactorReader的项目仅仅是指定的项目)
  8. 创建ReactorReader用以获取对象映射(getProjectMap( projects )),在获取的时候会对对象做唯一性校验,这些对象是从第6步中获取的对象集合
  9. 执行AbstractLifecycleParticipant.afterProjectsRead(session)后置处理
  10. 创建ProjectDependencyGraph,不用再调整,第7步已经做了这个工作,这里要完成对AbstractLifecycleParticipants的拓扑排序,可能会改变依赖进而影响构建顺序
  11. 开始执行LifecycleStarter.start()生命周期开始
    //=============================================================================
    //上述为构建执行的准备步骤,接下来是构建的详细步骤,会涉及到获取maven插件(plugin)配置的获取以及插件goal的执行,直到每个插件执行自己的构建逻辑:
    1. 每个插件的goal执行是基于某个生命周期运行的,这里是执行插件某个goal的入口:org.apache.maven.lifecycle.internal.LifecycleStarter#execute(MavenSession)
    2. 执行DefaultLifecycleTaskSegmentCalculator#calculateTaskSegments,获取配置插件对应的goal集合,如果找不到就会默认用MavenSession.getTopLevelProject().getDefaultGoal(),走默认maven构建(获得的构建任务放在TaskSegment里)
    3. 根据2中得出的构建任务,计算得出构建对象集合,封装在ProjectBuildList里,实际上是做了一个映射,当前MavenSession的所有projects都必须执行TaskSegment集合里的任务
    4. 获取执行参数里的builderId,分为两种,单例构建或多线程构建,默认是多线程,可以通过-T命令设置线程数量,然后通过指定构建器着手进行构建工作
    5. 构建逻辑,单例构建遍历任务集合(TaskSegment)与构建对象集合ProjectBuildList逐一进行构建,多线程按照指定的线程数量作为上限进行并发构建
    6. 一个项目(project)构建策略,计算得出当前项目的构建计划MavenExecutionPlan,统一Project中的PluginManagement与BuildPlugin(构建模块的版本),获得MojoExecution,
public void buildProject(MavenSession session, MavenSession rootSession, ReactorContext reactorContext,
        MavenProject currentProject, TaskSegment taskSegment) {
    session.setCurrentProject(currentProject);
    sessionScope.enter(reactorContext.getSessionScopeMemento());
    sessionScope.seed(MavenSession.class, session);
    try {
        eventCatapult.fire(ExecutionEvent.Type.ProjectStarted, session, null);
        //获取构建执行计划
        MavenExecutionPlan executionPlan = builderCommon.resolveBuildPlan(session, currentProject, taskSegment,
                new HashSet<Artifact>());
        //通过执行计划得到执行器
        List<MojoExecution> mojoExecutions = executionPlan.getMojoExecutions();

        projectExecutionListener.beforeProjectLifecycleExecution(
                new ProjectExecutionEvent(session, currentProject, mojoExecutions));
        //执行插件构建任务,其内部是循环执行mojoExecutions对应的执行器,
        //具体逻辑是通过MavenPluginManager拿到插件对应的Mojo接口实例
        //然后执行Mojo实例,由此执行扩展接口逻辑,得到插件提供的强大扩展能力
        mojoExecutor.execute(session, mojoExecutions, reactorContext.getProjectIndex()); // todo 核心:解析依赖
    } catch (Throwable t) {
    } finally {
    }
}
  • org.apache.maven.lifecycle.internal.MojoExecutor#execute

    • org.apache.maven.lifecycle.internal.MojoExecutor#execute
    • org.apache.maven.lifecycle.internal.MojoExecutor#execute

    mojo执行器

原文地址:https://blog.csdn.net/yin18827152962/article/details/121482340

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


可以认为OpenFeign是Feign的增强版,不同的是OpenFeign支持Spring MVC注解。OpenFeign和Feign底层都内置了Ribbon负载均衡组件,在导入OpenFeign依赖后无需专门导入Ribbon依赖,用做客户端负载均衡,去调用注册中心服务。
为进一步规范小程序交易生态、提升用户购物体验、满足用户在有交易的小程序中便捷查看订单信息的诉求,自2022年12月31日起,对于有“选择商品/服务-下单-支付”功能的小程序,需按照平台制定的规范,在小程序内设置订单中心页。开发者可通过小程序代码提审环节,或通过「设置-基础设置-小程序订单中心path设置」模块设置订单中心页path。1、 新注册或有版本迭代需求的小程序,可在提审时通过参数配置该商家小程序的订单中心页path。2、无版本迭代需求的小程序,可在小程序订单中心path设置入口进行设置。
云原生之使用Docker部署Dashdot服务器仪表盘
本文主要描述TensorFlow之回归模型的基本原理
1.漏洞描述Apache Druid 是一个集时间序列数据库、数据仓库和全文检索系统特点于一体的分析性数据平台。Apache Druid对用户指定的HTTP InputSource没有做限制,并且Apache Druid默认管理页面是不需要认证即可访问的,可以通过将文件URL传递给HTTP InputSource来绕过。因此未经授权的远程攻击者可以通过构造恶意参数读取服务器上的任意文件,造成服务器敏感性信息泄露。2.影响版本Apache Druid &lt;= 0.21.13...
内部类(当作类中的一个普通成员变量,只不过此成员变量是class的类型):一个Java文件中可以包含多个class,但是只能有一个public class 如果一个类定义在另一个类的内部,此时可以称之为内部类使用:创建内部类的时候,跟之前的方法不一样,需要在内部类的前面添加外部类来进行修饰 OuterClass.InnerClass innerclass = new OuterClass().new InnerClass();特点:1.内部类可以方便的访问外部类的私有属性...
本文通过解读国密的相关内容与标准,呈现了当下国内技术环境中对于国密功能支持的现状。并从 API 网关 Apache APISIX 的角度,带来有关国密的探索与功能呈现。作者:罗泽轩,Apache APISIX PMC什么是国密顾名思义,国密就是国产化的密码算法。在我们日常开发过程中会接触到各种各样的密码算法,如 RSA、SHA256 等等。为了达到更高的安全等级,许多大公司和国家会制定自己的密码算法。国密就是这样一组由中国国家密码管理局制定的密码算法。在国际形势越发复杂多变的今天,密码算法的国产化
CENTOS环境Apache最新版本httpd-2.4.54编译安装
Apache HTTPD是一款HTTP服务器,它可以通过mod_php来运行PHP网页。影响版本:Apache 2.4.0~2.4.29 存在一个解析漏洞;在解析PHP时,将被按照PHP后缀进行解析,导致绕过一些服务器的安全策略。我们查看一下配置:读取配置文件,前三行的意思是把以 结尾的文件当成 文件执行。问题就在它使用的是 符号匹配的,我们都知道这个符号在正则表达式中的意思是匹配字符串的末尾,是会匹配换行符的,那么漏洞就这样产生了。 进入容器里,打开index.php,发现如果文件后缀名为 php、
apache Hop现在好像用的人很少, 我就自己写一个问题收集的帖子吧, 后面在遇到什么问题都会在该文章上同步更新
2.启动容器ps:注意端口占用,当前部署在 8080 端口上了,确保宿主机端口未被占用,不行就换其他端口ps:用户名和密码都是 admin,一会用于登录,其他随便填5.下载一个官方提供的样例数据库【可跳过】ps:此步国内无法访问,一般下载不了,能下的就下,不能下的跳过就行了,一会配置自己的数据库7.访问登录页面ps:注意端口是上面自己配置的端口,账号密码是 admin依次点击 Settings → Database Connections点击 DATABASE 就可以配置自己的数据库了
String类的常用方法1. String类的两种实例化方式1 . 直接赋值,在堆上分配空间。String str = "hello";2 . 传统方法。通过构造方法实例化String类对象String str1 = new String("Hello");2.采用String类提供的equals方法。public boolean equals(String anotherString):成员方法 str1.equals(anotherString);eg:publi
下载下载地址http://free.safedog.cn下载的setup:安装点击下面的图标开始安装:可能会提示:尝试先打开小皮面板的Apache服务:再安装安全狗:填入服务名:如果服务名乱写的话,会提示“Apache服务名在此机器上查询不到。”我干脆关闭了这个页面,直接继续安装了。安装完成后,需要进行注册一个账户,最后看到这样的界面:查看配置:...
一、问题描述一组生产者进程和一组消费者进程共享一个初始为空、大小n的缓冲区,只有缓冲区没满时,生产者才能把资源放入缓冲区,否则必须等待;只有缓冲区不为空时,消费者才能从中取出资源,否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入资源,或一个消费者从中取出资源。二、问题分析(1)、关系分析。生产者和消费者对缓冲区互斥访问是互斥关系,同时生产者和消费者又是一个相互协作的关系,只有生产者生产之后,消费者只能才能消费,它们还是同步关系。(2)、整理思路。只有生产生产者和消费者进程,正好是这两个进程
依赖注入的英文名是Dependency Injection,简称DI。事实上这并不是什么新兴的名词,而是软件工程学当中比较古老的概念了。如果要说对于依赖注入最知名的应用,大概就是Java中的Spring框架了。Spring在刚开始其实就是一个用于处理依赖注入的框架,后来才慢慢变成了一个功能更加广泛的综合型框架。我在学生时代学习Spring时产生了和绝大多数开发者一样的疑惑,就是为什么我们要使用依赖注入呢?现在的我或许可以给出更好的答案了,一言以蔽之:解耦。耦合度过高可能会是你的项目中一个比较
<dependency><groupId>org.apache.velocity</groupId><artifactId>velocity-engine-core</artifactId><version>使用人数最多的版本</version></dependency>importorg.apache.velocity.Template;importorg.apache.velo
Java Swing皮肤包前言:一.皮肤包分享二.皮肤包的使用1.先新建一个项目。2.导入皮肤包1.先导入我们刚刚下载的jar文件,右键项目demo即可2.如果右键没有这个选项,记得调为下图模式3.点击下图蓝色圆圈处4.找到刚刚下载的jar文件,点击打开即可5.我们看一下效果,是不是比原生的好看前言:因为Java Swing自身皮肤包不是很好看,甚至有点丑,怎么让你的界面更加好看,这里就需要用到皮肤包,我发现了一个还不错的皮肤包,让你的界面美观了几个等级。废话不多说。一.皮肤包分享百度网盘分享链接:
一、前言在做Java项目开发过程中,涉及到一些数据库服务连接配置、缓存服务器连接配置等,通常情况下我们会将这些不太变动的配置信息存储在以 .properties 结尾的配置文件中。当对应的服务器地址或者账号密码信息有所变动时,我们只需要修改一下配置文件中的信息即可。同时为了让Java程序可以读取 .properties配置文件中的值,Java的JDK中提供了java.util.Properties类可以实现读取配置文件。二、Properties类Properties 类位于 java.util.Pro
Mybatis环境JDK1.8Mysql5.7maven 3.6.1IDEA回顾JDBCMysqlJava基础MavenJunitSSM框架:配置文件的最好的方式:看官网文档Mybatis1、Mybatis简介1.1 什么是Mybatis如何获得Mybatismaven仓库:中文文档:https://mybatis.org/mybatis-3/zh/index.htmlGithub:1.2 持久化数据持久化持久化就是将程序的数据在持久状态和瞬时状态转