Spring详解

焦旭峰 于 April 10, 2021

Spring详解

第一章 Spring概述

​ spring家族有spring,springmvc ,spring boot , spring cloud

​ spring出现是在2002左右,用来解决企业开发的难度,减轻对项目模块之间的管理,类和类之间的管理, 帮助开发人员创建对象,管理对象之间的关系。 ​ spring核心技术有ioc(控制反转), aop(面向切片编程)。能实现模块之间,类之间的解耦合。

​ 依赖: 类A中使用类 B的属性或者方法,则称类A依赖类B。


第二章 IOC

1、如何学习框架

​ 框架是一个软件,其它人写好的软件。

1)知道框架能做什么, mybatis–访问数据库, 对表中的数据执行增删改查。 2)框架的语法, 框架要完成一个功能,需要一定的步骤支持的, 3)框架的内部实现, 框架内部怎么做。 原理是什么。 4)通过学习,可以实现一个框架。

2、IOC简介

​ IoC (Inversion of Control) : 控制反转, 是一个理论,概念,思想。ioc描述的是把对象的创建,赋值,管理工作都交给代码之外的容器实现, 也就是对象的创建是由其它外部资源完成的。

控制创建对象,对象的属性赋值,对象之间的关系管理。反转把原来的开发人员管理、创建对象的权限转移给代码之外的容器实现。由容器代替开发人员管理、创建对象、给属性赋值。

正转:由开发人员在代码中,使用new 构造方法创建对象, 开发人员主动管理对象。

       public static void main(String args[]){
            Student student = new Student(); // 在代码中, 创建对象。--正转。
	   }

容器是一个服务器软件, 一个框架(spring)

3、为什么要使用IOC?

​ 目的是减少对代码的改动, 也能实现不同的功能。 实现解耦合。

4、Java中创建对象有哪些方式?

1. 构造方法 ,如 new Student()
2. 反射
3. 序列化
4. 克隆
5. ioc :容器创建对象
6. 动态代理

5、IOC的体现

IOC可以体现在 servlet中:

​ 1: 创建类继承自HttpServelt ​ 2: 在web.xml 注册servlet , 使用

    <servlet-name> myservlet </servlet-name>
    <servelt-class>com.bjpwernode.controller.MyServlet1</servlet-class>

​ 3.开发人员无需手动创建 Servlet对象,也就是没有 MyServlet myservlet = new MyServlet()

​ 4.Servlet是Tomcat服务器它能自动创建的。 Tomcat作为容器,里面存放的有Servlet对象, Listener , Filter对象。

6、IOC的技术实现

DI 是IOC的技术实现DI(Dependency Injection)依赖注入, 只需要在程序中提供要使用的对象名称就可以, 至于对象如何在容器中创建。赋值,查找都由容器内部实现。

Spring是使用的DI实现了IOC的功能, Spring底层创建对象,使用的是反射机制。Spring是一个容器,管理对象,给属性赋值, 底层是反射创建对象。

7、spring-context和spring-webmvc

spring-conetxt 和 spring-webmvc是spring中的两个模块、

spring-context:是ioc功能的,创建对象的。

spring-webmvc:做web开发使用的, 是servlet的升级。 spring-webmvc中也会用到spring-context中创建对象的功能。

8、单元测试junit

​ junit : 单元测试, 一个工具类库,做测试方法使用的。其中,单元:指定的是方法, 一个类中有很多方法,一个方法称为单元。

使用单元测试的步骤: 1.需要加入junit依赖

	  <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

2.创建测试作用的类:测试类 在src/test/java目录中创建测试类

3.创建测试方法

测试方法的特点: 1)public 方法 2)没有返回值 void 3)方法名称自定义,建议名称是test + 你要测试方法名称 4)方法没有参数 5)方法的上面加入 @Test ,这样的方法是可以单独执行的。 不用使用main方法。


9、基于XML的DI

9.1、set注入

​ set注入也叫做设值注入,spring框架调用类中的set方法,通过set方法可以完成属性赋值。

