第五章 Spring与AOP 简介

5.1 AOP编程术语

(1)、切面(Aspect)

切面泛指交叉业务逻辑。常用的切面有通知与顾问。

实际上就是对主业务逻辑的一种增强。

(2)、织入(Weaving)

将切面代码插入到目标对象的过程。

(3)、连接点(JoinPoint)

通常业务中接口中的方法均为连接点。

(4)、切入点(Pointcut)

指切面具体织入的那个连接点。

被标记为final的方法是不能作为连接点和切入点的。

(5)、目标对象(Target)

目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。

如StudentServiceImpl等。

(6)、通知(Advice)

通知是切入的一种实现。通知无法指定切入点,所有方法都会执行。

切入点定义切入的位置,通知定义切入的时间。

(7)、顾问(advisor)

顾问是切入的另一种实现。

能够将通知以更为复杂的方式织入到目标对象中,是将通知包装为更复杂切面的装配器。

第四章 Spring与IOC 基于注解的DI

DI-annotation

对于注解DI,不再需要在spring配置文件中声明Bean实例。

(1)、导入AOP的jar包。

注解的后台实现用到了AOP编程。

####(2)、添加约束头

约束在spring-framework-reference\html\xsd-configuration.html文件中

(3)、组件扫描器

配置文件中添加

<context:component-scan base-package="com.bjpowernode.di03"></context:component-scan>

Student.class

@Component("myStudent")//组件,表示该类被容器所管理
public class Student {
    @Value("zhangsan")
    private String name;
    @Value("23")
    private int age;

    private School school;

    ...

}

4.1 组件扫描器的base-package

扫描com.bjpowernode这个包及其子包

<context:component-scan base-package="com.bjpowernode"></context:component-scan>

扫描com.bjpowernode这个包的子包

<context:component-scan base-package="com.bjpowernode.*"></context:component-scan>

4.1 基于注解的DI - @Component相关注解

与@Component注解,功能相同但是意义不同的注解还有三个:

  • @Repository: 注解在Dao实现类上

  • @Service: 注解在Service实现类上

  • @Controller: 注解在SpringMVC的处理器上

4.2 基于注解的DI - @Scope

不指定的话,默认也是singleton

@Scope("prototype")
@Component("myStudent")
public class Student {
    @Value("zhangsan")
    private String name;
    @Value("23")
    private int age;

4.3 基于注解的DI - 域属性的注入

byName方式的注入,要求@Autowired与@Qualifier联合使用

@Scope("prototype")
@Component("myStudent")
public class Student {
    @Value("zhangsan")
    private String name;
    @Value("23")
    private int age;
    @Autowired
    @Qualifier("mySchool")
    private School school;//域属性的注解注入
    ...
}

4.3 基于注解的DI - 域属性的注入 - 使用@Resource注解

@Resource //byType的方式注入

@Resource(name=”mySchool”) //byName方式的注解注入

@Component("myStudent")    
public class Student {
    ...
    //@Resource //byType的方式注入
    @Resource(name="mySchool") //byName方式的注解注入
    private School school;//域属性的注解注入
    ...
}

4.4 基于注解的DI - 使用Spring的JUnit4测试

(1)、导入jar包

spring与junit4的整合jar包:spring-test.4.1.RELEASE.jar

测试类

@RunWith(SpringJUnit4ClassRunner.class)
//指定配置文件
@ContextConfiguration(locations="classpath:com/bjpowernode/di03/applicationContext.xml")
public class MyTest {
    //注解注入student
    @Resource(name="myStudent")
    private Student student;
    @Test
    public void test01() {
        System.out.println(student.toString());
    }
}

4.5 Spring与IOC DI-XML的优先级要基于注解的DI

同时存在基于XML的DI和基于注解的DI,XML的优先级要高。

第三章 Spring与IOC 基于XML的DI

Bean实例在调用无参构造器创建了空值对象后,就要对Bean对象的属性进行初始化。初始化是由容器自动完成的,称为注入。常用方式分为设值注入和构造注入。

3.1 基于xml-设值注入

通过set方法注入,基本数据类型用value,引用数据类型用ref。

applicationContext.xml

<bean id="mySchool" class="com.bjpowernode.di01.School">
    <property name="name" value="清华大学" />
</bean>

<bean id="myStudent" class="com.bjpowernode.di01.Student">
    <property name="name" value="张三" />
    <property name="age" value="23"></property>
    <property name="school" ref="mySchool"></property>
</bean>

School.class

public class School {
    private String name;
    public void setName(String name) {
        this.name = name;
    }

}

Student.class

public class Student {

    private String name;
    private int age;
    private School school;

    public Student() {
        super();
    }

    public void setSchool(School school) {
        this.school = school;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + ", school=" + school + "]";
    }

}

3.2 基于xml-为集合属性赋值

Some.class

public class Some {
    private School[] schools;
    private String[] myStrs;
    private List<String> myList;
    private Set<String> mySet;
    private Map<String,Object> myMap;
    private Properties myPros;
    ...
    set 方法
    ...
    @Override
    public String toString() {
        return "Some [schools=" + Arrays.toString(schools) + ", mystrs=" + Arrays.toString(myStrs) + ", myList="
                + myList + ", mySet=" + mySet + ", myMap=" + myMap + ", myPros=" + myPros + "]";
    }
}

applicationContext.xml

<bean id="mySchool1" class="com.bjpowernode.di02.School">
    <property name="name" value="清华大学" />
</bean>

<bean id="mySchool2" class="com.bjpowernode.di02.School">
    <property name="name" value="北京大学" />
</bean>

<bean id="mySome" class="com.bjpowernode.di02.Some">
    <property name="schools">
        <array>
            <ref bean="mySchool1"/>
            <ref bean="mySchool2"/>
        </array>
    </property>

    <property name="myStrs">
        <array>
            <value>山东</value>
            <value>临沂</value>
        </array>
    </property>

    <property name="myList">
        <list>
            <value>平邑</value>
            <value>一村</value>
        </list>
    </property>

    <property name="mySet">
        <set>
            <value>一街道</value>
            <value>二街道</value>
        </set>
    </property>

    <property name="myMap">
        <map>
            <entry key="mobile" value="15011111111"></entry>
            <entry key="qq" value="11111"></entry>
        </map>
    </property>

    <property name="myPros">
        <props>
            <prop key="age">12</prop>
            <prop key="sex">femal</prop>
        </props>
    </property>
</bean>

3.3 基于xml-域属性-自动注入

3.3.1 基于xml-autowire="byName"

会从容器中查找与Student类中域属性名scholl相同的Bean的id,并将该Bean对象自动注入给该域属性

<bean id="school" class="com.bjpowernode.di01.School">
    <property name="name" value="清华大学" />
</bean>

<bean id="myStudent" class="com.bjpowernode.di01.Student" autowire="byName">
    <property name="name" value="张三" />
    <property name="age" value="23"></property>
</bean>
3.3.2 基于xml-autowire="byType"

会从容器中查找与Student类中域属性类型School具有is-a关系的Bean,并将该Bean对象自动注入给该域属性

<bean id="mySchool" class="com.bjpowernode.di01.School">
    <property name="name" value="清华大学" />
</bean>

<bean id="myStudent" class="com.bjpowernode.di01.Student" autowire="byType">
    <property name="name" value="张三" />
    <property name="age" value="23"></property>
</bean>
3.3.3 基于xml-DI-SPEL注入
<bean id="myPerson" class="com.bjpowernode.di01.Person">
    <property name="pname" value="lisi"></property>
    <!-- 通过spring el表达式调用静态方法 -->
    <property name="page" value="#{T(java.lang.Math).random() * 50}"></property>
</bean>

<bean id="myStudent" class="com.bjpowernode.di01.Student">
    <!-- 调用其它Bean的属性 -->
    <property name="name" value="#{myPerson.pname}"></property>
    <!-- 运算 -->
    <property name="age" value="#{myPerson.page > 25 ? 25 : myPerson.page}"></property>
    <!-- 调用其它Bean的成员方法 -->
    <property name="age" value="#{myPerson.computeAge()}"></property>
</bean>

3.4 为应用指定多个Spring配置文件

(1)、平等关系的配置文件

两个配置文件:

  • com/bjpowernode/di01/spring-base.xml

  • com/bjpowernode/di01/spring-beans.xml

创建容器:

String source01 = "com/bjpowernode/di01/spring-base.xml";
String source02 = "com/bjpowernode/di01/spring-beans.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(source01,source02);

通配符

String source = "com/bjpowernode/di01/spring-*.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(source);
(2)、包含关系的配置文件

三个配置文件:

  • com/bjpowernode/di01/applicationContext.xml

  • com/bjpowernode/di01/spring-base.xml

  • com/bjpowernode/di01/spring-beans.xml

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
...
>
   <!--引入其它的配置文件-->     
    <import resource="classpath:com/bjpowernode/di01/spring-base.xml" />
    <import resource="classpath:com/bjpowernode/di01/spring-beans.xml" />

