理解 Servlet
这是一个简单的 Servlet 例子,实现了登录,退出,登录用户可以进行 Hello World 服务和 Echo 服务。
web.xml
首先 web.xml 是 Java Web 项目的一个重要的配置文件,但是 web.xml 文件并不是 Java Web 工程必须的。
web.xml 文件是用来配置:欢迎页、servlet、filter 等。当你的web工程没用到这些时,你可以不用 web.xml 文件来配置你的 Web 工程。
web.xml 位于 web/WEB-INF/web.xml(如果你不是用 IDE 自动生成 web.xml 文件,请注意路径为 WEB-INF,而不是 WEB-INFO)
web.xml 加载顺序
首先简单讲一下,web.xml的加载过程。当启动一个 WEB 项目时,容器包括(JBoss、Tomcat等)首先会读取项目web.xml配置文件里的配置,当这一步骤没有出错并且完成之后,项目才能正常地被启动起来。
1. 启动WEB项目的时候,容器首先会去它的配置文件web.xml读取两个节点: <listener></listener> 和 <context-param></context-param>。
2. 紧接着,容器创建一个ServletContext(application),这个WEB项目所有部分都将共享这个上下文。
3. 容器以 <context-param></context-param> 的 name 作为键, value 作为值,将其转化为键值对,存入 ServletContext。
4. 容器创建 <listener></listener> 中的类实例,根据配置的class类路径 <listener-class></listener-class> 来创建监听。
举例:你可能想在项目启动之前就打开数据库,那么这里就可以在 <context-param> 中设置数据库的连接方式(驱动、url、user、password),在监听类中初始化数据库的连接。
在监听类中会有 contextInitialized(ServletContextEvent args) 初始化方法,启动 Web 应用时,系统调用Listener的该方法,在这个方法中获得:
ServletContext application = ServletContextEvent.getServletContext();
application.getInitParameter("context-param的键");
它还有销毁方法 contextDestroyed(ServletContextEvent args),关闭 Web 应用时,系统调用 Listener 的该方法,用于关闭应用前释放资源,比如:说数据库连接的关闭。
5. 接着容器会读取 <filter></filter> 根据指定的类路径来实例化过滤器。
6. 以上都是在WEB项目还没有完全启动起来的时候就已经完成了的工作。如果系统中有 Servlet,则 Servlet 是在第一次发起请求的时候被实例化的,而且一般不会被容器销毁,它可以服务于多个用户的请求。所以,Servlet的初始化都要比上面提到的那几个要迟。
总的来说,web.xml的加载顺序是: -> -> -> 。其中,如果 web.xml 中出现了相同的元素,则按照在配置文件中出现的先后顺序来加载。
对于某类元素而言,与它们出现的顺序是有关的。以 为例,web.xml 中当然可以定义多个 ,与 相关的一个元素是 ,注意,对于拥有相同 的 和 元素而言, 必须出现在 之后,否则当解析到 时,它所对应的 还未定义。web 容器启动初始化每个 时,按照 出现的顺序来初始化的,当请求资源匹配多个 时, 拦截资源是按照 元素出现的顺序来依次调用 doFilter() 方法的。 同 类似,此处不再赘述。
例子
<?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 应用的名称,可以在 http://localhost:8080/manager/html 中查看 -->
<display-name></display-name>
<!-- 告诉servlet/JSP容器,Web容器中部署的应用程序适合在分布式环境下运行。-->
<distributable></distributable>
<!-- <welcome-file-list> 元素可以包含一个或多个 <welcome-file> 子元素。如果在第一个 <welcome-file> 元素中没有找到指定的文件,Web 容器就会尝试显示第二个,以此类推。-->
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<!-- <context-param>元素含有一对参数名和参数值,用作应用的Servlet上下文初始化参数,参数名在整个Web应用中必须是惟一的,在web应用的整个生命周期中上下文初始化参数都存在,任意的Servlet和jsp都可以随时随地访问它。-->
<!-- <param-name>子元素包含有参数名,而<param-value>子元素包含的是参数值。-->
<!-- 作为选择,可用<description>子元素来描述参数。-->
<!-- 其他: [1] -->
<context-param>
<param-name>key</param-name>
<param-value>value</param-value>
</context-param>
<!-- 设置 session 超时 -->
<!-- 默认 30 minutes,设置在容器 /conf/web.xml 文件中-->
<!-- 该元素值必须为整数,以分钟为单位。如果 session-timeout元素的值为零或负数,则表示会话将永远不会超时-->
<session-config>
<session-timeout>120</session-timeout>
</session-config>
<filter>
<filter-name>filterHelloWorld</filter-name>
<filter-class>com.wyy.filter.FilterHelloWorld</filter-class>
<!-- 通过 FilterConfig 获取 -->
<init-param>
<param-name>key</param-name>
<param-value>value</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>filterHelloWorld</filter-name>
<url-pattern>/helloworld</url-pattern>
<!-- 通过 servlet name 配置-->
<servlet-name>helloworld</servlet-name>
<!-- 类型 -->
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<listener>
<listener-class>com.wyy.listener.ListenerApp</listener-class>
</listener>
<servlet>
<!-- 为Servlet指定一个文本描述。-->
<description></description>
<servlet-name>universal</servlet-name>
<servlet-class>com.wyy.control.UniversalServlet</servlet-class>
<!-- 为 Servlet 指定一个图标,在图形管理工具中表示该 Servlet。-->
<icon></icon>
<!-- 用来指定应用中JSP文件的完整路径。这个完整路径必须由 / 开始。-->
<jsp-file></jsp-file>
<!-- 如果 load-on-startup 元素存在,而且也指定了 jsp-file 元素,则 JSP 文件会被重新编译成 Servlet,同时产生的 Servlet 也被载入内存。<load-on-startup> 的内容可以为空,或者是一个整数。这个值表示由Web容器载入内存的顺序。-->
<!-- 举个例子:如果有两个Servlet元素都含有 <load-on-startup> 子元,则 <load-on-startup> 子元素值较小的 Servlet 将先被加载。如果 <load-on-startup> 子元素值为空或负值,则由Web容器决定什么时候加载 Servlet。如果两个Servlet的 <load-on-startup> 子元素值相同,则由Web容器决定先加载哪一个Servlet。<load-on-startup>1</load-on-startup> 表示启动容器时,初始化Servlet。-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>universal</servlet-name>
<!-- url-pattern 要么以斜杠开头,要么以 *.扩展名结尾 例如:/* 或 *.do -->
<!-- 不能同时出现这两种方式。例如 /*.do 是非法的 -->
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
[1]:配置 Spring,必须需要 ,而 可有可无,如果在 web.xml 中不写 配置信息,默认的路径是 /WEB-INF/applicationontext.xml,在WEB-INF目录下创建的 xml 文件的名称必须是 applicationContext.xml。如果是要自定义文件名可以在 web.xml 里加入 contextConfigLocation 这个 contex t参数:在
里指定相应的xml文件名,如果有多个xml文件,可以写在一起并以“,”号分隔。
Hello World 服务
我们先来写一个简单的服务,用户请求 /helloworld,页面返回 “hello word”。
HelloWorldServlet.java
public class HelloWorldServlet extends HttpServlet {
String message = "";
@Override
public void init() throws ServletException {
super.init();
message = "hello word";
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
OutputStream outputStream = response.getOutputStream();
outputStream.write(message.getBytes());
outputStream.close();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
web.xml
<servlet>
<servlet-name>helloworld</servlet-name>
<servlet-class>HelloWorldServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>helloworld</servlet-name>
<url-pattern>/helloworld</url-pattern>
</servlet-mapping>
Hello World 服务非常简单,用户请求 /helloworld,服务返回一个 “hello world” 字符串。
我们还可以编写一个 Echo 服务,用户请求 /echo ,服务返回用户 say 参数的值,用户的完整请求地址为 /echo?say=good,服务返回 “good” 字符串
Login 服务
接下来,我们写一个登录服务,我们假设用户都非常不重视安全,设置的用户名与密码相同,用户请求 /login,我们比较用户的 username 参数与 password 是否相等,相等则登录成功,否则失败。
User.java
public class User {
private String name;
private String password;
private Integer age;
private boolean isLogin;
public User(String name, String password, Integer age) {
this.name = name;
this.password = password;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public boolean isLogin() {
return isLogin;
}
public void setLogin(boolean login) {
isLogin = login;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", password='" + password + '\'' +
", age=" + age +
'}';
}
}
LoginServlet.java
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
OutputStream outputStream = response.getOutputStream();
String name = request.getParameter("name");
String password = request.getParameter("password");
if(name != null && password != null && name.equals(password)){
HttpSession session = request.getSession();
User user = new User(name, password, (int)(Math.random() * 100));
user.setLogin(true);
session.setAttribute(name, user);
Cookie cookie = new Cookie("user", name);
cookie.setMaxAge(60 * 5);
response.addCookie(cookie);
outputStream.write("login success".getBytes());
} else {
outputStream.write("login failure".getBytes());
}
outputStream.close();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
web.xml
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
我们还必须写一个退出登录,让用户退出。请求地址: /outLogin
未登录用户拦截
现在我们编写一个 Filter,只让登录的用户访问 Hello World 服务
IsLoginFiller.java
public class IsLoginFiller implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("IsLoginFiller init");
System.out.println("打印 web.xml 中配置的参数:");
Enumeration<String> enumerations = filterConfig.getInitParameterNames();
while (enumerations.hasMoreElements()) {
String name = enumerations.nextElement();
System.out.print(name + ":" + filterConfig.getInitParameter(name) + ",");
}
System.out.println();
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletResponse.setContentType("text/html");
OutputStream outputStream = servletResponse.getOutputStream();
HttpServletRequest request = (HttpServletRequest) servletRequest;
boolean flag = false;
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
if("user".equals(cookie.getName())){
HttpSession session = request.getSession();
User user = (User) session.getAttribute(cookie.getValue());
if(user != null && user.isLogin()){
filterChain.doFilter(servletRequest, servletResponse);
flag = true;
break;
}
}
}
if(!flag){
outputStream.write("please login".getBytes());
}
outputStream.close();
}
@Override
public void destroy() {
System.out.println("IsLoginFiller destroy");
}
}
web.xml
<filter>
<filter-name>isLogin</filter-name>
<filter-class>IsLoginFiller</filter-class>
<!--没有实际作用-->
<init-param>
<param-name>desc</param-name>
<param-value>is login</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>isLogin</filter-name>
<servlet-name>helloworld</servlet-name>
</filter-mapping>
从配置文件读取用户信息
我们的用户幡然醒悟,不再用简单的用户名和密码,现在我们将用户信息保存到 user.properties 文件中
AppListener.java
public class AppListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("App start");
ServletContext servletContext = servletContextEvent.getServletContext();
InputStream inputStream = null;
Map<String, User> userMap = new HashMap<>();
String path = servletContext.getInitParameter("path");
File file = new File(path);
Properties properties = new Properties();
if(file.exists() && file.isFile()){
try {
inputStream = new FileInputStream(file);
properties.load(inputStream);
String name = properties.getProperty("username");
String password = properties.getProperty("password");
String age = properties.getProperty("age");
if(name != null && password != null && age != null){
User user = new User(name, password, Integer.valueOf(age));
userMap.put(name, user);
}
servletContext.setAttribute("userMap", userMap);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("App close");
}
}
user.properties
username=good
password=idea
age=12
web.xml
<context-param>
<param-name>path</param-name>
<param-value>user.properties</param-value>
</context-param>
<listener>
<listener-class>demo.AppListener</listener-class>
</listener>
LoginServlet.java
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
OutputStream outputStream = response.getOutputStream();
ServletContext servletContext = request.getServletContext();
Map<String, User> userMap = (Map<String, User>) servletContext.getAttribute("userMap");
String name = request.getParameter("name");
String password = request.getParameter("password");
User user = userMap.get(name);
if(name != null && password != null && user != null && password.equals(user.getPassword())){
HttpSession session = request.getSession();
//user = new User(name, password, (int)(Math.random() * 100));
user.setLogin(true);
session.setAttribute(name, user);
Cookie cookie = new Cookie("user", name);
cookie.setMaxAge(60 * 5);
response.addCookie(cookie);
outputStream.write("login success".getBytes());
} else {
outputStream.write("login failure".getBytes());
}
outputStream.close();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
Thanks
https://blog.csdn.net/a376298333/article/details/79121548
https://www.cnblogs.com/linhuaming/p/9464356.html
https://blog.csdn.net/believejava/article/details/43229361#
https://blog.csdn.net/qq_19782019/article/details/80292110
https://blog.csdn.net/chenkun2016/article/details/79498618