9.1.1、简单类型的set注入
<property name="属性名" value="属性的值"/>
8.1.2、引用类型的set注入
<property name="属性名” ref="bean的id"/>
9.2、构造注入

​ 构造注入是指,spring调用类的有参数构造方法,通过构造方法完成属性赋值。

9.2.1、使用name属性
<constructor-arg>的name属性,name表示构造方法的形参名
9.2.2、使用index属性
<constructor-arg>的index属性,表示构造方法形参的位置,从0开始
8.2.3、省略index属性

10、多个配置文件的优势

1.每个文件的大小比一个文件要小很多。效率高 2.避免多人竞争带来的冲突。

如果你的项目有多个模块(相关的功能在一起) ,一个模块一个配置文件。 学生考勤模块一个配置文件, 张三 学生成绩一个配置文件, 李四

10.1、多文件的分配方式
1. 按功能模块,一个模块一个配置文件
2. 按类的功能,数据库相关的配置一个文件配置文件, 做事务的功能一个配置文件, 做service功能的一个配置文件等

11、基于注解的DI

​ 通过注解完成java对象创建,属性赋值。

11.1、使用注解的步骤

1、加入maven的依赖 spring-context。在加入spring-context的同时, 会间接加入spring-aop的依赖。使用注解必须使用spring-aop依赖。

2、在类中加入spring的注解(多个不同功能的注解)

3、在spring的配置文件中,加入一个组件扫描器的标签,说明注解在你的项目中的位置

11.2、需要学习的注解

​ 1.@Component ​ 2.@Repository ​ 3.@Service ​ 4.@Controller ​ 5.@Value ​ 6.@Autowired ​ 7.@Resource

11.3、用户处理请求

​ 用户form ,参数name ,age—–Servlet(接收请求name,age)—Service类(处理name,age操作)—dao类(访问数据库的)—mysql

11.4、注解和xml的对比

​ 注解:方便、高效、直观。以硬编码的方式写入java代码,修改时需要重新编译代码

​ xml:配置和代码分离;在xml中做修改,无需编译代码,重启服务器即可将新的配置加载。编写麻烦,效率低,大型项目过于复杂。

11.5、注解方式的解耦合

例:

1、将需要赋值的属性写入配置文件test.properties中

myname=\u5F20\u4E09\u975Emyage=20

2、在spring配置文件中加载属性配置文件test.properties

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xmlns:context="http://www.springframework.org/schema/context"       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">    <context:component-scan base-package="com.scut.ba02"/>    <!--加载属性配置文件-->    <context:property-placeholder location="test.properties"/></beans>

3、通过${}在类中使用属性配置文件中的内容

package com.scut.ba02;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;public class Student {    //使用属性配置文件中的内容    @Value("${myname}")    private String name;    @Value("${myage}")    private Integer age;    @Override    public String toString() {        return "Student{" +                "name='" + name + '\'' +                ", age=" + age +                '}';    }}

==============================================================================================

第三章 AOP

1.动态代理(背)

jdk动态代理:使用jdk中的Proxy,Method,InvocaitonHanderl创建代理对象。jdk动态代理要求目标类必须实现接口。

cglib动态代理:第三方的工具库,创建代理对象,原理是继承。 通过继承目标类,创建子类。子类就是代理对象。 要求目标类不能是final的, 方法也不能是final的。

2.动态代理的作用(背)

1)在目标类源代码不改变的情况下,增加功能。 2)减少代码的重复 3)专注业务逻辑代码 4)解耦合,让你的业务功能和日志,事务非业务功能分离。

3.AOP(Aspect Orient Programming)面向切面编程

​ AOP是面向切面编程, 基于动态代理的,可以使用jdk,cglib两种代理方式。Aop就是动态代理的规范化, 把动态代理的实现步骤,方式都定义好了, 让开发人员用一种统一的方式,使用动态代理。 Aspect: 切面,给你的目标类增加的功能,就是切面。 像上面用的日志,事务都是切面。 ​ 切面的特点: 一般都是非业务方法,独立使用的。