    <!--通配符-->
    <import resource="classpath:com/bjpowernode/di01/spring-*.xml" />

第二章 spring与IOC

控制反转(IOC,Inversion of control),是一个概念,是一种思想。指将传上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。

比较流行的两种实现方式有两种:

  • 依赖查找:Dependency Lookup, DL,容器提供回调接口与上下文环境给组件,程序代码则需要提供具体的查找方式。比较典型的是依赖于JNDI服务接口(java naming and directory interrface)的查找。

  • 依赖注入:Dependency Injection, DI,程序代码不做定位查找,这些工作由容器自行完成。

依赖注入是目前最优秀的解耦方式。依赖注入让spring的Bean之间以配置文件的方式组织在一起,而不是以硬编码的方式耦合到一起。

2.1 创建spring容器

public class MyTest {
    @Test
    public void test01() {
        SomeServiceImpl service = new SomeServiceImpl();
        service.doSome();
    }

    @Test
    public void test02() {
        //从当前应用的类路径下查找
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        ISomeService service = (ISomeService) ac.getBean("myService");
        service.doSome();
    }

    @Test
    public void test03() {
        //从当前应用的根路径下查找
        ApplicationContext ac = new FileSystemXmlApplicationContext("src/applicationContext.xml");
        ISomeService service = (ISomeService) ac.getBean("myService");
        service.doSome();
    }

    @Test
    public void test04() {
        BeanFactory bf = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
        ISomeService service = (ISomeService) bf.getBean("myService");
        service.doSome();
    }

}

applicationContext与BeanFactory两种容器的区别:对于其中的Bean的创建时机不同。

  • applicationContext容器在进行初始化时,会将其中的所有Bean对象进行创建。

    缺点:占用系统资源(内存、cpu等)

    优点:响应速度快

  • BeanFactory容器中的对象,在容器初始化时并不会创建,而是在真正调用时才会被创建

    缺点: 响应速度慢

    优点: 不多占用系统资源

2.2 Bean的装配

Bean的装配,即Bean的创建。

底层应用反射机制,通过调用无参构造器创建的Bean对象。

2.3 动态工厂Bean

applicationContext.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.xsd">

    <!-- 注册动态工厂 -->
    <bean id="factory" class="com.bjpowernode.service.ServiceFactory" />

    <!-- 注册Service:动态工厂Bean -->
    <bean id="myService" factory-bean="factory" factory-method="getSomeService" />

</beans>

动态工厂

public class ServiceFactory {
    public ISomeService getSomeService() {
        return new SomeServiceImpl();
    }
}

接口

public interface ISomeService {
    void doSome();
}

实现类

public class SomeServiceImpl implements ISomeService {

    @Override
    public void doSome() {
        System.out.println("execute dosome() method");
    }

}

测试类

public class MyTest {

@Test
public void test05() {
    ApplicationContext ac = new FileSystemXmlApplicationContext("src/applicationContext.xml");
    ISomeService service = (ISomeService) ac.getBean("myService");
    service.doSome();
}

}

2.4 静态工厂Bean

applicationContext.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.xsd">

    <!-- 注册Service:静态工厂Bean -->
    <bean id="myService" class="com.bjpowernode.service.ServiceFactory" factory-method="getSomeService" />

</beans>

静态工厂

public class ServiceFactory {
    public static ISomeService getSomeService() {
        return new SomeServiceImpl();
    }
}

2.5 Bean的作用域

<bean id="myService" class="com.bjpowernode.service.SomeServiceImpl" scope="singleton" />

scope="singleton" 单例模式:容器中对象的创建是在Spring容器初始化时就全部创建,是默认值

scope="prototype" 原型模式:容器中对象的创建时机不是在Spring容器初始化时创建,而是在代码中真正访问时才创建

ISomeService service1 = (ISomeService) ac.getBean("myService");

ISomeService service2 = (ISomeService) ac.getBean("myService");

默认,即单例模式下,service1与service2从容器中获取的是同一个对象

原型模式下,service1与service2分别创建新的对象

第一章 spring概述

1.1 概述

spring的核心是控制反转(IOC)和面向切面编程(AOP)。

spring的主要作用就是为代码“解耦”,降低代码间的耦合度。

spring根据代码功能特点,将降低耦合度的方式分为了两类:IOC与AOP。
IOC使得主业务在相互调用过程中,不用再自己维护关系了,即不用再自己创建使用的对象了。而是由spring容器统一管理,自动“注入”。而AOP使得系统级服务得到了最大复用,且不用由程序员手工将系统级服务“混杂”到主业务逻辑中了,而是由spring容器统一完成“织入”。

session&cookie

1.1 cookie简介

cookie是1993年由网景公司前雇员发明的一种进行网络会话状态跟踪的技术。

会话是由一组请求与响应组成,是围绕着一种相关事情所进行的请求与响应。所以这些请求与响应之间一定是需要有数据传递的,即需要进行会话状态跟踪。然而HTTP协议是一种无状态协议,在不同的请求之间是无法进行数据传递的。此时就需要一种可以进行请求之间进行数据传递的会话跟踪技术,而cookie就是一种这样的技术。

cookie是由服务器生成,保存在客户端的一种信息载体。这个载体中存放着用记访问该站点的会话状态信息。只在cookie没有被清空,或者cookie没有失效,那么,保存在其中的会话状态就有效。

用户在提交第一次请求后,由服务器生成cookie,并将其封装到响应头中,以响应的形式发送给客户端。客户端接收到这个响应后,将cookie保存到客户端。当客户端再次发送同类请求后(资源路径相同,资源名称不同),在请求中会携带保存在客户端的cookie数据,发送到客户端,由服务器对会话进行跟踪。

cookie技术并不是jvavaweb开发的专属技术,而是属于web开发的技术,是所有web开发语言均支持的技术。

cookie是由若干键值对构成,键值对均为字符串。

1.2 javaEE中的cookie

在javaEE中的javax.servlet.http包中存在一个类Cookie,就是用于完成会话跟踪的Cookie。其只有一个带参构造器。

默认绑定路径

访问路径由资源路径与资源名称构成。默认情况下,cookie与访问路径中的资源路径绑定。只要用户发出带有绑定资源路径的请求,则在请求头部,将会自动携带与之绑定的cookie路径。

生成cookie
public class SomeServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.getWriter().append("Served at: ").append(request.getContextPath());
        //创建两个Cookie
        Cookie cookie = new Cookie("company","abc");
        Cookie cookie2 = new Cookie("teacher","def");

        //指定cookie绑定的资源路径。必须加上项目名称
        cookie.setPath(request.getContextPath() + "/aaa/bbb/ccc");
        cookie2.setPath(request.getContextPath() + "/ddd/eee/fff");

        //设置cookie有效期
        //大于0,表示将cookie存放到客户端硬盘
        //小于0,与不设置一样,会将cookie存放到浏览器的缓存
        //等于0,表示cookie一生成,马上失效
        cookie.setMaxAge(60 * 60);
        cookie2.setMaxAge(60 * 60 * 24 * 10);

        //向响应中添加Cookie
        response.addCookie(cookie);
        response.addCookie(cookie2);

    }
}
获取cookie
public class OhterServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.getWriter().append("Served at: ").append(request.getContextPath());

        Cookie[] cookies = request.getCookies();

        for(Cookie cookie:cookies) {
            System.out.println(cookie.getName() + "===" + cookie.getValue());
            if(cookie.getName().equals("teacher") && cookie.getValue().equals("aaa")) {
                //...
            } else {
                //..
            }
        }
    }

}

2.1 HttpSession

session,即会话,是web开发中的一种会话状态跟踪技术。不同的是,cookie是将会话状态保存在了客户端,而session是将会话状态保存在了服务器端。

在javaweb开发中,session是以javax.servlet.HttpSession的接口对象的形式出现。


对于request的getSession()的用法:

  • 一般情况下,若要向session域中写入数据,则需要使用getSession(true),即getSession()方法。意味着有老的session用老的,没有老的则创建新的。

  • 若要从session中读取数据,则需要使用getSession(false)。意味着有老的session用老的,没有老的返回null。


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
    <form action = "FirstServlet" method = "POST">
        用户名:<input type = "text" name = "username" />
        <input type = "submit" value = "提交" />
    </form>
</body>
</html>

FirstServlet

public class FirstServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取用户提交的数据
        String username = request.getParameter("username");

        //将参数放到request域
        request.setAttribute("user", username);

        //获取session对象
        HttpSession session = request.getSession();

        //向session域中写入数据
        session.setAttribute("username", username);

        response.getWriter().print("SomeServlet" + username);

    }

}

SecondServlet

public class SecondServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //从request域中读取user属性
        Object user = request.getAttribute("user");

        //获取session
        HttpSession session = request.getSession(false);

        //从session中读取指定属性
        String username = null;
        if(session != null) {
            username = (String) session.getAttribute("username");
        }

        PrintWriter out = response.getWriter();
        out.println("user = " + user);
        out.println("username = " + username);
        out.println("session = " + session);
    }

}

2.2 Session的工作原理

