1.1、基本了解
web相当于网页的意思,而web又分为静态web和动态web
- 静态web:html、css等,给人看到的数据不会发生变化,所用用户看到的都是同一个页面;
- 静态web存在的点击特效等等都是属于伪动态;
- 它无法和数据库交互(数据不能持久化)
- 动态web:提供给人看的数据会根据不同的人产生不同的数据,始终会发生变化;
- 动态web通过服务器可以与数据库交互;
- 动态web可能因为一些web资源出现问题,因此要维护代码,出现停机状态。
我们的javaWeb开发就属于动态web。
1.2、web应用程序基本了解
Web应用程序:是一种可以通过Web访问的应用程序,用户只需要有浏览器即可,不需要再安装其他软件。
web应用程序基本由(静态资源、JSP,Servlet、自定义类、工具类、配置文件)6个部分组成。这些资源会被统一整合放到同一个文件夹下。
在启动服务器的时候,通常要设置一个应用程序的环境路径(可以不设置),例如:它为/smbms,所有资源则必须要以/smbms为根目录摆放,在根目录下的资源可以直接下载或访问。
但一个应用程序中有一些要保密的资源。而web中/WEB-INF目录则解决了这个问题。存放在这个目录中的资源不能被客户端直接访问,直接访问这个目录下的资源则会报出404错误,可以让程序来取得其中的资源。/WEB-INF中的资源有着一定的名称和结构,例如:
- /WEB-INF/web.xml是部署描述文件(Servlet,过滤器,监听器等映射)
- /WEB-INF/classes是用来放置自定义类和配置文件等等
- /WEB-INF/lib用来放置web需要用到的Jar包(提醒:有时候web报错,可能是因为没有导入jar包)
所有我们能访问的资源,都是存在这个世界的某个计算机当中。
2.1、Tomcat服务器
Web服务器一般指网站服务器,是指驻留于因特网上某种类型计算机的程序,可以处理浏览器等Web客户端的请求并返回相应响应,也可以放置网站文件,让全世界浏览;可以放置数据文件,让全世界下载。
而我们这次Web的学习是使用Tomcat服务器。
2.1、Tomcat安装和配置
Tomcat官网:https://tomcat.apache.org/
在Tomcat官网上直接下载压缩包,解压成功后就可以在bin目录下找到并打开startup.bat文件
之后访问测试:https://localhost:8080/
Tomcat的默认端口号是8080,如果我们的端口号并占用可以修改它的端口号。在conif文件下找到server.xml中
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
修改port就可以了。
这里就测试完毕了。
这样我们可以先通过模仿来写我们第一个web网站
我们来到下面标红的路径可以发现,这里是我们测试路径localhost:8080网页的资源文件
在前面我们说过启动服务器需要设置环境路径,这里ROOT是Tomcat的默认路径,这样我们可以通过模仿,在webapps文件夹下新创建一个根目录,复写ROOT文件中的资源。把index.jsp修改成
<h1> HelloWorldh1>
之后启动服务器,通过环境路径就可以发布第一个web网站了
这里还没有结束,我们要在编译软件上使用Tomcat还需要进行配置。大家可以自行百度。
这里建议大家再安装一个Maven,这样就可以方便写代码的时候导包。
3、Servlet
什么是Servlet呢?
Servlet是Sun公司提出的一项技术,使用Servlet可以将Http的请求和响应封装在标准的java类中实现各种web。Servlet是使用Java语言编写的服务器端程序,它能够接受客户端的请求并产生响应。
Servlet的产生和原理
由上图可以了解到Servlet的产生和原理,这里要注意一下,每一个Servlet只执行一次init()方法,之后的处理客户端的请求不会再调用此方法,直接调用service()方法。大家可以通过源码更加深入了解一下。
Servlet的配置
当我们自定义Servlet类的时候,需要到WEB-INF文件中的web.xml配置Servlet
<servlet> <servlet-name>helloservlet-name> <servlet-class >com.feng.servlet.HelloServletservlet-class> servlet> <servlet-mapping> <servlet-name>helloservlet-name> <url-pattern>/hellourl-pattern> servlet-mapping>
在以上代码不难看出,每个Servlet都需要在web.xml中注册,这样才能确保自定义的Servlet类和服务器有关联。可能有人会想和有什么关系。
我们从下图可以看出这里使用访问顺序的,其中2和3的值是必须相同的。
而url-pattern标签中是我们在浏览器地址栏输入的url,两者相同则产生联系,然后2和3相同因为需要servlet-name映射到servlet-class才能访问到Servlet类。
这里再说一下,标签url-pattern中的/表示:https://localhost:端口号/ + 根目录(工程名)
从以上可以得出客户端的请求和自定义类能构成联系,主要的原因是servlet-name的关系。
在web.xml配置Servlet中,一个Servlet只可以指定一个映射路径,但是Servlet-mapping则可以指定多个映射路径。
ServletContext
ServletContext是Servlet上下文,Web容器启动的时候会为每个web程序创建一个对应的ServletContext对象,它代表了当前的web程序。这个对象是全局唯一的,程序中的所有Servlet都是共用这个对象。
ServletContext是一个域对象,不同的Servlet都可以拿到它的数据。
setAttribute(String name,Object value); | 添加数据是以key-value形式添加 |
getAttribute(String ); | 用key读取数据 |
removeAttribute(String name); | 用key删除数据 |
public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// this.getInitParameter() 初始化参数// this.getServletConfig() servlet配置// this.getServletContext() servlet上下文// 这里的this.getServletContext()返回的ServletContex对象相当于一个全局变量,所有都可以用。 ServletContext context = this.getServletContext(); String usename = "Java"; //数据 context.setAttribute("usename", usename); //这个可以理解成把这个键值对写入了web.xml中, /*就像获取初始化参数这个道理 username Java */ //将一个数据保存在了ServletContext中:键为:usename 值为:usename } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); }}
public class Getservlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { ServletContext context = this.getServletContext(); String usename = (String) context.getAttribute("usename"); resp.setContentType("text/html"); resp.setCharacterEncoding("utf-8"); PrintWriter writer = resp.getWriter(); writer.print("名字"+usename); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); }}
上面代码可以看出在不同的两个Servlet中ServletContext可以共享数据。
在上述的代码中,大家大概可以看到这段代码。
<context-param> <param-name>usernameparam-name> <param-value>Javaparam-value> context-param>
其实这段代码和this.getServletContext().setAttribute的本质是一样的,不同的是这里在web容器启动的时候已经初始化这个参数了。
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String usename = this.getServletContext().getInitParameter("usename"); System.out.println(username); }
用getInitParameter(String name)这个方法就能等到web.xml中的初始化参数了
HttpServletRequest和HttpServletResponse
web服务器接收到web客户端的请求,为了和请求联系上,分别创建了HttpServletRequest和HttpServletResponse,分别代表请求和响应。
HttpServletRequest代表客户端的请求,因为Http把所有信息都封装到这个这个对象中,所以我们在Servlet中可以通过HttpServletRequest获取客户端所有的信息。
这里我们说几个经常用到的方法
getContextPath(); | 获取web应用的根路径 |
getParameter(String name); | 获取名为name的参数单个值 |
getParameterValues(String name); | 获取名为name的参数的多个值 |
getAttribute(String name); | 获取名为name的属性值 |
setAttribute(String name,String value); | 设置名为name的属性值为value |
getRequerstDispatcher(String path); | 获取请求转发对象,转发到path地址中。所获得的对象需要forward()方法实现真正的跳转。 |
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8"); resp.setCharacterEncoding("utf-8"); String username = req.getParameter("username"); String password = req.getParameter("password"); String[] hobbys = req.getParameterValues("hobbys"); System.out.println(username); System.out.println(password); System.out.println(req.getContextPath()); //通过请求转发 //这里的/代表当前的web应用 req.getRequestDispatcher("/success.jsp").forward(req,resp); }
HttpServletResponse代表响应,响应数据回客户端。
setContentType(String type) | 设置响应的内容类型为type |
setCharacterEncoding(String charset) | 设置响应的编码字符集为charset。 |
有时候我们返回的类型不同,都需要用到serContentType(String type)来设置内容类型,例如设置Json之类的。而setCharacterEncoding(String charset)则是防止乱码,我们通常都设置成UTF-8字符集。
在HttpServletResponse负责发送数据的方法有:
HttpServletResponse.getOutputStream() | 获取输出流对象,所获对象需要借助里面输出方法才能发送数据 |
HttpServletResponse.getWriter() | 同上 |
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getOutStream().print("Hello world!");
PrintWriter out = resp.getWriter();
out.writer("Hello Servlet!");
}
响应中还有一个重要的功能,就是实现重定向。
重定向就是当A对B发送请求,B通知A你需要去访问C。
A-->B--(去访问C)->A-->C 相当于这个过程。
void sendRedirect(String url) throws IOException;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
/*重定向做了这两步
resp.setHeader("Location", "/r/imag");
resp.setStatus(302);
*/
resp.sendRedirect("/r/imag"); //重定向
}
这时候我们来说下重定向和转发的一些区别和相同点。
相同点
- 实现这个方法后,两者的页面都会发生跳转
不同点
- 请求转发的时候,url地址栏不会产生变化,为原本页面的url
- 重定向的时候,url地址栏会发生变化,为sendRedirect(String url)中的url
重定向和转发还需要注意一下相对路径和绝对路径的问题
相对路径response.sendRedirect("index.jsp")
http://localhost:8080/项目名/index.js
绝对路径response.sendRedirect("index.jsp")
http://localhost:8080/index.js
4、Cookie和Session
Cookie是存在于客户端(浏览器),是服务器发送回给浏览的一小片数据,浏览器可以把存储(cookie:一般会保存在本地的 用户目录下 appdata),等下一次请求的时候,会一起发送回服务器,一个浏览器可以创建多个cookie。cookie的作用通常是是用来判断请求是否来自同一个浏览器,就相当于通行证。
这个通行证就是session id,当服务器第一次接受到请求的时候,会创建一个Session对象,随着也会生成一个session id,并通过Response Header(响应头)中的Set-Cookie客户端发送要求设置cookie的响应,这样一来客户端中的cookie中的JSESSIONID就是session的id了。
因为cookie的默认周期是浏览器关闭,如果我们关闭了浏览器,cookie也随之关闭,因此cookie中的JSESSIONID发生了变化,就能判断出来session发生了变化,来自不同的浏览器。如果我们需要关闭浏览器后cookie还存在,我们可以设置cookie的有效期。
Cookie[] cookies = req.getCookies(); //获得Cookie
cookie.getName(); //获得cookie中的key
cookie.getValue(); //获得cookie中的vlaue
new Cookie("lastLoginTime", System.currentTimeMillis()+""); //新建一个cookie
cookie.setMaxAge(24*60*60); //设置cookie的有效期
resp.addCookie(cookie); //响应给客户端一个cookie
在cookie中我们也提到过session。Session的主要目的就是为了弥补Http的无状态特性。简单的说,就是服务器可以利用session存储客户端在同一个会话期间的一些操作记录;服务器用session id区分是否为同一个会话(cookie)。
- 服务器第一次接收到客户端请求后,服务器就会为这次请求开辟一块内存空间,这个对象便是Session对象,存储结构为ConcurrentHashMap。
- 一个Seesion独占一个浏览器,如果浏览器关闭了,重新打开浏览器,因为Session位于服务器,没有被删除,所以还存在着,但是因为Cookie发生了变化,所以这个原本的Session并不能作用于重新打开的浏览器上。
- 这里就要说到一个Session劫持的问题,比如我们登录用户中,如果没有勾选保留七天之类的,直接关闭掉浏览器,下次打开浏览器的时候我们就要重新输入账号密码。但是如果我们使用某种手段改写浏览器发出的HTTP请求报头,把原来的session id发送到服务器,则再次打开浏览器仍然能够找到原来session。
session被删除的情况:
- 程序调用HttoSession.invalidate();
- 上一次客户端发送sessionid超过了session最大有效时间
- 服务器停止
public class SessionDemo01 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//得到Session
HttpSession session = req.getSession();
//给Session中存东西
session.setAttribute("name","随笔月记");
//获取Session的ID
String sessionId = session.getId();
//判断Session是不是新创建
if (session.isNew()){
resp.getWriter().write("session创建成功,ID:"+sessionId);
}else {
resp.getWriter().write("session以及在服务器中存在了,ID:"+sessionId);
}
//移除Session中的属性
session.removeAttribute("name");
//手动注销Session
session.invalidate();
//Session创建的时候做了什么事情:这里就是给客户端中的Cookkie发送sessionID
// Cookie cookie = new Cookie("JSESSIONID",sessionId);
// resp.addCookie(cookie);
}
在web.xml中设置Session的有效时间
15
5、JavaBean
JavaBean是Java的可重用组件技术,在我看来实际就是一个实体类。
JavaBean的固有写法:
- 必须要有无参构造函数
- 属性私有化
- 属性必须有get()/set()方法,用作对属性的读写。
JavaBean经常用于更好的封装事务逻辑和数据库的字段映射,它主要目的是实现代码重用。
6、过滤器和监听器
Filter(过滤器)
如果设置了对应的过滤器,客户端访问相应的资源的时候会有对应的过滤器拦截。过滤器可以对请求内容做出改变或者重新发出请求。这里客户端和服务器都不需要知道过滤器的存在。
以上就是客户端访问有过滤器的客户端了。
Filter的使用要实现Filter接口,大家注意的是导包的时候,不要导错。
下面是我在网上找的一个解决乱码的过滤器。实现Filter接口后,要实现三个方法,在下面的代码中交代的很清楚。
并且要在web.xml中配置过滤器。
public class demo01 implements Filter {
//初始化:web服务器启动,就已经初始化了,随时等待过滤对象出现!
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("CharacterEncodingFilter初始化");
}
//Chain:链
/*
1.过滤中的所有代码,在过滤特定请求的时候都会执行
2.必须要让过滤器继续通行
filterChain.doFilter(request, response);
*/
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
request.setCharacterEncoding("utf-8"); //过来的请求
response.setCharacterEncoding("utf-8"); //过去的请求
response.setContentType("text/html;charset=UTF-8");
System.out.println("过滤器执行前");
filterChain.doFilter(request, response);//让我们的请求继续走,如果不写,程序到这里就被拦截停止了!
System.out.println("过滤器执行后");
}
//销毁:web服务器关闭的时候,过滤会销毁
public void destroy() {
System.out.println("CharacterEncodingFilter销毁");
}
}
CharacterEncodingFiltercom.filter.demo01CharacterEncodingFilter/servlet/*
监听器(了解即可)
监听器有许多种,它是根据不同类型来触发不同的触发器,就是说,你要用到监听器的时候,决于你的类型是什么。
例如:session监听,你就要实现HttpSessionListener接口
public class demo01 implements HttpSessionListener {
//每当有一个Session创建,就会执行sessionCreated事件,常用于统计网站的在线人数
public void sessionCreated(HttpSessionEvent se) {
}
//每当有一个Session销毁,就会执行sessionDestroyed事件。
public void sessionDestroyed(HttpSessionEvent se) {
}
}
监听器也需要在web.xml中注册
com.listener.demo01
7、JDBC
在javaWeb开发中,肯定少不了数据库的时候。这里我们就简单讲一下java如何连接数据库。
在数据库和Application有一个JDBC Driver Interface,这里可以成为统一驱动,因为有许多不同的数据库,所以会有许多不同的设置,因此设置了加多一层来统一连接方式。JDBC驱动程序是对JDBC规范完整的实现,它的存在在JAVA程序与数据库系统之间建立了一条通信的渠道。
由上图可以看出不同的数据库用不同的驱动来连接Application。这里我们用MySQL举列子。
java连接MySQL数据库必须要用的jar包:
- mysql-connector-java (MySQL 驱动)
- java.sql
- javax.sql
连接数据库的方式基本是固定的,如下:
1加载驱动
2 建立连接,此处代表数据库
3 执行SQL语句
4 处理结果集
5 关闭数据库
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//配置信息
//jdbc:mysql://localhost:3306/(数据库名字)?设置数据信息
String url = "jdbc:mysql://localhost:3306/jdbc?useUnicode=true&characterEncoding=utf8&&useSSL=true";
String username = "root";
String password = "123456";
//1.加载驱动
Class.forName("com.mysql.jdbc.Driver");
//2.连接数据库,代表数据库
Connection connection = DriverManager.getConnection(url, username, password);
//3.得到执行SQL语句的对象
Statement statement = connection.createStatement();
//4.编写sql
String sql = "select * from users;";
//5.执行SQL语句,并获得结果集
ResultSet rs = statement.executeQuery(sql);
//6.处理结果集
while (rs.next()) {
System.out.println(rs.getInt("id"));
System.out.println(rs.getString("name"));
System.out.println(rs.getString("password"));
System.out.println(rs.getString("email"));
System.out.println(rs.getDate("birthday"));
}
//7.关闭连接,释放资源,最后开的最先关闭。
rs.close();
statement.close();
connection.close();
}
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//配置信息
//jdbc:mysql://localhost:3306/(数据库名字)?设置数据信息
String url = "jdbc:mysql://localhost:3306/jdbc?useUnicode=true&characterEncoding=utf8&&useSSL=true";
String username = "root";
String password = "123456";
//1.加载驱动
Class.forName("com.mysql.jdbc.Driver");
//2.连接数据库,代表数据库
Connection connection = DriverManager.getConnection(url, username, password);
//3.编写sql
String sql = "select * from users where id = ?";
//4.预编译的sql,在后面直接执行就可以了
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//5.给sql语句中的占位符输入
//sql语句变成:select * from users where id = 1
preparedStatement.setObject(1, 1);
//6.获得结果集,因为已经预编译了sql语句,所以这里不用写sql的参数
ResultSet rs = preparedStatement.executeQuery();
//7.处理结果集
if (rs.next()) {
System.out.println(rs.getInt("id"));
System.out.println(rs.getString("name"));
System.out.println(rs.getString("password"));
System.out.println(rs.getString("email"));
System.out.println(rs.getDate("birthday"));
}
//8.关闭连接,释放资源,最后开的最先关闭。
rs.close();
statement.close();
connection.close();
}
Staement和PreparedStatement两者执行SQL语句的区别在于,后者是通过预编译执行的,这样可以防止注入问题。
事务
数据库使用中,还有一个特别重要的ACID原则:原子性、一致性、隔离性、持久性。
在执行增删查改的时候,要开启事务,防止数据丢失等等。事务总的来说就是,执行语句的时候,要么都成功,要么都失败。不能只成功一半,而后半部分丢失的情况。
10、MVC三层架构
MVC 模式(Model–view–controller)是软件工程中的一种软件架构模式,它把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。
每层的用处
Model:
- 业务处理 ------Service层
- 实现算法
- 数据管理(CRUD)-----Mapper层
Controller
- 接受请求,处理请求参数。
- 视图跳转(转发,重定向)
- 把业务交给业务层处理。
View
- 界面设计
- 与用户交互
- 展示数据
从上图就可以很好的理解MVC架构
与MVC架构出现之前相比,View层和Controller层是在一起,这样的架构高耦合,不易操作,难维护。
MVC架构的优点有:低耦合、代码可重用性、可维护性高等;
这样一来每一层的分工明确,更好的实现功能。