分类 软件 下的文章

在初步接触Java Servlet之后,会发现一个HTTPServlet有这么两个附属对象:ServletConfigServletContext,两者长得很像,又有着不少相同的方法(getAttribute(), getInitParameter(), getServletName()/getServletNames()),在网上搜了一下,发现有讲述两者之间的区别的博客,但是我是在理解之后才看懂的。。

在此简述一下使用Tomcat运行Web服务的三大主要层次,我依次将其命名为Server, Application和Servlet。其中Server指的是Tomcat本身,可以理解为一台服务器上一般只会跑一个Tomcat,所有需要借助Tomcat实现的Web服务都会依赖于这一个Tomcat实例。其次就是Application了,这就是上面所说,需要借助Tomcat实现的服务。一个Web服务就是一个Web项目,内部可以有复杂的Java逻辑代码,可以包括与数据库交互,等等。一个Servlet指的是提供单一Web功能的程序单位。

Application举例说明的话就是登陆系统,从登陆到连接后台,查看数据,修改用户信息、密码等,算是一个完整的后台管理系统服务,仅靠单一的URL是很难(但是是可以)实现整个功能的,起码作为一个便于维护和扩展的项目来说,都会需要多个URL来实现不同的功能,因为这样至少光靠URL地址就能有效地组织整合不同的功能了。这是Application。而Servlet实现的是原子操作,比如说后台管理系统中的登陆,或是获取用户信息,或是修改密码这种单一而清晰的任务。

然后就可以说明了:ServletConfig是Servlet层面的,每个Servlet都有自己独立的ServletConfig;而ServletContext是Application层面的,同一服务/项目下的所有Servlet可以通过ServletContext共享数据。那为什么ServletContext不是Server层面呢?ServletContext中可以存放应用的重要数据,出于安全考虑,一是防止数据窃取,二是防止篡改,不论是无意还是有意。

再仔细看看ServletContext的方法的话,会发现有一个方法名为getServletNames(),返回的是一个Enumeration,也就是说具有多个Servlet名称。相对地,ServletConfig的方法名为getServletName(),返回一个String。这下就更加清晰了,显然一个Servlet只应该有一个名字,那么ServletContext肯定就是Servlet层面之上的了。

此外有一个小坑:声明Application级别的初始变量是在web-app建立context-param子节点,使用ServletContextgetInitParameter()方法获取;Servlet级别的初始变量则需要在web-app节点下先新建servlet子节点,再在servlet子节点下新建init-param节点定义,使用ServletConfiggetInitParameter()方法获取。其中两个param的定义结构相同,使用param-name子节点定义变量名,param-value子节点定义变量值,而显然这个值只能定义为String类型。

例如:

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

  ...

  <context-param>
    <param-name>contextInitParam</param-name>
    <param-value>someValue</param-value>
  </context-param>

  <servlet>
    <servlet-name>demoServlet</servlet-name>
    <servlet-class>tk.esperz.demoServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
    <init-param>
      <param-name>servletInitParam</param-name>
      <param-value>otherServletsCannotSeeMe</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
     <servlet-name>demoServlet</servlet-name>
     <url-pattern>/demo.do</url-pattern>
  </servlet-mapping>
</web-app>

其中为该Web项目定义了一个名为contextInitParam的上下文初始化变量,值为someValue,所有该项目中的Servlet都可以通过ServletContext.getInitParameter("contextInitParam")获取;为名为demoServlet的Servlet定义了一个名为servletInitParam的初始化变量,值为otherServletsCannotSeeMe,仅在该Servlet内可以通过ServletConfig.getInitParameter("servletInitParam")获取。凡是用错了方法的都会返回null

按照常规操作,在定义完变量之后应该对其进行初始化,然后再使用。于是就有了这么一段代码:

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        PrintWriter out = response.getWriter();
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html; charset=UTF-8");
        out.write("喵喵喵?");
    }

然而在编译并由Tomcat发布后,访问获得的结果却是????。 你是????,我也是????啊??比特流编码和传输给浏览器的MIME信息都已经声明了,为啥还是不对?

直到看了这个 https://blog.csdn.net/tlms_/article/details/78749980

才发现

要先设置好response的属性,然后再获得由这些属性生成的PrintWriter。很是奇怪的逻辑。。 将getWriter()操作放到几个set操作之后,问题顺利解决。

