项目构建

项目构建是以“java源文件”,“框架配置文件”,“JSP”,“HTML”,“图片”等资源为原材料去生产一个可以运行的项目的过程。
如果不使用项目构建工具,对java源文件的编译,打包,运行等过程都必须通过相应命令来执行,为了提高开发效率,催生出了项目构建工具。
构建工具的发展:make->ant->maven->gradle。项目构建工具有多种,maven只是其中一种而已。

maven概述

maven是一款自动化构建工具,专注于java平台的项目构建和依赖管理。maven由apache软件基金会进行维护,是一个开源的项目。
maven使用pom(项目对象模型)来管理项目,通过一小段描述信息来管理项目的构建、报告及文档等。

为什么要使用maven?

1、分布式开发

传统项目和分布式项目的区别:

  • 传统项目:
    开发:只创建一个项目,以package来划分模块;
    部署:将项目部署到服务器上的tomcat容器,发布一次即可;
  • 分布式项目:
    开发:按模块将一个完整的项目拆分为多个项目;
    部署:将拆分后的项目分别发布到对应的tomcat中,需要发布多次;

一般来说,如果项目负载量高,应该采用分布式进行开发和部署;如果项目负载量很低,体现不出分布式开发的优势。
maven可以很方便的实现分布式开发。

2、依赖管理

  • 使用maven可以避免频繁的手动复制jar包到WEB-INF/lib目录;
  • 使用maven可以避免繁琐的jar包下载过程;
  • 使用maven可以自动导入项目所需jar包依赖的其他jar包,无需去记忆jar包之间的依赖,从而大大降低了学习成本。

maven的安装和配置

安装

下载地址:https://maven.apache.org/download.cgi,解压即可用。
注意:应解压到一个无中文无空格的目录下。

配置

  • 创建一个MAVEN_HOME环境变量,其值指向解压文件所在目录。
  • 引入path,%MAVEN_HOME%\bin

测试

在命令行模式中,输入mvn -v命令,回车执行后从响应信息中可查看到maven是否安装成功。

maven核心概念

maven项目的目录结构

约定优先于配置
在maven中,项目的目录结构是约定好的,不能改变。

Maven - 图1
maven在项目构建过程中,最主要的几种操作:

  • 清理:删除之前编译的结果,为下一次编译做准备。
    对应的maven命令:mvn clean
  • 编译:将java源文件编译为字节码文件。
    对应的maven命令:mvn compile(编译主程序)或者mvn test-compile(编译测试程序)
  • 测试:对项目开发的关键节点进行测试,保证项目在迭代开发过程中关键节点的正确性。
    对应的maven命令:mvn test
  • 报告:对测试结果使用标准格式进行记录和展示。
  • 打包:将一个包含诸多文件项目封装成一个可被安装或部署的包。java项目->jar,web项目->war
    对应的maven命令:mvn package
  • 安装:在maven环境中,安装是指将打包结果安装到maven的本地仓库。
    对应的maven命令:mvn install
  • 部署:将打包结果部署到远程仓库或是将war包部署到服务器上的tomcat容器。
    对应的maven命令:mvn deploy

案例

步骤1:创建maven对应的目录结构及文件。
test\src\main\resources
test\src\main\java\com\woniu\maven\MainHello.java

  1. package com.woniu.maven;
  2. public class MainHello{
  3. public static void main(String[] args){
  4. System.out.println("main,hello world!!");
  5. }
  6. }

test\src\test\resources
test\src\test\java\com\woniu\maven\TestHello.java

  1. package com.woniu.maven;
  2. public class TestHello{
  3. public static void main(String[] args){
  4. System.out.println("test,hello world!!");
  5. }
  6. }

test\pom.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <!-- 坐标 -->
  7. <groupId>com.woniu.maven</groupId>
  8. <artifactId>Hello</artifactId>
  9. <version>1.0-SNAPSHOT</version>
  10. </project>

步骤2:在命令行模式中,测试maven命令的使用。注意:maven命令只能在pom.xml所在的目录中执行
测试maven编译命令:mvn compile
当本机的jdk版本与maven默认的jdk版本不一致时,会编译失败,从而导致项目构建失败。
编辑maven安装目录/conf/settings.xml,在标签中修改maven的jdk版本。

  1. <profile>
  2. <id>jdk8</id>
  3. <activation>
  4. <activeByDefault>true</activeByDefault>
  5. <jdk>8</jdk>
  6. </activation>
  7. <properties>
  8. <maven.compiler.source>8</maven.compiler.source>
  9. <maven.compiler.target>8</maven.compiler.target>
  10. <maven.compiler.compilerVersion>8</maven.compiler.compilerVersion>
  11. </properties>
  12. </profile>

