前言
在使用Java开发时,经常是打开一个IDE,然后按着固定的步骤新建项目,写一些代码,编译运行。而且现在的IDE越来越智能,有些甚至按几个按钮就可以开始写代码了,导致我们都不知道为什么需要配置这些选项,以及为什么配置了这些选项。虽然不要重复造轮子,而且IDE大大提高了开发效率,但是离开了IDE,都不再会编程了,所以基本的知识,还是必要学习下。
在使用Spring开发时,我们会有各种这样的jar包,各种配置文件,而且Spring中实现控制反转(IOC)的依赖注入(DI)也离不开这些配置。但是为什么是这样的,一直不理解,下面结合w3cSchool中的Spring教程,全手动搭建第一个Spring程序。
各位看官,如有错误,还望告知,不甚感激!
参考:1,https://www.w3cschool.cn/wkspring/dgte1ica.html
正文
前置条件:
1、已安装配置java环境,这里使用java版本为jdk1.8
2、下载Spring 库,这里使用Spring版本为5.0.8.RELEASE
3、下载Apache Commons Logging API,这里使用的版本为1.2,(开始不知道为什么要用这个包,理解上只是运行一个helloworld好像不需要,查了一下,因为spring的内部实现中使用commons-logging,见https://www.w3cschool.cn/wkspring/dcu91icn.html)
步骤:
一、建立工作目录
一般一个java项目相关的文件都会放在一个工作目录,并在其中建立子文件夹来存放相关类型的文件。这里建立一个工作目录(在IDE中一般是称为项目路径),并在其中建立src目录(用于存放源码),libs目录(用于存放外部jar包),resources目录(用于存放静态资源文件,包括xml配置文件),classes目录(用于存放编译生成的字节码文件)。如下图
二、编写源文件
IDE只是作为一种工具,这里手动搭建Spring程序,在src目录下建立两个文件(可以使用Notepad++),分别命名为HelloWorld.java和MainApp.java。文件内容来源于参考1,如下:
HelloWorld.java
package org.wufeipeng;
// 一个简单的Java Bean
public class HelloWorld {
private String message;
public void setMessage(String message){
this.message = message;
}
// 调用这个方法会打印出message
public void getMessage(){
System.out.println("Your Message : " + message);
}
}
MainApp.java
package org.wufeipeng;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
// 主程序
public class MainApp {
public static void main(String[] args) {
// 读取classpath下的配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
// 获取Bean对象,上下文中的Bean都是Object类型的,需要强转
HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
// 执行对象的方法
obj.getMessage();
}
}
如上,建立源文件时并没有按package建立目录,这和IDE中建立package时的结果是不同的,我觉得最开始的搞java开发肯定也没有想这么多。按着最原始的做法,但是在源文件中还是得分包(也就是最上面那一句package ***.***;),如果不分包,如今版本的jdk编译时会报错。
同时还要新建一个xml配置文件,用来注入Bean,内容如下:
Beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!-- 定义一个Java Bean,并为其属性赋值 -->
<bean id="helloWorld" class="org.wufeipeng.HelloWorld">
<property name="message" value="Hello World!"/>
</bean>
</beans>
三、编译运行
将Spring的库文件和Apache logging的库文件拷贝到libs目录。打开命令行工具,首先切换到工作目录,然后进行按照Java程序运行方法进行编译和运行。最开始我将所有Spring的jar包都拷到libs目录,但是,肯定不是所有jar包都需要的,为了试验到底哪些jar有用,编译运行了多次。经过试验,在编译时使用以下命令就可以编译成功,下面多个classpath的路径之间需要用分隔符,Windows环境用;分割,在Linux环境用:分割。
> javac -d .\classes -classpath .\libs\spring-beans-5.0.8.RELEASE.jar;.\libs\spring-context-5.0.8.RELEASE.jar;.\libs\spring-core-5.0.8.RELEASE.jar .\src\*.java
> javac -d .\classes -classpath .\libs\spring-beans-5.0.8.RELEASE.jar;.\libs\spring-context-5.0.8.RELEASE.jar;.\libs\spring-core-5.0.8.RELEASE.jar -encoding utf-8 .\src\*.java
一共需要3个包:spring-beans-5.0.8.RELEASE.jar;spring-context-5.0.8.RELEASE.jar;spring-core-5.0.8.RELEASE.jar
解析:javac命令用于编译,-d选项指定编译类文件的目标目录(classes目录),-classpath设置用户类路径,它将覆盖 CLASSPATH 环境变量中的用户类路径,即将后面的三个jar包加到类路径,这样编译才能用到它们。.\src\*.java表示编译这个文件夹中所有.java文件。
编译成功,打开classes目录,会发现,虽然写源码没有按包建立目录结构,但是,编译之后还是会按照包建立目录结构。
在运行时,还得加上一些jar包以及目录,下面多个classpath的路径之间也需要分割,Windows环境用;分割,在Linux环境用:分割。
> java -classpath .\classes;.\resources;.\libs\spring-beans-5.0.8.RELEASE.jar;.\libs\spring-context-5.0.8.RELEASE.jar;.\libs\spring-core-5.0.8.RELEASE.jar;.\libs\commons-logging-1.2.jar;.\libs\spring-expression-5.0.8.RELEASE.jar org.wufeipeng.MainApp
> java -classpath .\classes;.\resources;.\libs\spring-beans-5.0.8.RELEASE.jar;.\libs\spring-context-5.0.8.RELEASE.jar;.\libs\spring-core-5.0.8.RELEASE.jar;.\libs\commons-logging-1.2.jar;.\libs\spring-expression-5.0.8.RELEASE.jar org.wufeipeng.MainApp
这里增加了两个包,commons-logging-1.2.jar,spring-expression-5.0.8.RELEASE.jar 。
解析:java命令用于运行class字节码文件,-classpath设置用户类路径,即将后面的classes目录,resource目录(因为要读取配置文件)和依赖的jar包加到类路径。org.wufeipeng.MainApp表示运行这个类,java class loader会根据这个全限定名在用户类路径中查找这个类来运行其main方法。
运行结果
可见其中用Bean中的方法,成功打印出了xml文件中指定的message。
四、打成jar包运行
一般在运行java程序时,不可能将工作目录拷贝到各个地方运行,而是将编译好的文件打成jar包拷贝到其他路径运行。
首先将Beans.xml拷贝到classes文件夹中(这里就是classpath了),然后新建一个META-INF并在其中建立清单文件MANIFEST.MF。内容如下
MANIFEST.MF:
Manifest-Version: 1.0
Created-By: 1.8.0_152 (Oracle Corporation)
Main-Class: org.wufeipeng.MainApp
Class-Path: resources libs/spring-beans-5.0.8.RELEASE.jar libs/spring-context-5.0.8.RELEASE.jar libs/spring-core-5.0.8.RELEASE.jar libs/commons-logging-1.2.jar libs/spring-expression-5.0.8.RELEASE.jar
注意每一行的冒号(:)后面有个空格,Class-Path的各个值之间用空格分割,文件的最后要留空一行,也不知道为啥,很伤。
第一行标明了版本,第二行声明该文件的生成者,一般该属性是由jar命令行工具生成的,第三行定义jar文件的入口类,第四行标明类搜索路径。
运行打包命令:
> jar -cvfm hello-spring.jar META-INF\MANIFEST.MF -C classes .
> jar -cvfm hello-spring.jar META-INF\MANIFEST.MF -C classes .
注意最后一个点(.)
解析:jar命令用于打包class字节码文件,-c表示创建一个jar包,-f指定jar包的文件名,-v生成详细的报造,并输出至标准设备,即打印出打包过程,-m指定manifest.mf文件。后面指定了jar包名为hello-spring.jar,manifest.mf文件的路径。-C表示转到相应的目录下执行jar命令,即在classes目录下执行jar命令,因为我们现在在工作目录下,而jar命令需要在生成的class文件的目录中执行,最后还有一个点,表示以这里(classes)为起点以下的目录结构为包结构。
打包成功,jar包的结果如下
执行jar包
> java -jar hello-spring.jar
> java -jar hello-spring.jar
执行结果,和上面的结果相同。
小结:
打包运行成功后还要几个问题:
1,以上命令在win7和win10环境的命令提示符中可以运行,但是在win10的PowerShell环境中报错,不知为何?
2,jar包的结构中没有第三方的jar包,在当前工作目录可以执行是因为目录下有libs子目录,而manifest.mf文件指定了其为类路径,当时拷贝到其他地方不能执行了,如何将第三方jar包放入jar包中,可以直接在任何环境运行jar文件?
分析:
上面main函数在运行时,首先使用使用配置文件类路径下的Beans.xml,生成了应用程序上下文,后面的ClassPathXmlApplicationContext很容易理解读取的是类路径下的Xml文件,在生成上下文过程中扫描配置文件,并生成相应的Beans,这里xml文件中定义了一个Bean,类型为org.wufeipeng.HelloWorld,通过id(helloworld)来标识这个Bean,并为这个类的属性message赋值Hello World。这句话就相当于
HelloWorld helloWorld = new HelloWorld();
helloWorld.setMessage("Hello World!");
而上下文context持有这个Bean,通过getBean("helloWorld")可以获得这个Bean,不过是Object类型的,因为其本身是HelloWorld类型,所以可以强转成功,然后调用其getMessage()方法,可以执行其中的方法,打印出Your Message : Hello World!
总结
学习Spring的体系结构知道,Spring框架分为了以下这些内容,其中最核心的容器由spring-core,spring-beans,spring-context,spring-context-support和spring-expression(SpEL)构成,这刚好是编译的时候需要的几个包,而spring-context里面已经包含了spring-context-support的一些内容,理解中spring-expression主要是用来处理配置文件中的EL表达式的,所编译的时候没有读取配置文件,所以不需要,但是在运行的时候还是需要spring-expression。
简单记录下核心模块的功能:
-
spring-core模块提供了框架的基本组成部分,包括 IoC 和依赖注入功能。
-
spring-beans 模块提供 BeanFactory,工厂模式的微妙实现。
-
context模块建立在由core和 beans 模块的基础上建立起来的,它以一种类似于JNDI注册的方式访问对象。
- spring-expression模块提供了强大的表达式语言,用于在运行时查询和操作对象图。
它们完整的依赖关系
这里可以解释为什么需要commons-logging。之所以没有引用spring-aop也可以正常运行,可能因为没有使用面向切面编程(AOP),也可能是因为spring-context包中已经有了相关的内容。
一个简单的程序,也可以折腾好多东西,特别是编译运行的阶段,查看了好多资料,慢慢才知道怎么用,后面有机会,还是得更加细致的看看。
——做一个明明白白的码农!