3.1、怎么理解面向切面编程 ? (背)

1)需要在分析项目功能时,找出切面。 2)合理的安排切面的执行时间(在目标方法前, 还是目标方法后) 3)合理的安全切面执行的位置,在哪个类,哪个方法增加增强功能

3.2、术语(掌握)

​ 1)Aspect:切面,表示增强的功能, 就是一堆代码,完成某个一个功能。非业务功能, ​ 常见的切面功能有日志, 事务, 统计信息, 参数检查, 权限验证。

​ 2)JoinPoint:连接点 ,连接业务方法和切面的位置。 就是某个类中的业务方法 ​ 3)Pointcut : 切入点指多个连接点方法的集合。多个方法 ​ 4)目标对象: 给哪个类的方法增加功能, 这个类就是目标对象 ​ 5)Advice:通知,通知表示切面功能执行的时间。

需要掌握Aspect,Pointcut,Advice

3.3、切面的三个关键要素

1)切面的功能代码,切面干什么 2)切面的执行位置,使用Pointcut表示切面执行的位置 3)切面的执行时间,使用Advice表示时间,在目标方法之前,还是目标方法之后。

4、aop的实现(掌握)

aop是一个规范,是动态的一个规范化,一个标准

4.1、aop的技术实现框架

1、spring:spring在内部实现了aop规范,能做aop的工作。spring主要在事务处理时使用aop。我们项目开发中很少使用spring的aop实现。因为spring的aop比较笨重。

2、aspectJ: 一个开源的专门做aop的框架。spring框架中集成了aspectj框架,通过spring就能使用aspectj的功能。

4.2、aspectJ框架实现aop有两种方式

1、使用xml的配置文件 : 配置全局事务 2、使用注解,我们在项目中要做aop功能,一般都使用注解, aspectj有5个注解。

5、学习aspectj框架的使用

5.1、切面的执行时间(Advice)

​ 这个执行时间在规范中叫做Advice(通知,增强)。切面的执行时间在aspectj框架中使用注解表示的。也可以使用xml配置文件中的标签。 ​ 1)@Before ​ 2)@AfterReturning ​ 3)@Around ​ 4)@AfterThrowing ​ 5)@After

​ 需要掌握前置通知@Before,后置通知@AfterReturning,环绕通知@Around的使用,异常通知@AfterThrowing和最终通知@After了解即可。

5.2、切入点表达式(掌握)

​ 切入点表示式表示切面执行的位置。切入点表达式的格式如下:

execution(访问权限 方法返回值 方法声明(参数) 异常类型)

重点掌握下面五个例子。

例1、

execution(public * *(..)) 

指定切入点为:任意公共方法。

例2、

execution(* set*(..)) 

指定切入点为:任何一个以“set”开始的方法。

例3、

execution(* com.xyz.service.*.*(..)) 

指定切入点为:定义在service包里的任意类的任意方法。

例4、

execution(* com.xyz.service..*.*(..))

指定切入点为:定义在service包或者子包里的任意类的任意方法。“..”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类。

例5、

execution(* *..service.*.*(..))

指定切入点为:所有包下的serivce子包下所有类(接口)中所有方法为切入点

	com.service.impl	com.bjpowrnode.service.impl	cn.crm.bjpowernode.service	这些包中的service类均满足例5的切入点表达式

=========================================================================================================

6、使用jdk动态代理还是cglib动态代理

6.1、没有接口,spring自动使用cglib动态代理

​ 目标类没有接口时,使用cglib动态代理,spring框架会自动应用cglib。

6.2、有接口时,也可以使用cglib动态代理

如果你期望目标类有接口,使用cglib代理。需要在自动代理生成器标签中加入:proxy-target-class=”true”。这将告诉框架,要使用cglib动态代理。

即在spring配置文件中:

    <aop:aspectj-autoproxy proxy-target-class="true"/>

​ 如果你使用的自动代理生成器是:

    <aop:aspectj-autoproxy/>或者    <aop:aspectj-autoproxy proxy-target-class="false"/>

此时,将使用jdk动态代理。