测试maven清除命令:mvn clean
测试编译测试程序命令:mvn test-compile
测试命令:mvn test
测试打包命令:mvn package,注意:生成jar或war包时,仅打包主程序main下的内容。
测试安装命令:mvn install。

maven的仓库

Maven - 图2

本地仓库

maven默认的本地仓库:
windows系统:C:\Users\系统登录用户名.m2\repository
linux系统:用户的home目录.m2\repository
一般来说,windows系统中本地仓库不会使用默认仓库,而是使用自定义的本地仓库。

自定义本地仓库
  1. 编辑maven安装目录/conf/settings.xml,在该文件的标签中配置本地仓库。
  1. <localRepository>E:\maven\repository</localRepository>

远程仓库

  • 私服:搭建在局域网环境中,为当前局域网内所有的maven项目服务
  • 中央仓库:在internet上,为全世界的maven项目提供服务
  • 中央仓库镜像:为了分担中央仓库流量,提升用户访问和下载速度

设置中央仓库镜像
  1. 编辑maven安装目录/conf/settings.xml,在该文件的标签中设置中央仓库镜像。
  1. <mirrors>标签中配置中央仓库镜像
  2. <mirror>
  3. <id>nexus-aliyun</id>
  4. <mirrorOf>central</mirrorOf>
  5. <name>Nexus aliyun</name>
  6. <url>http://maven.aliyun.com/nexus/content/groups/public</url>
  7. </mirror>
  8. 2020年,阿里云镜像地址
  9. <mirror>
  10. <id>aliyunmaven</id>
  11. <mirrorOf>*</mirrorOf>
  12. <name>阿里云公共仓库</name>
  13. <url>https://maven.aliyun.com/repository/public</url>
  14. </mirror>
  15. IDEA设置Maven忽略https检查
  16. 路径:setting--->build--->maven--->importing--->vm options for importer
  17. 值:-Xmx768m -Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true

maven的生命周期

    maven的核心程序定义了三套相互独立的生命周期,生命周期中各个阶段的具体任务是插件来完成。

    maven核心程序为了更好的实现自动化构建,设定了一个生命周期执行规则:**不管是执行生命周期中的哪一个阶段,都会从当前生命周期的最初位置开始执行**。

maven的三套生命周期

clean lifecycle
    在项目构建之前执行的一些清理工作,clean生命周期包含三个阶段:
  1. pre-clean:执行清理工作之前做的一些事情
  2. clean:执行清理工作
  3. post-clean:执行完清理工作之后做的一些事情

default lifecycle
    是项目构建的核心部分,编译、测试、打包、安装、部署等,default生命周期是maven生命周期中最重要的一个,绝大部分工作都发生在这个生命周期中。
  1. validate
  2. generate-sources
  3. process-sources
  4. generate-resources
  5. process-resources:复制并处理资源文件,放到目标目录,准备打包。
  6. compile:编译项目主程序源代码
  7. process-classes
  8. generate-test-sources
  9. process-test-sources
  10. generate-test-resources
  11. process-test-resources:复制并处理资源文件,放到测试目录。
  12. test-compile:编译项目测试程序源代码
  13. process-test-classes
  14. test:使用合适的单元测试框架运行测试,测试代码不会被打包和部署。
  15. prepare-package
  16. package:接受编译好的代码,打包成可发布的格式。jar或war等。
  17. pre-integration-test
  18. integration-test
  19. post-integration-test
  20. verify
  21. install:将打包结果安装到maven本地仓库,以便于其他项目进行依赖。
  22. deploy:将最终的包复制到远程仓库,以便其他开发人员与项目共享,或者把war包部署到服务器上运行。

site lifecycle
    site生命周期用于生成项目报告,生成一个站点,发布站点。
  1. pre-site:在生成站点文档之前做的一些事情
  2. site:生成站点文档
  3. post-site:生成站点文档之后做的一些事情
  4. site-deploy:将生成的站点文档部署到特定的服务器上。

maven的pom.xml