在服务器中系统会为每个会话维护一个Session。不同的会话,对应不同的Session。那么,系统是如何识别Session对象的?即是如何做到在同一个会话过程中,一直使用的是同一个Session对象呢?

(1)、写入Session列表

服务器对当前应用中的Session是以Map的形式进行管理的,这个Map称为Session列表。该Map的key为一个32位长度的随机串,这个随机串称为JSessionID,value则为Session对象的引用。

当用户每一次提交请求时,服务端Servlet中执行到request.getSession()方法后,会自动生成一个Map.entry对象,key为一个根据某种算法新生成的JSessionID,value则为新创建的HttpSession对象。

(2)、服务器生成并发送cookie

在将session信息写入session列表后,系统还会自动将“JSessionID”作为name,这个32位长度的随机串作为value,以cookie的形式存放到响应报头中,并随着响应,将该cookie发送到客户端。

http://localhost:8080/sessioncookie
提交表单,跳转到FirstServlet后,

Response Headers

Content-Length: 13

Date: Fri, 27 Apr 2018 07:54:40 GMT

Set-Cookie: JSESSIONID=DCCE10CC2F5A6FAE40B716CA91B01EB0; Path=/sessioncookie; HttpOnly
(3)、客户端接收并发送cookie

客户端接收到这个cookie后会将其存放到浏览器缓存中。即,只要客户端浏览器不关闭,浏览器缓存中的cookie就不会消失。

当用户提交第二次请求时,会将缓存中的这个cookie,伴随着请求的头部信息,一块发送到服务端。

再次发送请求时,http://localhost:8080/sessioncookie/SecondServlet

Request Headers

...

Cookie: JSESSIONID=DCCE10CC2F5A6FAE40B716CA91B01EB0

Host: localhost:8080

(4)、从session列表中查找

服务端从请求中读取到客户端发送来的cookie,并根据cookie的SeesionID的值,从Map中查找相应key所对应的value,即session对象。然后,该session的域属性进行读写操作。

2.3 Session的失效

若某个session在指定的时间内一直未被访问,那么session将超时,即将失效。

在web.xml中可以通过<session-config />标签设置session的超时时间,单位为分钟。默认的超时时间为30分钟。时间从最后一次访问开始计时。

<session-config>
    <session-timeout>120</session-timeout>
</session-config>

若未到超时时间,也可以通过HttpSession中的方法invalide(),使得session失效。

2.4 cookie禁用后的session

cookie禁用后,从someServlet跳转到otherServlet时,

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取用户提交的数据
        String username = request.getParameter("username");

        //获取session对象
        HttpSession session = request.getSession();

        //向session域中写入数据
        session.setAttribute("username", username);

        //跳转到otherServlet
        response.sendRedirect(request.getContextPath() + "/otherServlet");    
    }

someServlet发送到客户端的包含有sessionid的cookie并没有被客户端保存,所以在发送otherServlet请求时,没有将该包含有sessionid的cookie发送到服务端,因此otherServlet服务端找不到该session,也就不能读取里面的数据。

解决方法:

request.encodeRedirectURL()方法

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //获取用户提交的数据
    String username = request.getParameter("username");

    //获取session对象
    HttpSession session = request.getSession();

    //向session域中写入数据
    session.setAttribute("username", username);

    //跳转到otherServlet
    String uri = request.getContextPath() + "/otherServlet";
    uri = request.encodeRedirectURL(uri);
    response.sendRedirect(uri);    
}

这样的话,跳转到otherServlet时,会将sessionid包含到访问地址中。

http://localhost/8080/sessioncookie/otherServlet;jsessionid=6BD1D909077DF0798ASSSJAK8200EC12

这样的话,发送请求到OtherServlet时,就会将该sessionid发送到服务端。

2.5 cookie禁用后非重定向跳转时session的跟踪

HttpServletResponse具有一个方法encdodeURL(),可以完成类似超链接这样的非重定向页面跳转的URL的重写,即在其路径后自动添加jsessionid

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //获取用户提交的数据
    String username = request.getParameter("username");

    //获取session对象
    HttpSession session = request.getSession();

    //向session域中写入数据
    session.setAttribute("username", username);

    //跳转到otherServlet
    response.setContentType("text/html;charset=utf-8");
    PrintWriter out = response.getWriter();
    String uri = "otherServlet";
    uri = response.encodeURL(uri);
    out.println("<a href='" + uri + "'>跳转</a>到otherservlet");
}

第3章 jsp系统开发模型

第3章 系统开发模型

3.1.1 纯jsp

3.1.2 jsp+javaBean的Model1

广义的javaBean分为数据承载Bean和业务处理Bean

狭义的javaBean,需要满足javaBean规范。

  • public 修饰
  • 实现Serializable接口
  • 无参构造器
  • 成员变量私有化,且提供getter和setter

3.1.3 MVC的Modle2

3.1.4 三层架构

View层:视图层、表现层、Web层。

Service层:业务层、逻辑层。

Dao层:持久层、数据访问层。data access object

三层架构的设计中,采用面向抽象编程。即上层对下层的调用,是通过调用接口的方法实现的。而下层对上层真正的服务提供者,是下层接口的实现类。

提供报务的接口相同,服务的实现类可以更换,实现了层间解耦。

3.1.5 MVC+三层架构

View Level:Contoller(Servlet) + View(jsp)

Service Level:Interfaces + Impls

Dao Level:Interfaces + Impls

将MVC的Model分为了两层:Service层与Dao层,分别用于处理业务逻辑和持久化操作。

3.2 学生注册登录系统

package: com.test.beans

//定义狭义的javaBean Student

public class Sdudent implements Serializable {

    private static final long serialVersionUID = 1L;
    private Integer id;//业务无关主键
    private String password;
    private String num;//学号
    private String name;
    private String age;
    private double score;
    public Sdudent() {
        super();
    }
    public Sdudent(String num, String name, String age, double score) {
        super();
        this.num = num;
        this.name = name;
        this.age = age;
        this.score = score;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public String getNum() {
        return num;
    }
    public void setNum(String num) {
        this.num = num;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAge() {
        return age;
    }
    public void setAge(String age) {
        this.age = age;
    }
    public double getScore() {
        return score;
    }
    public void setScore(double score) {
        this.score = score;
    }
    @Override
    public String toString() {
        return "Sdudent [id=" + id + ", password=" + password + ", num=" + num + ", name=" + name + ", age=" + age
                + ", score=" + score + "]";
    }

}

建表sdudent

//login.jsp 修改欢迎页面为login.jsp

<form action="${pageContext.request.contextPath }/loginServlet">
    学号:<input type="text" name="num" />
    密码:<input type="text" name="password" />
    <input type="submit" value="登录" />
</form>
<a href="${pageContext.request.contextPath}/register.jsp">注册</a>

//register.jsp

${message }<br />
<form action="${pageContext.request.contextPath }/registerServlet">
    学号:<input type="text" name="num" />
    密码:<input type="text" name="password" />
    <input type="submit" value="登录" />
</form>

//连接数据库,工具类

package com.test.utils;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class JdbcUtils {
    //加载DB驱动
    static {
        //将驱动mysql-connector-java-5.1.7-bin.jar放到lib下
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    private static Connection conn;        

    //获取connection对象
    public static Connection getConnection() throws SQLException {

        String url = "jdbc:mysql://127.0.0.1:3306/test";
        String user = "root";
        String password = "";
        if(conn == null || conn.isClosed()) {
            conn = DriverManager.getConnection(url, user, password);
        }
        return conn;

    }

    //关闭资源
    public static void close(Connection conn, Statement stmt, ResultSet rs) throws SQLException {

        if(conn != null && !conn.isClosed()) {
            conn.close();
        }
        if(stmt != null && !stmt.isClosed()) {
            stmt.close();
        }
        if(rs != null && !rs.isClosed()) {
            rs.close();
        }

    }

}

//登录的servlet

package com.test.servlets;

public class LoginServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1、接收请求参数
        String num = request.getParameter("num");
        String password = request.getParameter("password");

        HttpSession session = request.getSession();

        if(num == null || "".equals(num.trim())) {
            session.setAttribute("message", "输入不能为空");
            response.sendRedirect(request.getContextPath() + "/login.jsp");
            return;
        }

        //2、创建Service对象
        IStudentService service = new StudentServiceImpl();

        //3、调用Service对象的checkUser验证方法
        Student sdudent = service.checkUser(num, password);

        //4、验证失败,跳转到登录页面
        if(sdudent == null) {
            session.setAttribute("message", "输入错误");
            response.sendRedirect(request.getContextPath() + "/login.jsp");
            return;
        }

        //5、验证通过,跳转到index.jsp
        response.sendRedirect(request.getContextPath() + "/index.jsp");

    }
}

//注册的servlet

package com.test.servlets;
public class RegisterServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //1、获取表单参数
        request.setCharacterEncoding("UTF-8");
        String num = request.getParameter("num");
        String password = request.getParameter("password");
        String name = request.getParameter("name");
        String ageStr = request.getParameter("age");
        String scoreStr = request.getParameter("score");

        Integer age = Integer.valueOf(ageStr);
        Double score = Double.valueOf(scoreStr);