第四章 Spring集成mybatis

1、通过ioc将mybatis和spring集成在一起

​ 使用ioc技术把mybatis框架和spring集成在一起,像一个框架一样使用。

1.1、为什么ioc能把mybatis和spring集成在一起,像一个框架?

​ 是因为ioc能创建对象。可以把mybatis框架中的对象交给spring统一创建, 开发人员从spring中获取对象。开发人员就不用同时面对两个或多个框架了, 就面对一个spring

2、spring整合mybatis的思路

2.1、回顾mybatis使用步骤

1.定义dao接口 ,StudentDao 2.定义mapper文件 StudentDao.xml 3.定义mybatis的主配置文件 mybatis.xml 4.创建dao的代理对象, StudentDao dao = SqlSession.getMapper(StudentDao.class);

List students = dao.selectStudents();

要使用dao对象,需要使用getMapper()方法,怎么能使用getMapper()方法,需要哪些条件? 1.获取SqlSession对象, 需要使用SqlSessionFactory的openSession()方法。 2.创建SqlSessionFactory对象。 通过读取mybatis的主配置文件,能创建SqlSessionFactory对象

需要SqlSessionFactory对象, 使用Factory能获取SqlSession ,有了SqlSession就能有dao , 目的就是获取dao对象 Factory创建需要读取主配置文件。

我们会使用独立的连接池类替换mybatis默认自己带的, 把连接池类也交给spring创建。

mybatis主配置文件: 1.数据库信息

 <environment id="mydev">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!--数据库的驱动类名-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <!--连接数据库的url字符串-->
                <property name="url" value="jdbc:mysql://localhost:3306/springdb"/>
                <!--访问数据库的用户名-->
                <property name="username" value="root"/>
                <!--密码-->
                <property name="password" value="123456"/>
</dataSource>
  1. mapper文件的位置

    <mappers>
         <mapper resource="com/bjpowernode/dao/StudentDao.xml"/>
         <!--<mapper resource="com/bjpowernode/dao/SchoolDao.xml" />-->
     </mappers>
    

==============================================================

2.2、通过以上的说明,我们需要让spring创建以下对象

1.独立的连接池类的对象, 使用阿里的druid连接池 2.SqlSessionFactory对象 3.创建出dao对象

需要学习就是上面三个对象的创建语法,使用xml的bean标签。

2.3、连接池:多个连接Connection对象的集合

List connlist : connList就是连接池

通常使用Connection访问数据库 Connection conn =DriverManger.getConnection(url,username,password); Statemenet stmt = conn.createStatement(sql); stmt.executeQuery(); conn.close();

使用连接池 在程序启动的时候,先创建一些Connection Connection c1 = … Connection c2 = … Connection c3 = … List connlist = new ArrayLits(); connList.add(c1); connList.add(c2); connList.add(c3);

Connection conn = connList.get(0); Statemenet stmt = conn.createStatement(sql); stmt.executeQuery(); 把使用过的connection放回到连接池 connList.add(conn);

Connection conn1 = connList.get(1); Statemenet stmt = conn1.createStatement(sql); stmt.executeQuery(); 把使用过的connection放回到连接池 connList.add(conn1);

==================================================================

第五章 spring的事务处理

1.什么是事务?

​ 事务是指一组sql语句的集合,集合中有多条sql语句。可能是insert , update ,select ,delete。我们希望这些多个sql语句都能成功,或者都失败, 这些sql语句的执行是一致的,作为一个整体执行。

2.什么时候使用事务?

​ 当我的操作,涉及得到多个表,或者是多个sql语句的insert,update,delete。需要保证这些语句都是成功才能完成我的功能,或者都失败,保证操作是符合要求的。

2.1、在java代码中写程序,控制事务,此时事务应该放在那里呢?

service类的业务方法上,因为业务方法会调用多个dao方法,执行多个sql语句

3.怎么处理事务 ?

​ jdbc访问数据库,处理事务 Connection conn ; conn.commit(); conn.rollback(); ​ mybatis访问数据库,处理事务, SqlSession.commit(); SqlSession.rollback(); ​ hibernate访问数据库,处理事务, Session.commit(); Session.rollback();