maven的坐标

    数学的坐标分为两类:
  • 平面:由X和Y两个向量来唯一进行定位某个具体的点。
  • 空间:由X和Y、Z三个向量来唯一进行定位某个具体的点。

      在maven中,通过groupId和artifactId、version三个向量在maven仓库中唯一定位到一个maven项目,使用坐标来描述当前项目存放在maven仓库的位置。
    
<!-- 坐标 -->
<!-- 公司或组织的域名倒序+开发的项目名 -->
<groupId>com.woniu.maven</groupId>
<!-- 项目中的模块名 -->
<artifactId>Hello</artifactId>
<!-- 版本 -->
<version>1.0-SNAPSHOT</version>

使用mvn install安装到本地仓库的项目jar包的命名:artifactId+”-“+version.jar。

maven的依赖

maven的依赖通过<dependency>标签实现,该标签是<dependencies>的子标签,不能在pom.xml独立存在,必须作为<dependencies>的子标签出现。
<dependencies>
    <dependency>
        <!-- 被依赖的项目groupId -->
        <groupId>com.woniu.maven</groupId>
        <!-- 被依赖的项目artifactId -->
        <artifactId>Hello</artifactId>
        <!-- 被依赖的项目的version -->
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>
标签中并不只有坐标信息,还可以有其他配置,重要的配置有:
:用于描述当前依赖的类型。 xml <dependencies> <dependency> <groupId>com.woniu.maven</groupId> <artifactId>Hello</artifactId> <version>1.0-SNAPSHOT</version> <!-- 手动指定当前依赖的类型,默认为jar。也可以取值为:war或pom --> <type>jar</type> </dependency> </dependencies> :定义了导入的依赖作用范围。 xml <dependencies> <dependency> <groupId>com.woniu.maven</groupId> <artifactId>Hello</artifactId> <version>1.0-SNAPSHOT</version> <type>jar</type> <!-- 定义依赖的作用范围,默认范围是compile。 可以取值:compile|test|runtime|provided|system|import --> <scope>compile</scope> </dependency> </dependencies> - compile:默认值,表示当前依赖会参与项目的编译、测试、和运行阶段,属于强依赖,打包时,会打进对应的包中。 - test:表示当前依赖仅仅只参与测试相关的内容,包括测试用例的编译和执行。典型:junit。 - runtime:当前依赖只参与运行周期中的使用。一般来说,这种都是接口与实现相分离的类库,典型:JDBC类库,在编译时仅依赖相关接口,在具体运行时才需要具体的数据库驱动程序。 - provided:表示当前依赖不参与打包过程,这个依赖由运行时环境来提供。典型:servlet-api.jar。 - system:使用上跟provided相同,表示当前依赖不从maven仓库中获取,而从本地文件系统中获取。 - import:只能在dependencyManagement中使用,能解决maven单继承的问题,import的依赖关系实际上并不参与限制依赖关系的传递性。 修改idea的maven版本
settings->build->build tools->maven - maven home derictory:指向maven安装目录; - user settins file:指向安装目录下/conf/settings.xml; - local repository:指向自定义的本地仓库; #### maven的继承 ##### maven项目的分类 在maven中,根据项目打包类型的不同,将项目分为三种类型:jar、war、pom。 - jar:普通java项目 - war:web项目 - pom:父项目,如果项目下有子项目存在,则该项目的打包类型一定是pom。 通过来设置项目的打包类型。 xml <packaging>jar|war|pom</packaging> 在某个项目的pom.xml中使用标签来声明该项目的父项目信息后,该项目就作为父项目的子项目出现,从而实现了maven的项目继承。 ##### 父项目集中管理依赖版本 创建tiangou_parent项目,pom.xml内容如下: xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.woniu</groupId> <artifactId>tiangou_parent</artifactId> <version>1.0-SNAPSHOT</version> <!-- <properties>的子标签可以自定义 --> <properties> <!-- 使用自定义标签存放依赖的版本信息,如需修改,只需要在此处进行改动即可 --> <spring.version>5.2.1.RELEASE</spring.version> </properties> <packaging>pom</packaging> <!-- 依赖管理:只做声明,不做实际导入。 一般会在子项目中通过<dependencies>标签来实际导入相关依赖。--> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <!-- 使用${自定义标签名}引用对应标签中存放的内容 --> <version>${spring.version}</version> </dependency> </dependencies> </dependencyManagement> </project> 创建tiangou_child项目,pom.xml内容如下: xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>tiangou_child</artifactId> <packaging>jar</packaging> <!-- 声明父项目信息 --> <parent> <groupId>com.woniu</groupId> <artifactId>tiangou_parent</artifactId> <version>1.0-SNAPSHOT</version> <!-- 设置父项目的pom.xml相对于本项目的路径 --> <relativePath>../tiangou_parent/pom.xml</relativePath> </parent> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> </dependency> </dependencies> </project> 在maven的继承中,父项目与子项目之间只有逻辑上的父子关系,也就是说子项目不会被包含在父项目中。 #### maven的聚合 maven项目的聚合是建立在继承的基础上的。
在父项目中使用标签将子项目引入父项目,此时就实现了maven项目的聚合,给父项目打包时,子项目的内容也会被打包进来。 xml <!-- 指定当前项目中应该包含的模块 --> <modules> <module>../tiangou_child</module> <module>../tiangou_child1</module> <module>../tiangou_child2</module> </modules> ##### 13 案例1 需求:使用spring-session结合redis完成session共享和伪单点登录。
步骤1:导入依赖 ```xml org.springframework.boot spring-boot-starter-web
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <!--spring-data-redis-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!--spring-session-->
    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-data-redis</artifactId>
        <version>2.2.1.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.43</version>
    </dependency>

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.0.5</version>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.3</version>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.62</version>
    </dependency>

</dependencies>
步骤2:配置文件
```yml
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql:///taotao
    username: root
    password: root

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