        //2、创建Student对象
        Student student = new Student(num,name,age,score);
        student.setPassword(password);

        //3、创建Service对象
        IStudentService service = new StudentServiceImpl();

        //4、调用Service对象的saveStudent()方法,写入DB
        Integer id = service.saveStudent(student);

        //5、写入失败,跳转到注册页面,重新注册
        if(id == null) {
            response.sendRedirect(request.getContextPath() + "/register.jsp");
        }

        //6、写入成功,跳转到登录页面
        response.sendRedirect(request.getContextPath() + "/login.jsp");
    }

}

service层

//服务层接口

package com.test.service;

import com.test.beans.Student;

public interface IStudentService {
    //对登录用户进行验证
    Student checkUser(String num, String password);

    //向DB中添加Student
    Integer saveStudent(Student student);
}

//服务层实现类

package com.test.service;

import com.test.beans.Student;
import com.test.dao.IStudentDao;
import com.test.dao.SdudentDaoImpl;

public class StudentServiceImpl implements IStudentService {
    private IStudentDao dao;

    public StudentServiceImpl() {
        dao = new SdudentDaoImpl();
    }

    @Override
    public Student checkUser(String num, String password) {
        return dao.selectStudentLogin(num,password);
    }

    @Override
    public Integer saveStudent(Student student) {
        return dao.insertStudent(student);
    }

}

//dao层

//dao层接口

package com.test.dao;

import com.test.beans.Student;

public interface IStudentDao {

    Student selectStudentLogin(String num, String password);

    Integer insertStudent(Student student);

}

//dao层实现类

package com.test.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import com.test.beans.Student;
import com.test.utils.JdbcUtils;

public class SdudentDaoImpl implements IStudentDao {
    private Connection conn;
    private Statement stmt;
    private PreparedStatement ps;
    private ResultSet rs;