4.问题3中事务的处理方式,有什么不足?

1)不同的数据库访问技术,处理事务的对象,方法不同, 需要了解不同数据库访问技术使用事务的原理 2)掌握多种数据库中事务的处理逻辑。知道什么时候提交事务,什么时候回顾事务 3)需要知道处理事务的多种方法。

总结:就是多种数据库的访问技术,有不同的事务处理的机制,对象,方法。

5.怎么解决4中的不足?

spring提供了一种处理事务的统一模型, 能使用统一步骤,方式完成多种不同数据库访问技术的事务处理。

使用spring的事务处理机制,可以完成mybatis访问数据库的事务处理 使用spring的事务处理机制,可以完成hibernate访问数据库的事务处理。

6.处理事务,需要怎么做,做什么?

​ spring处理事务的模型,使用的步骤都是固定的。把事务使用的信息提供给spring就可以了

6.1、事务管理器接口PlatformTransactionManager

​ 使用事务管理器对象,完成事务内部提交,回滚事务。代替你完成commit,rollback。事务管理器是一个接口和他的众多实现类。 ​ 事务管理器接口:PlatformTransactionManager ,定义了事务重要方法 commit ,rollback ​ 事务管理器接口的实现类:spring把每一种数据库访问技术对应的事务处理类都创建好了。 ​ mybatis访问数据库—spring创建好的是DataSourceTransactionManager ​ hibernate访问数据库—-spring创建的是HibernateTransactionManager

6.1.1、怎么使用:你需要告诉spring 你用是那种数据库的访问技术,怎么告诉spring呢?

​ **声明数据库访问技术对于的事务管理器实现类, 在spring的配置文件中使用声明就可以了** 例如,你要使用mybatis访问数据库,你应该在xml配置文件中

  <bean id=“xxx" class="...DataSourceTransactionManager"> 
6.2、事务定义接口TransactionDefinition

​ 事务定义接口指定你的业务方法需要什么样的事务,说明需要事务的类型。

6.2.1、事务的隔离级别

​ 有4个值。 ​ DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle默认为 READ_COMMITTED。 ​ ➢ READ_UNCOMMITTED:读未提交。未解决任何并发问题。 ​ ➢ READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。 ​ ➢ REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读 ​ ➢ SERIALIZABLE:串行化。不存在并发问题。

6.2.2、事务的超时时间

​ 表示一个方法最长的执行时间,如果方法执行时超过了时间,事务就回滚。单位是秒, 整数值, 默认是 -1.。

6.2.3、事务的传播行为

​ 控制业务方法是不是有事务的, 是什么样的事务的。7个传播行为,表示你的业务方法调用时,事务在方法之间是如何使用的。

PROPAGATION_REQUIRED

PROPAGATION_REQUIRES_NEW

PROPAGATION_SUPPORTS 以上三个需要掌握

​ PROPAGATION_MANDATORY ​ PROPAGATION_NESTED ​ PROPAGATION_NEVER ​ PROPAGATION_NOT_SUPPORTED

6.3、spring提交事务,回滚事务的时机

​ 1)当你的业务方法,执行成功,没有异常抛出,当方法执行完毕,spring在方法执行后提交事务。(通过调用事务管理器commit方法)

​ 2)当你的业务方法抛出运行时异常或ERROR, spring执行回滚。(通过调用事务管理器的rollback方法) ​ 运行时异常的定义:RuntimeException和他的子类都是运行时异常, 例如NullPointException , NumberFormatException

​ 3) 当你的业务方法抛出非运行时异常, 主要是受查异常时,提交事务 ​ 受查异常:在你写代码中,必须处理的异常。例如IOException, SQLException

7、总结spring的事务

1、管理事务的是事务管理器接口和他的实现类 2、spring的事务是一个统一模型 1)指定要使用的事务管理器实现类,使用 2)指定哪些类,哪些方法需要加入事务的功能 3)指定方法需要的隔离级别,传播行为,超时