server:
  port: 8888

步骤3:开发实体类

@Data
@TableName("t_user")
public class User implements Serializable {
    @TableId(type=IdType.AUTO)
    private Integer uid;
    private String username;
    private String password;
}

步骤4:开发controller

@RestController
@RequestMapping("/user")
public class UserController {
    @Resource
    private UserService userService;
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @GetMapping("login")
    public Result login(User user, HttpSession httpSession){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username",user.getUsername());
        User userDB = userService.getOne(queryWrapper);
        if (ObjectUtils.isEmpty(userDB)) {
            return new Result(false,StatusCode.UNKNOWNACCOUNT,"用户尚未注册");
        }else{
            if (userDB.getPassword().equals(user.getPassword())) {
                httpSession.setAttribute("loginUserId",userDB.getUid());
                //将sessionId存储到redis中
                ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();
                valueOperations.set("loginUser:"+userDB.getUid(),httpSession.getId());

                return new Result(true,StatusCode.OK,"登录成功");
            }else{
                return new Result(false,StatusCode.INCORRECTCREDENTIALS,"密码错误");
            }
        }
    }

    @GetMapping("getAll")
    public Result getAll(){
        List<User> users = userService.list(null);
        return new Result(true,StatusCode.OK,"查询所有用户成功",users);
    }

}

步骤5:开发拦截器

public class LoginInterceptor implements HandlerInterceptor {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Object loginUserId = request.getSession().getAttribute("loginUserId");
        if (ObjectUtils.isEmpty(loginUserId)) {
            //未登录,应该拦截并提示未登录
            response.getWriter().write(JSONObject.toJSONString(new Result(false, StatusCode.ERROR,"当前用户尚未登录")));
            return false;
        }else{
            String sessionId = request.getSession().getId();
            String redisSessionId = stringRedisTemplate.opsForValue().get("loginUser:" + loginUserId);
            //如果redis中存储的sessionId与请求中的session一致,放行
            if (sessionId.equals(redisSessionId)) {
                return true;
            }else{//如果redis中存储的sessionId与请求中的session不一致,表示当前用户已经在其他地方进行了登录
                response.getWriter().write(JSONObject.toJSONString(new Result(false, StatusCode.ERROR,"当前用户已在其他设备登录")));
                return false;
            }
        }
    }
}

步骤6:注册拦截器

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public LoginInterceptor loginInterceptor(){
        return new LoginInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/user/login");
    }
}

步骤7:测试

# 将项目打包成两个,端口号分别为8888和9999,使用java -jar命令分别启动两个项目
# 使用8888端口登录项目,并访问getAll,可成功访问,使用9999端口登录项目,此时可以访问getAll,但再次使用8888端口访问时,出现无法访问,用户在其他位置被登录的提示。