    @Override
    public Student selectStudentLogin(String num, String password) {
        Student student = null;
        try {
            conn = JdbcUtils.getConnection();
            String sql = "select * from student where num=? and password=?";
            ps = conn.prepareStatement(sql);
            ps.setString(1, num);
            ps.setString(2, password);
            rs = ps.executeQuery();
            if(rs.next()) {
                student = new Student();
                student.setId(rs.getInt("id"));
                student.setNum(rs.getString("num"));
                student.setPassword(rs.getString("password"));
                student.setName(rs.getString("name"));
                student.setAge(rs.getInt("age"));
                student.setScore(rs.getDouble("score"));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                JdbcUtils.close(conn, ps, rs);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return student;
    }

    @Override
    public Integer insertStudent(Student student) {
        Integer id = null;
        try {
            conn = JdbcUtils.getConnection();
            String sql = "insert into student(num,password,name,age,score) values(?,?,?,?,?)";
            ps = conn.prepareStatement(sql);
            ps.setString(1, student.getNum());
            ps.setString(2, student.getPassword());
            ps.setString(3, student.getName());
            ps.setInt(4, student.getAge());
            ps.setDouble(5, student.getScore());

            ps.executeUpdate();

            sql = "select @@identity newId";
            ps = conn.prepareStatement(sql);
            rs = ps.executeQuery();
            if(rs.next()) {
                id = rs.getInt("newId");
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                JdbcUtils.close(conn, ps, rs);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return id;
    }

}

第2章 JSP核心

第2章 JSP核心

2.1 内置对象

2.1.1 pageContext

javax.servlet.jsp.PageContext

页面上下文,具有一个只在当前页面范围的域属性空间,即其具有setAttribute()方法和getAttribute()方法。

EL表达式,访问变量时,只能访问域属性空间内的变量。因此,可以将变量先存放到该域属性空间,或其它三个域属性空间(request、session、application)。

pageContext具有一些get方法,可以获取Request、Response、Session、ServletContext、ServletConfig、page(即当前Servlet)、exception、out等其它八个内置对象

2.1.2 application

即ServletContext

ServletContext所具有的方法,application都有。

常用的方法,例如:

void setAttribute(String name, Object object);

Object getAttribute(String name);

Object removeAttribute(String name);

2.1.3 out

所属类型:javax.servlet.jsp.JspWriter

JspWriter类继承自IO流的Writer类。

即out就是一个输出流。

2.1.4 page

查看jsp翻译的servlet,page对象即Servlet对象本身。

final java.lang.Object page = this.

2.1.5 exception

普通的jsp页面中,不能使用exception内置对象。因为打开jsp翻译的servlet,发现其中并没有exception对象。

若要在页面中直接使用exception对象,则需要配合着page指令使用。

2.1.6 其它对象

request,response,session,config,用法与servlet时的用法相同。

2.2 JSP指令(directive)

jsp中包含三类指令:

page指令
include指令
taglib标签库指令

<%@ 指令名称 属性名 = 属性值 属性名 = 属性值 %>

2.1.1 page指令

(1)、pageEncodeing属性

<%@ page pageEncoding = “utf-8”%>

默认的MIME类型为text/html

//servlet
response.setContentType(“text/html;charset=UTF-8”);

(2)、contentType属性

<%@ page contentType = “text/html; charset=UTF-8” %>

//servlet
response.setContentType(“text/html;charset=UTF-8”);

可以对pageEncoding默认的MIMe类型进行修改

(3)、import属性

<%@ page import = “java.util.Date”%>

(4)、errorPage\isErrorPage属性

<%@ page errorPage = "/error.jsp" %>

//error.jsp

<%@ page isErrorPage = "true"%>
<html>
    <body>
        error page<br />
        ex = <%=exception.getMessage() %>
    </body>
</html>

//serlvet

public void _jspService(...){
    ...
    java.lang.Throwable exception = ...;
    ...
}

(5)、Session属性

//hello1.jsp

<body>
    <%
        session.setAttribute("user","zhangsan");
    %>
</body>

//servlet

session = pageContext.getSession();

查看源码发现,返回的还是request.getSession()

1、 设置

HttpSession session = request.getSession(true);
session.setAttribute(…);

设为true,意味着没有session对象时才创建,有则不创建。

2、读取

HttpSession session = request.getSession(false);
if(session != null){
session.getAttribute(“…”);
}

设为false,意味着获取现有的session对象,如果没有,也不创建。

//hello2.jsp

<%@ page contentType = "text/html; charset = UTF-8"%>
<%-- 清除内置的Session对象 --%>
<%@ page session = "fasle"%>

<body>
    HttpSession session =     request.getSession(false);
    if(session != null){
        String user = (String) session.getAttribute("user");
        out.print("user = " + user);
    }        
<body/>

2.2.2 include指令

(1)、用法

可以包含动态文件也可以包含静态页面文件。

index.jsp

<html>
<body>
    index page before<br />

    <%@ include file = "/next.jsp"%>

    index page after<br />
</body>
</html>

next.jsp

<html>
<body>
    next page
</body>
</html>

(2)、静态联编

只生成了一个index_jsp.java的servlet源文件,没有生成next_jsp.java文件。

jsp翻译引擎,将include指令包含的next.jsp文件直接翻译到了index.jsp对应的servlet中,形成了一个.java文件。

该包含操作是在编译之前,由jsp翻译引擎完成的,而不是在程序运行期间完成的。

这种包含是一种静态包含,称为静态联编。

整个过程只一个_jspService()方法。也就是说,index.jsp和next.jsp之间是可以相互访问局部变量的。

2.3 JSP动作(Action)

页面中,应该使用EL表达式、JSTL标签及JSP动作,来代替java代码块、表达式。

JSP动作的语法格式

<jsp:动作名称 属性名 = 属性值 属性名 = 属性值 ...></jsp:动作名称>

<jsp:动作名称 属性名 = 属性值 属性名 = 属性值 ... />

实际开发中,常用的就两个forward和include。

底层使用的是request.getRequestDispatcher("").forward(...)和.include()

2.3.1 forward动作

index.jsp

<body>
    index page befor <br />

    <jsp:forward page="/next.jsp"></jsp:forward>

    index page after <br />

<body/>

next.jsp

<body>
    next page
</body>

forward跳转时,只显示

next page

2.3.2 include动作

index.jsp

<body>
    index page befor <br />

    <jsp:include page="/next.jsp"></jsp:include>

    index page after <br />

<body/>

next.jsp

<body>
    next page
</body>

include跳转时,全显示

index page befor
next page
index page after 

(注):

生成了两个.java文件:index_jsp.java与left_jsp.java。

这个包含动作是在程序运行过程中,由index_jsp文件中的_jspService()方法通过JspRuntimeLibrary类的include()方法调用了left_jsp文件中的_jspService()方法完成的。

这种在运行期执行的包含,称为动态联编。

2.4 EL表达式

Expression Language,表达式语言。

${expression}获取到指定表达式的值。

2.4.1 获取数据

(1)、EL只能从四大域中获取数据

其查找数据的顺序是,依次按照由小到大的范围,从四大域中查找指定名称的属性值,找到后就不再继续往下查找。

<body>
    <%
        String username = "abc";
        pageContext.setAttribute("username", username);
        request.setAttribute("username", username);
        session.setAttribute("username", username);
        application.setAttribute("username", username);
    %>
    //从四大域属性空间中依次查找
    username = ${username}

</body>

(2)、从指定域中获取数据

//通过EL的内置对象,从指定的域属性空间中查找
username = ${requestScope.username}

(3)、访问Bean的属性

<body>
    <%
        School school = new School("qinghua","beijing");        
        Student studentb = new Student("zhangsan",23);
        pageContext.setAttribute("student", student);
    %>

    student = ${student}

    name = ${student.name}

    schoolName = ${student.school.sname}

</body>

(4)、获取数组中的元素

<body>

    <%
        String[] names = {"zhangsan", "lisi", "wangwu"};
        pageContext.setAttribute("names", names);
    %>

    names[1] = ${names[1]}

    <%
        School[] schools = new School[3];
        schools[0] = new School("qinghua", "beijing");
        schools[1] = new School("haiyang", "qingdao");
        schools[2] = new School("shanda", "shandong");
        pageContext.setAttribute("chools", schools);
    %>

    chools[2].sname = ${schools[2].sname}

</body>

(5)、获取List中的元素

EL可以通过索引访问List,但无法访问Set。因为Set中没有索引的概念。

<body>
    <%
        List<String> names = new ArrayList<String>();
        names.add("zhangsan");
        names.add("lisi");
        names.add("wangwu");
        pageContext.setAttribute("names",names);
    %>

    names[2] = ${names[2]};

<body/>

(6)、获取Map中的元素

<body>
    <%
        Map<String, Object> map = new Map<>();
        map.put("school", new School("qinghua", "beijing"));
        map.put("mobile", "1111111");
        map.put("age", 21);
        pageContext.setAttribute("map", map);
    %>

    school.name = ${map.school.sname}

    mobile = ${map.mobile};

    age = ${map.age};

</body>

2.4.2 运算符

算术运算符,关系运算符,逻辑运算符,条件运算符,取值运算符

除了这些,还有一个empty

${empty 变量}

  • 变量未定义,返回true
  • 变量为String类型,空串返回true
  • 变量为引用类型,其值为null返回true
  • 变量为集合类型,不包含任何元素,返回true

2.4.3 EL内置对象

EL中表示四个域属性空间的内置对象:pageScope requestScope sessionScope applicationScope

其它的常用内置对象还有:

(1)、pageContext

EL中的pageContext与JSP中内置对象中的pageContext是同一个对象。

通过该对象,可以获取request、response、session、servletContext、servletConfig等对象。

  • ${pageContext.request.contextPath},获取项目根目录,一般用在JSP页面的路径前。



  • pageContext.request 其底层实际调用的是pageContext.getRequest()方法

  • 在EL的11个内置对象中,除了pageContext外,其它10个内置对象,其类型均为java.util.Map类型。

(2)、param

获取请求中指定参数的值

GET: http:localhost:8080/textproject/index.jsp?name=abc

//index.jsp

param.name = ${param.name}

POST:

<form action = "${pageContext.request.contextPath/show.jsp}">
    姓名:<input type = "text" name = "name" />
    年龄:<input type = "text" name = "age" />
    爱好:
    <input type = "checkbox" name = "hobby" value = "swimming" />
    <input type = "checkbox" name = "hobby" value = "climbing" />
    <input type = "checkbox" name = "hobby" value = "reading" />
</form>

//show.jsp

name = ${param.name}
age = ${param.age}

(3)、paramValues

hobby[0] = ${paramValues.hobby[0]}
hobby[1] = ${paramValues.hobby[0]}
hobby[2] = ${paramValues.hobby[0]}

(4)、initParam

获取web.xml中初始化参数

web.xml

<context-param>
    <param-name>company<param-name/>
    <param-value>powernode<param-value/>
</context-param>

jsp页面

company = ${initParam.company}

2.4.4 自定义EL函数

自定义EL函数,实际字符串连接的功能

1、定义类

public class ELFunctions{
    public static String lowerToUpper(String source){
        return source.toUpperCase();
    }

    public static String upperToLower(String source){
        return source.toLowerCase();
    }
}

2、标签库定义 注册

自定义的类及其函数,需要在扩展名为.tld的XML文件中进行注册

XML文件是需要约束的,即需要配置文件头部。这个头部约束可以从以下文件中复制。

comcat-9/webapps/examples/web-inf/jsp2/jsp2-example-taglib.tld

//WEB-INF/myElFuns.tld

<tablib ...>

    //定义标签库信息
    <tlib-version>1.0</tlib-version>
    <show-name>myElFuncs</short-name>
    <uri>http://www.test.com/myproject/jsp/el/customElFuncs</uri>

    //注册函数
    <function>
        <name>myLowerToUpper</name>
        <function-class>myproject.testpackage. ELFunctions</function-class>
        <function-signature>java.lang.String. lowerToUpper(java.lang.String)</function-signature>
    <function/>

    <function>
        <name>myUpperToLower</name>
        <function-class>myproject.testpackage. ELFunctions</function-class>
        <function-signature>java.lang.String. upperToLower(java.lang.String)</function-signature>
    <function/>

</tablib>

//index.jsp

<%@ taglib uri = "http://www.test.com/myproject/jsp/el/customElFuncs" prefix = "myElFuncs"%>
<body>
    ${myElFuncs:myLowerToUpper("abc")}
</body>

2.4.5 JSTL中的EL函数

JSTL,JSP Standard Tag Library,即JSP标准标签库。

该标签库中,定义好了一套处理字符串的函数标签库。

将JSTL的Jar包导入WEb/lib后,在jsp页面中即可直接使用。

//index.jsp

<%@ taglib uri = "http://java.sun.com/jsp/jstl/functions" prefix = "fn"%>

<body>
    ${fun.substring("abcdefg", 2, 5)}
    .....
    .....
</body>

2.4.6 EL总结

  • EL不能出现在java代码块、表达式块等jsp动态代码部分
  • EL只能从pageContext、request、session、application四大域属性空间中获取数据
  • EL不会抛出空指针异常
  • EL不会抛出数组访问越界异常
  • EL不具有字符串处理能力,只能通过JSTL标签库来处理

2.5 自定义标签

简化代码,替代java代码块。

2.5.1 基本用法

(1)、需求

自定义标签,输出客户端Ip

(2)、定义标签处理器

实现处理器接口:javax.servlet.jsp.tagext.simpleTag

//定义标签处理器
public class ClientIpTag extends SimpleTagSupport {
    @Override
    public void doTag() throws JspException, IOException {
        //获取pageContext
        PageContext pc = (PageContext) this.getJspContext();
        //获取请求对象
        ServletRequest request = pc.getRequest();
        //获取客户端IP
        String clientIp = request.getRemoteAddr();
        //获取标准输出流
        JspWriter out = pc.getOut();
        //输出
        out.print(clientIp);
    }
}

(3)、注册标签处理器

//customTags.tld

<?xml version="1.0" encoding="UTF-8"?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
    version="2.0">
    <!--配置标签库信息-->
    <tlib-version>1.0</tlib-version>
    <short-name>customTags</short-name>
    <uri>http://www.jstlproject.com/jsp/tags/custom</uri>
    <!--注册标签-->
    <tag>
        <name>clientIp</name>
        <tag-class>tags.ClientIpTag</tag-class>
        <body-content>empty</body-content>
    </tag>

</taglib>

(4)、使用自定义标签

<%@ taglib uri = "http://www.jstlproject.com/jsp/tags/custom" prefix = "customTags" %>
<body>
    <% 
        String ip = request.getRemoteAddr();
        out.println("ip = " + ip);
    %>
    <br/>
    <customTags:clientIp />
</body>

2.5.2 定义带标签体的标签

//标签处理器 - 小写变大写

public class LowerToUpperTag extends SimpleTagSupport {
    @Override
    public void doTag() throws JspException, IOException {
        //获取标签体对象
        JspFragment jspbody = this.getJspBody();
        //创建字符串输出流
        StringWriter strWriter = new StringWriter();
        //标签体对象写入字符串输出流
        jspbody.invoke(strWriter);
        //字符串输出流中的数据
        String str = strWriter.toString();
        //小写变大写
        str = str.toUpperCase();
        //写入标准输出流
        this.getJspContext().getOut().print(str);
    }
}

注册

<?xml version="1.0" encoding="UTF-8"?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
    version="2.0">
    <!--配置标签库信息-->
    <tlib-version>1.0</tlib-version>
    <short-name>customTags</short-name>
    <uri>http://www.jstlproject.com/jsp/tags/custom</uri>
    <!--注册标签-->
    <tag>
        <name>clientIp</name>
        <tag-class>tags.ClientIpTag</tag-class>
        <body-content>empty</body-content>
    </tag>
    <tag>
        <name>lowerToUpper</name>
        <tag-class>tags.LowerToUpperTag</tag-class>
        <body-content>scriptless</body-content>
    </tag>

</taglib>

附:

  • empty:表示当前标签没有标签体
  • scriptless:表示当前标签具有标签体,对el表达式解析。
  • jsp:原样输出,已过时
  • tagdependent: 原样输出到浏览器,不解析el表达式

//index.jsp

<%@ taglib uri = "http://www.jstlproject.com/jsp/tags/custom" prefix = "customTags" %>

<body>

    <%
        String username = "aaa";
        pageContext.setAttribute("username", username);
    %>
    <customTags:lowerToUpper>${username }</customTags:lowerToUpper>

</body>

2.5.3 定义带属性的标签

//注册标签

<tag>
    <name>if</name>
    <tag-class>tags.IfTag</tag-class>
    <body-content>tagdependent</body-content>
        <attribute>
            <name>test</name>
            <required>true</required>
            <!-- runtime expression value 
                true:该属性的值支持el表达式与jsp表达式
            -->
            <rtexprvalue>true</rtexprvalue>
        </attribute>
</tag>

//定义标签处理器

public class IfTag extends SimpleTagSupport {
    private boolean test;
    //标签的属性反映到标签处理器中,就是一个set属性
    public void setTest(boolean test) {
        this.test = test;
    }
    @Override
    public void doTag() throws JspException, IOException {
        if(test) {
            /*
            JspFragment jspbody = this.getJspBody();
            jspbody.invoke(this.getJspContext().getOut());
            */
            //以上代码等价于以下代码
            this.getJspBody().invoke(null);
        }
    }
}

//index.jsp

<customTages:if test = ${gender} }} }>男</customTages:if>
<customTages:if test = not gender }>女</customTages:if>