你需要告诉spring,你的项目中类信息,方法的名称,方法的事务传播行为。

8、spring框架中提供的事务处理方案

8.1、使用注解@Transactional(适合中小项目使用)

​ spring框架自己用aop实现给业务方法增加事务的功能, 使用@Transactional注解增加事务。@Transactional注解是spring框架自己的注解,放在public方法的上面,表示当前方法具有事务。可以给注解的属性赋值,表示具体的隔离级别,传播行为,异常信息等等

8.1.1、使用步骤

​ 1.需要声明事务管理器对象

<bean id="transactionManager" class="...DataSourceTransactionManager">

​ 2.开启事务注解驱动, 告诉spring框架,我要使用注解的方式管理事务 ​ spring使用aop机制,创建@Transactional所在的类代理对象,给方法加入事务的功能。 ​ spring给业务方法加入事务:在你的业务方法执行之前,先开启事务,在业务方法之后提交或回滚事务,使用aop的环绕通知。

	 @Around("你要增加的事务功能的业务方法名称")	 Object myAround(){       开启事务spring给你开启		  try{		     buy(1001,10);			  spring的事务管理器.commit();		  }catch(Exception e){         spring的事务管理器.rollback();		  }	 	 }

​ 3.在你的方法的上面加入@Trancational

8.2、使用aspectj框架的aop方式(适合大型项目使用)

​ 适合大型项目,有很多的类,方法,需要大量的配置事务,使用aspectj框架功能,在spring配置文件中声明类,方法需要的事务。这种方式业务方法和事务配置完全分离。

8.2.1、使用步骤

都是在xml配置文件中实现。 1)要使用的是aspectj框架,需要加入依赖

	<dependency>		<groupId>org.springframework</groupId>		<artifactId>spring-aspects</artifactId>		<version>5.2.5.RELEASE</version>	</dependency>

​ 2)声明事务管理器对象

<bean id="xx" class="DataSourceTransactionManager">

​ 3) 声明方法需要的事务类型(配置方法的事务属性【隔离级别,传播行为,超时】) ​ ​ 4) 配置aop:指定哪些哪类要创建代理

================================================================================

第六章 web项目中怎么使用容器对象。

1、javase和web项目创建容器对象的区别

1.1、javase项目

​ 做的是javase项目有main方法的,执行代码是执行main方法的,在main里面创建的容器对象 ApplicationContext ctx = new ClassPathXmlApplicationContext(“applicationContext.xml”);

1.2、web项目

​ web项目是在tomcat服务器上运行的。 tomcat一旦起动,项目是一直运行的。

2、使用Spring的监听器ContextLoaderListener

2.1、需求

​ web项目中容器对象只需要创建一次,把容器对象放入到全局作用域ServletContext中。

2.2、怎么实现

​ 使用监听器,当全局作用域对象被创建时,创建容器,存入ServletContext

监听器作用: 1)创建容器对象,执行 ApplicationContext ctx = new ClassPathXmlApplicationContext(“applicationContext.xml”); 2)把容器对象放入到ServletContext, ServletContext.setAttribute(key,ctx)

监听器可以自己创建,也可以使用框架中提供好的ContextLoaderListener

 private WebApplicationContext context;
 public interface WebApplicationContext extends ApplicationContext

 ApplicationContext:javase项目中使用的容器对象
WebApplicationContextweb项目中的使用的容器对象

把创建的容器对象放入到全局作用域
 key WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
       WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
 valuethis.context

 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

附录

1、使用archetype的webapp模板创建web项目时的web.xml文件如下:

<!DOCTYPE web-app PUBLIC
                "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
                "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
<display-name>Archetype Created Web Application</display-name>
</web-app>

其使用的web application是2.3版本,不支持EL表达式,版本过低。

我们在项目结构中将该xml文件删除:

image-20210105214124707

然后再添加为4.0的版本:

image-20210105214219649

最后将名称修改为web.xml

image-20210105214515866

此时,web.xml文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
         http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
</web-app>