在把Eclipse JEE更新到2019-09之后,打开之前可以运行的,使用Gradle配置的JSP项目运行时,发现所有页面都变成了404。前往Eclipse配置的tomcat临时文件夹查看,发现所写的jsp文件全部没有复制过去。然后在网上搜索发现,是由于JSP搜索路径没有配置好的问题。

https://guides.gradle.org/building-java-web-applications/ 从Gradle的官方教程不难发现,其默认配置的JSP根目录在src/main/webapp下,而不是Eclipse所使用的WebContent

https://stackoverflow.com/questions/18273184/how-to-set-webappdirname-in-gradle 依照这个方法,使用webAppDirName(或者全写为project.webAppDirName)参数指定JSP包的根目录后,方可使其正常搜索WebContent目录下的JSP文件。经过测试,这个参数与是否使用了war插件似乎无关,也就是说,不使用war插件也不能正常识别WebContent目录下的JSP文件,而这时webAppDirName参数是否有效,由于未经测试,则不得而知了。

这个问题与IDE的版本是有关的,在Eclipse JEE 2017-12中可以正常使用,升级到2019-09就不行了。

在设置完webAppDirName之后,再次使用Gradle进行编译的时候,又可能会遇到找不到Servlet的问题。原因推测是在指定JSP目录的时候,破坏了对Java源代码目录的设置。可以注意到Gradle编译的输出项,其中compileJava一项显示的是NO-SOURCE`,显然就是没找到写好的Java源代码文件。

https://stackoverflow.com/questions/31077844/add-another-java-source-directory-to-gradle-script 依照这个方法,显示指定Java源文件的目录后,再重新使用Gradle编译即可解决问题。

添加的参考配置如下:

sourceSets {
    main {
        java {
            srcDir 'src'
        }
    }
}

该配置可用于直接在src目录下建立包名,即使用Eclipse传统的Dynamic Web App向导建立的目录结构。

几乎完全参考自:https://blog.csdn.net/u012822903/article/details/64506037

  1. 下载所需系统的Minimal(最小版本),或者Cloud Image(云镜像),不用安装的那种

  2. 使用util目录下的gem5img.py生成镜像:python2 util/gem5img.py init /path/to/your/img <size> 其中size表示镜像大小,一般单位为MB。根据原文,使用了root权限建立镜像。

  3. 挂载镜像: 先用losetup将文件挂载为系统可识别的设备,然后再用mount命令挂载为磁盘分区。 gem5img.py可以自动完成这一系列工作:sudo util/gem5img.py mount /path/to/your/img /mount/folder

  4. 把Linux系统上的文件树复制过去,如果是压缩文件的话直接解压到根目录即可

  5. 设置串口作为GEM5与虚拟系统的默认通信方式: 建立etc/init/tty-gem5.conf

    # ttyS0 - getty
    #
    #This service maintains a getty on ttyS0 from the point the system is
    # started until it is shut down again, unless there is a script passed to gem5.
    # If there is a script, the script is executed then simulation is stopped.
    
    start on stopped rc RUNLEVEL=[12345]
    stop on runlevel [!12345]
    
    console owner
    respawn
    script
       # Create the serial tty if it doesn't already exist
       if [ ! -c /dev/ttyS0  ]
       then
         mknod /dev/ttyS0 -m 660 /dev/ttyS0 c 4 64
       fi
    
       # Try to read in the script from the host system
       /sbin/m5 readfile > /tmp/script
       chmod 755 /tmp/script
       if [ -s /tmp/script  ]
       then
         # If there is a script, execute the script and then exit the simulation
         exec su root -c '/tmp/script' # gives script full privileges as root user in multi-user mode
         /sbin/m5 exit
       else
         # If there is no script, login the root user and drop to a console
         # Use m5term to connect to this console
         exec /sbin/getty --autologin root -8 38400 ttyS0
       fi
    
    end script
  6. 按照目标架构,编译好m5文件放到sbin目录下: 由于不同架构下的编译方式可能不一样,make的时候要用-f选项指定目标平台所对应的Makefile。

  7. 系统内安装应用 不知道为啥要链接三个目录,但是这里不是用ln,因为非系统底层的文件系统映射,并不是所有软件都能较好支持。 这里用mount的bind选项:

    sudo /bin/mount -o bind /dev dev
    sudo /bin/mount -o bind /sys sys
    sudo /bin/mount -o bind /proc proc

    然后用chroot来模拟对另一套文件系统的操作:sudo chroot /path/to/mounted/root /bin/bash 这时的bash就会按照新的伪根目录进行操作了,有root权限。内核还是宿主系统的,显然。 操作完成之后要卸载镜像,在此之前需要先把上面的三个目录umount一下。

  8. 关于启动 最小版本的Ubuntu内部是不含有启动内核的。一般来说,内核文件是放在/boot目录下的。Ubuntu的最小版本可以通过apt下载对应的内核,但是这个方法并不适用于GEM5。GEM5的启动方式是直接执行内核,因而内核必须是一个完整的文件,静态编译且保留符号才可运行,所以在镜像外部准备一个未经压缩的完整Linux内核,在启动时指定为参数即可。现在发行版提供的大多是vmlinuz,是将原始版本vmlinux脱去符号、再经gzip压缩后的版本,还原后由于没有符号不能用于GEM5,还原方法打算另开一文简述(已经写好了->参见这里)。所以考虑一下自己编译吧。

  1. 官网,Dowload部分选择合适的版本下载。如果没有特别要求,可以直接无脑装个最新版,本地还要配合适的java运行环境,可以也直接挑最新的。当然,若是对java有一定了解的可以去看看有个帮你区分版本区别的页面,找一个满足最低要求的组合就行,如果不想在电脑上修改太多软件配置的话。在下使用的是Java 8 + Tomcat 9。

  2. 选择用installer安装的话可以直接一把梭,因而不作赘述。在此选择Core版本的zip包,下载解压,里面是一套较为标准的Linux文件树目录。

  3. 阅读RUNNING.txt可以获得将Tomcat运行起来所需要的一系列基本配置。本文是参照该指示,从中选取部分关键配置而将Tomcat运行起来的一系列记录。

  4. 高级系统设置-环境变量,建立新项设置CATALINA_HOME。该选项是必须在Tomcat运行前定义的,用于指示Tomcat的家目录,其意义同JAVA_HOME,指向Tomcat的bin目录的上一级。其余变量可以通过下一步的方式,在文件中定义,方便管理和多实例运行。

  5. bin目录下新建setenv.bat,该文件用于提供绝大部分Tomcat运行环境所需要的参数值,Tomcat会自动寻找该文件。(没有的话自动忽略呗,因为要手动配置,所以Apache干脆直接不提供这个文件了)在里面需要明确至少一个选项:JAVA_HOME/JRE_HOME。亦即是说,在配置多实例的Tomcat的时候,允许不同实例使用不同版本的java。不需要将变量值用引号包裹,即使路径里含有空格。

  6. 由于Tomcat提供了默认的配置文件,因而在配置好环境后可跳过软件内部的配置,直接运行。使用bin目录下的脚本进行对应的操作: 启动Tomcat:startup.bat / catalina.bat start 关闭Tomcat:shutdown.bat / catalina.bat stop

  7. 使用默认配置启动Tomcat后,可以访问http://localhost:8080进行测试,会出现一个管理界面,说明配置并启动成功。

  8. 此时若是关注Tomcat的日志输出,会发现出现如淇℃伅一类的乱码。这是由于Windows控制台和Tomcat中文编码不一致导致的。Tomcat提供了汉化的提示信息,上面这个是“信息”二字经过UTF-8编码后,又经GBK解码出现的结果。解决方法有二: 1) 修改conf目录下的logging.properties文件,把java.util.logging.ConsoleHandler.encoding的对应值修改为GBK; 2) 在bin/setenv.bat中添加一行:

    SET JAVA_OPTS=-Duser.language=en

    将JVM的语言设置为英文,这样看着也更熟悉,也避免了中文乱码的问题。当然可以配合第一条使用,不然算是治标不治本。 JAVA_OPTS是传递给JVM的一系列指令,可以填写多个选项,应该是同上一样空格隔开不加引号即可。 然而指导文件中提到,若要限制Tomcat的资源占用,应该在CATALINA_OPTS里而不是这里进行设置。

  9. 若要观察服务器状态,Tomcat提供了该功能,但是出于安全考虑,设计了一套权限管理系统。 修改conf目录下的tomcat-users.xml以注册用户: 在tomcat-users标签里添加:
    <role rolename="tomcat"/>
    <user username="tomcat" password="tomcat" roles="tomcat,manager-gui"/>

    可将用户tomcat以密码tomcat添加至该Tomcat实例的管理用户。由上可见,若是用户要加入不同的组,可以通过逗号隔开即可。 然后就可用tomcat/tomcat用户名-密码对登陆管理界面。