2.5.4 定义forEach标签

//定义标签处理器,遍历collection集合和数组

public class ForEachTag extends SimpleTagSupport {

    private Object items;
    private String var;

    public void setItems(Object items) {
        this.items = items;
    }    

    public void setVar(String var) {
        this.var = var;
    }

    public Collection getcoll() {
        if(items instanceof List) {
            return (List) items;
        }else if(items instanceof Set) {
            return (Set) items;
        }else if(items instanceof Map) {
            return ((Map) items).entrySet();
        }else if(items instanceof Object[]) {
            return Arrays.asList((Object[])items);
        }
        return null;
    }

    @Override
    public void doTag() throws JspException, IOException {
        for (Object obj:getcoll()) {
            this.getJspContext().setAttribute(var, obj);
            this.getJspBody().invoke(null);
        }
    }

}

//注册

 <tag>
    <name>forEach</name>
    <tag-class>tags.ForEachTag</tag-class>
    <body-content>scriptless</body-content>
        <attribute>
            <name>items</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>
        </attribute>
        <attribute>
            <name>var</name>
            <required>true</required>
            <rtexprvalue>false</rtexprvalue>
        </attribute>
</tag>

//index.jsp

<customTags:forEach items="${citys}" var="city">
    ${city }<br>
</customTags:forEach>

2.5.5 将自定义标签库打包发行

1、右键export选择jar包,只打包源码。

2、把.tld注册文件,放到jar包里的META-INFO里面。

3、把jar包放到项目的WEB-INF的lib下。

4、jsp页面中导入标签库,就可以直接用了。

2.6 JSTL

jsp standard tag library,包含五个子库。

核心标签库、格式化标签库、EL函数标签库、SQL操作标签库(过时)、XML操作标签库(过时)。

导入jstl.jar包和standard.jar包

2.6.1 核心标签库

<%@ taglib uri=”http://java.sun.com/jsp/jstl/core“ prefix=”c” %>

(1)、c:set

//将变量存放到指定域中
<c:set var="name" value="zhangsan" scope="session">
name = ${sessionScope.name}

//为Bean的属性赋值
<%
    Student student = new Student();
    pageContext.setAttribute("student", student);
%>
<c:set value="lisi" property="name" target="${pageScope.student}"></c:set>
student = ${student}

//为map赋值
<%
    Map<String, Object> map = new HashMap<>();
    pageContext.setAttribute("map", map);
%>
<c:set value="abc" property="company" target="${pageScope.map}"></c:set>
map = ${map}

(2)、c:remove

//从域属性空间中删除变量
<c:remove var="school">

(3)、c:out

//输出指定的变量
<c:set var="city" value="<h1>beijing</h1>">

city1 = <c:out value="${city}"/>

<br />

city2 = <c:out value="${city}" escapeXml="false" />

city1对h1标签进行了解析。

city2对h1标签没有进行解析,直接输出。

(4)、c:catch

//获取异常信息
<c:catch var="ex">
    <%
        int i = 3/0;
    %>
</c:catch>

(5)、c:if

<c:if test="${user == "zhangsan"}">
    <a href="#">进入系统</a>
</c:if>

(6)、c:choose

<c:choose>
    <c:when test="${pagenumber == 1}">
        第一页
    </c:when>
    <c:when test="${pagenumber == 2}">
        第二页
    </c:when>
    <c:otherwise>
        第三页
    </c:otherwise>
</c:choose>

(7)、c:forEach

//遍历集合
<c:forEach items="${citys}" var="city" begin="0" end="7" step="4">
    ${city} <br>
</c:forEach>

//输出行号
<c:forEach items="${students}" var="student" varStatus="vs">
    <tr class="${vs.count/2 == 0 ? 'even' : 'odd'}">
        <td>${vs.count}</td>
        <td>${student.name}</td>
        <td>${student.age}</td>
    </tr>
</c:forEach>

2.6.1 格式化标签库

<%@ taglib uri=”http://java.sun.com/jsp/jstl/fmt“ prefix=”fmt” %>

<%
    Date now = new Date();
    pageContext.setAttribute("now", now);
%>

//格式化日期,输出到浏览器
now = <fmt:formatDate value="${now}" pattern="yyyy-MM-dd">

//格式化日期,将结果放到域中的变量里
<fmt:formatDate value="${now}" pattern="yyyy-MM-dd" var="birth">
生日:<input type="text" name="birthday" value="${birth}" />

2.6.2 jstl下载

apache.org –> Tomcat –> Taglibs –> Download

第1章 JSP基础

第1章 JSP基础

1.1 JSP

JSP文件的本质是Servlet。只不过,JSP与Servlet不同的是,JSP是专门用于进行数据展示的Servlet,其有特殊的写法。而普通的Servlet是用于完成业务逻辑处理的。由于Servlet是运行在单例多线程环境下的,所以JSP同样也是运行在单例多线程环境下的。

1.2 JSP规范

将JSP页面翻译为Servlet的过程,是由Tomcat完成的。在Tomcat中内置了一个JSP的翻译引擎,当第一次访问该JSP页面时,翻译引擎会将JSP页面翻译为Servlet的.java文件,再将其编译为.class文件进行运行。

SUN公司制定的JavaEE规范中包含两个很重要的子规范,Servlet规范和JSP规范。其中,JSP规范中就包含了如何将JSP页面翻译为Servlet。例如,JSP页面中的html、css、javascript、普通文件部分,均会被翻译到out.write()中。

1.3 JSP注释

JSP注释:<%-- --%>

HTML注释:<!-- -->

JSP注释与HTML注释区别:

(1)、HTML注释会被JSP翻译引擎翻译到Servlet的out.write()中;而JSP注释会被翻译引擎忽略,在Servlet中看不到。

(2)、客户端浏览器查看源码时,HTML注释是可以查看到的;JSP注释查看不到。

1.4 JSP的java代码块

<% %> java代码块出现在Servlet的_jspService()方法中,不能定义方法,不能定义静态语句块。

<html>
    <% double b = 2; %>
    <body>
        <% int a = 1; %>
    </body>
</html>

1.5 JSP的声明语句块

<%! %> 声明语句块中的内容,将出现在Servlet类体中,没有包含到哪个方法体中。有线程安全隐患

<%! 
    private int amount = 3;

    public void showData(){
        syso("aaaaa");
    }
 %>

1.6 JSP的表达式块

<%= %> 表达式块出现的_jspService()方法的out.write()方法中。

count = <%=count %>

//Servlet
out.write("count = ");
out.write(count);

java Servlet 二、核心

二、Servlet核心

2.1 GenericServlet

2.1.1 GenericServlet 源码

//缺省适配器设计模式
public abstract class GenericServlet implements Servlet, ServletConfig,
        java.io.Serializable {

    private transient ServletConfig config;

    @Override
    public void destroy() {
        // NOOP by default
    }

    @Override
    public String getInitParameter(String name) {
        return getServletConfig().getInitParameter(name);
    }

    @Override
    public Enumeration<String> getInitParameterNames() {
        return getServletConfig().getInitParameterNames();
    }

    @Override
    public ServletConfig getServletConfig() {
        return config;
    }


    @Override
    public ServletContext getServletContext() {
        return getServletConfig().getServletContext();
    }


    @Override
    public String getServletInfo() {
        return "";
    }

    //模板设计模式
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        //调用无参的init()方法
        this.init();
    }

       //该无参的init()方法,就是要让子类来重写的
    public void init() throws ServletException {
        // NOOP by default
    }

    //抽象化service方法,让子类去实现
    public abstract void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException;


    @Override
    public String getServletName() {
        return config.getServletName();
    }
}

实现了servlet接口,并抽象化了service方法,让子类去实现。

实现了servletconfig接口,子类可以直接调用servletconfig的方法。

2.1.2 获取请求的提交方式

public class genericSevletDemo extends GenericServlet {
    private static final long serialVersionUID = 1L;

    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        String method = req.getMethod();

        if("POSt".equals(method)){
            doPost(req,res);
        }else if("GET".equals(method)){
            doGet(req,res);
        }
    }

}

继续封装doPost和doGet为HttpServlet类

2.2 HttpServlet

HttpServlet源码

public abstract class HttpServlet extends GenericServlet {

    @Override
public void service(ServletRequest req, ServletResponse res){
     HttpServletRequest  request;
    HttpServletResponse response;

    try {
        request = (HttpServletRequest) req;
        response = (HttpServletResponse) res;
    } catch (ClassCastException e) {
        throw new ServletException("non-HTTP request or response");
    }
    service(request, response);
}

     protected void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
            ...
        }

    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
        ...
        }
} 

//servlet继承httpservlet类

public class HttpservDemo extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.getWriter().append("Served at: ").append(request.getContextPath());
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

}

2.3 HttpServletRequest

2.3.1 请求的生命周期

当客户端浏览器将请求发送到服务器后,服务器会根据HTTP请求协议的格式对请求进行解析。同时,服务器会创建HttpServletRequest的实现类RequestFacade的对象,即请求对象。然后再调用相应的set方法,将解析出的数据封装到请示对象中。此时HttpServletRequest实例就创建并初始化完成了。也就是说,请求对象是由服务器创建的的。

当服务器向客户端发送响应结束后,HttpServletRequest实例对象就被服务器销毁。

一次请求对应一个请求对象,别外一次请求对应另一个请求对象,与之前的请求对象没有任何关系。HttpServletRequest实例的生命周期很短暂。

2.3.2 请求参数

HttpServletRequest对于请求中所携带的参数是以Map的形式接收的,并且该Map的key为String,value为String数组(一个请求参数可能会有多个值的情况出现)。

public class HttpservDemo extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //getParameter()方法,获取指定key对应的value,等同于getParameterValues()[0]
        String name = request.getParameter("name");
        String ageStr = request.getParameter("age");
        Integer age = Integer.valueOf(ageStr);
        System.out.println("name = "+name);
        System.out.println("age = "+age);

        //getParameterName()方法,获取所有keys
        Enumeration<String> names = request.getParameterNames();
        while(names.hasMoreElements()) {
            String eleName = names.nextElement();
            String eleValue = request.getParameter(eleName);
            System.out.println(eleName + "-----" + eleValue);
        }

        //getParameterValues()方法,获取指定key对应的所有values,
        //如<checkbox name = "hobby">running</checkbox>
        //如<checkbox name = "hobby">reading</checkbox>
        String[] hobby = request.getParameterValues("hobby");
        for(String h : hobby) {
            System.out.println(h);
        }

        //getParameterMap()方法,获取存放请求参数的Map
        Map<String, String[]> map = request.getParameterMap();
        for(String key : map.keySet()) {
            System.out.println(key+"-----"+request.getParameter(key));
        }
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

}
2.3.3 域属性

Request中也存在域属性空间,用于存放有名称的数据。该数据只在当前Request请求中可以访问。

对于Request中的域属性操作的常用方法:

设置

void setAttribute(String name, Object object);

获取

Object getAttribute(String name);

在FirstServlet中设置,跳转到otherServlet中获取

public class FirstServlet extends HttpServlet {   
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setAttribute("address", "zhuhaijinwan");
        request.setAttribute("phone", "15011112222");

        request.getRequestDispatcher("/other").forward(request, response);
    }
}

public class OtherServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String address = (String) request.getAttribute("address");
        String phone = (String) request.getAttribute("phone");

        Enumeration<String> names = request.getAttributeNames();
        while(names.hasMoreElements()) {
            String name = names.nextElement();
            String value = (String) request.getAttribute(name);
        }
    }
}
2.3.4 服务器相关信息

1、获取请求的URL,如:“http://localhost:8080/httpServlet/some”

StringBuffer getRequestURL();

2、获取请求的URI,如:”/httpServlet/some”

String getRequestURI();

3、获取当前应用在Web容器中的名称,如:”httpServlet”

String getContext();    

4、获取路径中与web.xml中注册servlet时的<url-pattern/>相匹配的路径信息

//servletPath指的是与<url-pattern>精确部分相匹配的路径
String getServletPath()

//pathInfo指的是与非精确部分相匹配的路径
String getPathInfo()

a、例如:<url-pattern/>的值为/xxx/ooo/*,用户提交的请求为:http://localhost:8080/httpServlet/xxx/ooo/abc/def。

servletPath就是:/xxx/ooo

pathInfo就是:/abc/def

b、若用户提交的请求为后辍方式,如:http://localhost:8080/httpServlet/aaa/bbb/ccc.do。

servletpath就是:/aaa/bbb/ccc.do

pathInfo: null

c、URI = ServletContext + ServletPath + PathInfo

5、获取请求的方式是post还是get等

String getMethod()

6、获取客户端浏览器的IP

//对于服务器端来说,客户端就是远程端
String getRemoteAddr()

2.4 中文乱码问题

当页面中提交一个包含中文的请求时,在服务器端有可能会出现中文乱码问题。

2.4.1 乱码产生的原因

Http协议中规定,数据传输采用字节码编码方式,即无论浏览器提交的数据包含的中文是什么字符编码格式,一旦由浏览器经过Http传输协议传输,则这些数据均将以字节码的形式上传给服务器。

因为Http协议的底层使用的TCP(Transmission Contrl Protocol)传输协议。传输控制协议是一种面向连接的、可靠的、基于字节流的、端对端的通信协议。在请求中,这些字节均以%开头,并以十六进制形式出现。如%5A%3D等。

那么乱码是如何产生的呢?

当用户通过浏览器提交一个包含 UTF-8编码格式的两个字的中文请求时,浏览器会将这两个中文字符变为六个字节,即形成六个类似%8E的字节表示形式,并将这六个字节上传到tomcat服务器。

tomcat服务器接收到这六个字节后,并不知道它们原来采用的是什么字符编码。而tomcat默认的编码格式为ISO-8859-1。所以会将这个六个字符按照这种格式进行编码,编码后在控制台显示,所以在控制台会显示乱码。

2.4.3 乱码的解决方法

1、tomcat9已经解决了GET提交时的中文乱码问题。

2、request.setCharacterEncoding(“UTF-8”);解决了POST提交中文时的乱码问题。无法解决GET提交时的中文乱码问题。

3、Get提交时中文乱码解决方案有两种:

(1)、修改tomcat/conf/server.xml,添加URIEncoding

<Connector port="8080" protocol="HTTP/1.1"
    connectionTimeOut="20000" redirectPort="8443" URIEncoding="UTF-8"
/>

(2)、无论Post还是get都可以

String name = request.getParaeter("name");
byte[] bytes = name.getBytes("ISO8859-1");
name = new String(bytes,"UTF-8");

2.5 HttpServletResponse

web服务器收到一个Http请求后,会针对每个请求创建一个HttpServletRequest对象和HttpServletResponse对象。若需要获取客户端提交请求的相关信息,刚需要HttpServletRequest对象来完成。若需要向客户端发送数据,则需要通过HttpServletResponse对象来完成。

2.5.1 向客户端发送数据

ServletResonse接口有一个方法getWriter(),用于获取到一个输出流对象PrintWriter,该输出流对象是专门用于向客户端浏览器中输出字符数据的,称为标准输出流。

response.getWriter().append("Served at: ").append(request.getContextPath());

PrintWriter out = response.getWriter();
out.append("dddd");
out.print("aaa");
out.pirntln("bbbb");
out.Write("ccc");
2.5.2 响应乱码的产生

两种方法:

1、设置MIME类型,设置字符编码格式

response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");

2、同时设置MIME类型和字符编码格式

response.setContentType("text/html;charset=UTF-8");

2.6 请求转发与重定向

通过HttpServletRequest获取到的RequestDispatcher对象的forward()方法,可以完成请求转发功能。

而通过HttpServletResponse的sendRedirect()方法,可以完成重定向功能。

2.6.1什么是请求转发与重定向

请求转发:一次请求,一次响应。

重定向:多次请求,多次响应。

2.6.2 请求转发程序举例
public class SomServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String name = req.getParameter("name");
        String age = req.getParameter("age");
        req.setAttribute("ageStr", age);
        //请求转发
        req.getRequestDispatcher("other").forward(req, resp);
    }
}

public class OtherServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //可以继续使用request,response,也可以获取someServlet中设置的域属性
        String name = request.getParameter("name");
        request.getAttribute("ageStr");

        PrintWriter out = response.getWriter();
        out.print("this is otherServlet");
    }
}
2.6.3 重定向举例
public class SomServlet extends HttpServlet {

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String name = req.getParameter("name");
        String age = req.getParameter("age");
        req.setAttribute("ageStr", age);
        req.getRequestDispatcher("other").forward(req, resp);

        //重定向
        resp.sendRedirect("other");
    }
}

public class OtherServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //不能再继续使用request,response,也不能获取someServlet中设置的域属性
        PrintWriter out = response.getWriter();
        out.print("this is otherServlet");
    }

}
2.6.4 重定向时的数据传递

//重定向数据传递,及乱码解决方案

public class SomServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String name = req.getParameter("name");
        //解决中文乱码
        name = new String(name.getBytes("ISO8859-1"),"UTF-8");

        String age = req.getParameter("age");
        req.setAttribute("ageStr", age);
        req.getRequestDispatcher("other").forward(req, resp);

        //编码,解决重定向传递时乱码问题
        name =  URLEncoder.encode(name,"UTF-8");

        //重定向
        resp.sendRedirect("other?pname="+name+"&page="+age);
    }

}    

public class OtherServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        String pname = request.getParameter("pname");
        //解码
        pname = URLDecoder.decode(pname,"UTF-8");

        //打散组装
        pname = new String(pname.getBytes("ISO8859-1"),"UTF-8");

        String page = request.getParameter("page");

        System.out.println("pname====" +pname);
        System.out.println("page====" +page);

        PrintWriter out = response.getWriter();
        out.print("this is otherServlet");
    }

}
2.6.5 重定向到其它应用

重定向与请求转发还有一点很重要的不同点是,重定向可以跳转到其它应用中,而请求转发只能在当前应用中跳转。也正因为如此,所以sendRedirect()的参数中,必须要添加request.getContext(),即当前应用的根目录,指定要跳转到哪个应用的哪个资源。

//重定向到另一个应用的名称为other的servlet
response.sendRedirect("/redirect-two/other");
2.6.6 请求转发与重定向对比

(1)、请求转发

A、浏览器只发出一次请求,收到一次响应

B、请求所转发到的资源中可以直接获取到请求中所携带的数据

C、浏览器地址栏显示的是用户所提交的路径

D、只能跳转到当前应用中的资源

(2)、

A、浏览器发出两次请求,接收到两次响应

B、重定向到的资源不能直接获取到用户提交请求中所携带的数据

C、浏览器地址栏显示的是重定向的请求路径,而非用户提交请求的路径。也正因为如此,重定向有一个很重要的作用:防止表单重复提交

D、重定向不仅可以跳转到当前应用的其它资源,也可以跳转到其它应用中的资源

2.6.7 请求转发与重定向的选择

(1)、若需跳转到其它应用,只能选择重定向

(2)、若是处理表单数据的servlet,要跳转到其它servlet,则需要选择重定向。为了防止表单重复提交。

(3)、若对某一请求进行处理的servlet的执行需要消耗大量的服务器资源(cpu,内存),此时这个servlet执行完毕后,也需要重定向。

(4)、其它情况,一般使用请求转发。

2.7 RequestDispatcher

RequestDispatcher是javax.servlet包下的一个接口,通过HttpServletRequest可以获取到其接口对象,该对象就是用于完成转发功能的。

2.7.1 forward()与include()

SomeServlet

public class SomServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("some request = " + req);
        System.out.println("some resp = " + resp);
        req.getRequestDispatcher("other").forward(req, resp);
    }

}

OtherServlet

public class OtherServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("other response = " + response);
        System.out.println("other request = " + request);
    }

}

输出结果中的request:

some request = org.apache.catalina.connector.RequestFacade@289113c7
other request = org.apache.catalina.core.ApplicationHttpRequest@4d0d88e6

1、other中的HttpServletRequest request是对some中的HttpServletRequest req的增强,实际上是将用户请求和转发请求这两次请求进行了合并。

2、将req.getRequestDispatcher(“other”).forward(req, resp);改为req.getRequestDispatcher(“other”).include(req, resp);后,输出结果和上面的相同。说明对于request,forward与include没有区别。

输出结果中的response:

//forward()方法跳转时    
some resp = org.apache.catalina.connector.ResponseFacade@6451e20e
other response = org.apache.catalina.connector.ResponseFacade@6451e20e

//include()方法跳转时
some resp = org.apache.catalina.connector.ResponseFacade@48202e11
other response = org.apache.catalina.core.ApplicationHttpResponse@5f53ab27

1、forward()方法跳转时,some中的resp与other中的response一样,都是ResponseFacade。

2、include()方法跳转时,some中的resp为ResponseFacade,other中的response为ApplicationHttpResponse。

3、说明requestDispatcher的forward()与include()的区别,主要集中在响应对象上。

2.7.2 代码测试

SomeServlet

public class SomServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        PrintWriter out = resp.getWriter();
        out.print("someServlet:forward() before ");

        req.getRequestDispatcher("other").forward(req, resp);

        out.print("someServlet:forward() after ");
    }

}

OtherServlet

public class OtherServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        PrintWriter out = response.getWriter();
        out.print("other servlet data");
    }

}

客户端浏览器输出结果:

forward()方法跳转的输出结果:

other servlet data

incluce()方法跳转的输出结果:

someServlet:forward() before 
other servlet data
someServlet:forward() after 

1、forward()与include()的区别,主要表现在标准输出流的开启时间不同。

2、说明在SomeServlet中,用forward()方法向前跳转时,请求此时还并没有结束,所以reqp还没有开启,因此两个输出语句没有执行。

3、include()方法跳转时,some和other中的输出语句全部执行。说明,include不仅将other的数据写入到了标准输出流中,也将some中的数据包含到了自己的输出流中,

4、forward的转发示意:

浏览器—>发送请求—>服务器中的SomeServlet—->发送请求—->服务器中的OtherServlet—>OtherServlet对浏览器做出响应。

向浏览器给出响应的是OtherServlet,输出流是在OtherServlet中开启的。

5、include的转发示意:

浏览器—>发送请求—>服务器中的SomeServlet—->发送请求—>服务器中的OtherServlet—->OtherServlet将SomeServlet进行了包含,并向SomeServlet做出响应 —> SomeServle对浏览器做出响应。

向浏览器给出响应的是SomeServlet,输出流在SomeServlet中就开启了。

包含的过程就是由ApplicationHttpResponse这个增强的response来完成的。

2.9 Servlet的线程安全问题

Servlet是单例多线程环境下运行的,其运行可能会产生线程安全问题。

2.9.1 线程安全问题

1、同时满足以下两个条件,则会出现线程安全问题。

(1)、存在多线程并发访问

(2)、存在可修改的共享数据

2、JVM中可能存在线程安全问题的数据分析

(1)、栈内存数据分析

栈内存是是多例的,即JVM会为每个线程创建一个栈,所以其中的数据是不共享的。另外,栈里面放的是方法栈帧,一个方法在栈里面,以一个栈帧的形式出现,方法的栈帧中放的是方法的签名(头部信息)、局部变量、返回的信息等,方法中的局部变量存放在Stack的栈帧中,方法执行完毕,栈帧弹栈,局部变量消失。局部变量是局部的,不是共享的。所以栈内存中的数据不存在线程安全问题。

(2)、堆内存数据分析

一个JVM中只存放一个堆内存,堆内存是共享的。被创建出的对象存放在堆内存中,而存放在堆内存中的对象,实际就是对象成员变量的集合。即成员变量是存放在堆内存的。堆内存中的数据是多线程共享的,也就是说,堆内存中的数据是存在线程安全隐患的。

(3)、方法区数据分析

一个JVM中只存在一个方法区。方法的代码片段,静态变量与常量都存放在方法区,方法区是多线程共享的。常量是不能修改的,所以常量不存在安全问题。静态变量是多线程共享的,所以静态变量存在安全隐患。

3、线程安全问题的解决方案

(1)、对于一般性的类,不要定义为单例的。除非项目有特殊需求,或该类对象属于重量级对象。所谓重量级对象是指,创建该类对象是会占用较大的系统资源。

(2)、无论类是否为单例类,尽量不要使用静态变量。

(3)、若需要定义为单例类,则单例类尽量不使用成员变量(例如Servlet类)。

(4)、若单例类中必须要使用成员变量,则对成员变量的操作添加串行化锁synchronized,实现线程同步。不过,最好不要使用线程同步机制,因为一旦操作进入串行化的排队状态,将大大降低程序的执行效率。

2.9.2 Servlet的线程安全问题

Servlet是单例多线程并发访问的,所以存在线程安全问题。

为了避免问题产生,对用Servlet类的使用,一般是不声明成员变量的。

若项目中必须要求声明成员变量,则只能通过同步机制synchronize避免。

2.9.3 对线程安全问题的合理利用

Servlet中的成员变量是每一个线程均可修改和访问的,所以可以利用这一点,实现计数器功能,用于统计当前Servlet的被访问的次数。

public class SomeServlet extends HttpServle{
    //定义成员变量 - 计数器
    private int count;
    public void doGet(...){
        count++;
        response.setContentType("text/html;charset=UTF-8");
        PrinterWriter out = resp.getWriter();
        out.print("当前网页的访问次数:" + count);

    }
}