分类 Java 下的文章

  1. 实际上SpringBoot是可以结合JSP使用的,不过需要加上providedRumtime("org.apache.tomcat.embed:tomcat-embed-jasper")作为JSP解析器。然而使用了JSP特性页面可能会出现编译错误,这个时候SpringBoot显示出来的是404。
  2. Thymeleaf的直接取值表达式是[[${val}]]
  3. 验证登录的时候,最好使用@{/login}获取Spring Security的登录地址,不然登录似乎会失败。
  4. Thymeleaf需要从application.properties一类配置文件中读取数据的时候,传统方法是先使用@Value{key.name}注入到对应类型的变量中,然后再用MVC的ModeladdAttribute()方法设置后传给对应的模板。
  5. 退出登录也要POST方法。
  6. 登陆/退出(默认URLlogin/logout)成功后自动跳转的方法名分别为defaultSuccessUrllogoutSuccessUrl。其中defaultSuccessUrl有第二个选填参数表示是否永远跳转至该页面。
  7. 别想着明文密码存储了,Spring Security 5删除了PlaintextPasswordEncoder,现在基本上都是用BCrypt,到网上随便找一个BCrypt加密工具来生成测试账户的密码吧。(或者用AuthenticationManagerBuilder.withUser直接在代码里也行,这里可以用明文)
  8. 可以通过重写UserDetailsUserDetailsService来重定向登陆所用的用户信息类到自己想要的方式。比如写一个连接数据库的Entity,加上jdbcAuthentication()(说实话,这个真的有必要吗),就可以实现从数据库中获取登录信息。进行鉴权处理的是Authentication类,所需要的UserDetails信息是约定俗成usernamepassword两个属性,其中password是存在数据库中的、使用约定(可以设置)的加密方式加密后的串。(不记得从哪看来的了,说是spring的原则是约定高于配置) 参考:https://www.baeldung.com/spring-security-authentication-with-a-database 以及附带的github代码
  9. 如果在post表单里用了th:object的话,在对应的Controller方法中要用@ModelAttribute(modelName)获得。
  10. Kotlin的foreach用的是大括号,可以使用it获取当前次循环正使用的元素。
  11. 若是对RequestMapping方法中,有javax.validation验证限制的对象使用@Valid注解的话,可以按照预期检查出错误来,但是由于validation本身发生在进入方法体之前,方法本身并不会被执行。需要使用ExceptionHandler来捕获异常,若是对数据传输对象Data Transfer Object, DTO使用@Valid的话,会产生org.springframework.validation.BindException(噗,stackoverflow上有个老哥写成了java.net.BindException
  12. Kotlin会给方法自动生成getter和setter,但是仅仅是作用和Java中的一样,并不能通过getXXX()方法来访问属性= =
  13. Kotlin通过反射调用方法有点麻烦,可以绕道Java,通过::class.java.getMethod().invoke()实现等效的功能。
  14. Thymeleaf在使用@{}生成链接地址的时候,可以通过特殊的方式生成含变量的路径,格式形如@{/path/{key}(key=${val})}会生成/path/${val}形式的地址。
  15. URL作为变量要用@PathVariable而不是@PathParam。以及为了防止万一,最好传入封装类型而不是基本类型。
  16. 可以使用th:checked属性设置输入复选框的初值。

在看Spring WebFlux的时候,看到了这么一段代码:

public interface Publisher<T> 
{
    public void subscribe(Subscriber<? super T> s);
}

之前在写Java程序的时候,看到部分Java内置类型中就出现过类似的表达,即<? super T>。当时也没考虑过这玩意具体有什么用,如果仅仅只是看表达式的话,很容易理解它要表达的基础意思:这里要传入一个类,要求该类是T的父类。但是有什么用呢?怕你传入一个T的子类?虽然这么说听起来的确有点道理,但显然这个设计要表达的不是这个意思。

基础介绍可以参见这两篇还行的文章: https://blog.csdn.net/qq_29951485/article/details/88068338 https://www.cnblogs.com/hoojjack/p/6817547.html?utm_source=itdadao&utm_medium=referral

总而言之目的是提供泛型类之间的继承关系。很容易从文章中看出的是,编译器不会认泛型参数之间的继承关系,因而Sun的程序员们决定给泛型提供superextends两个通配符关键字来人为声明泛型参数类的继承关系。注意使用时一同添加的?的含义,虽然表示任意,但是不是任意类型,而是任意确定类型。好比摸奖的时候,手到箱子里抓了一个球,你没有办法口胡说摸了特等奖,摸了是啥就是啥,因为只要手从箱子里出来之后,是什么球大家都知道得清清楚楚。

但是显然非一一对应的映射是没有反函数的,因而就会出现这么一个问题:泛型类操作时的参数类型究竟是什么?显然如果这个问题不明确的话只有两种可能:编译器找不到合适的方法操作数据,罢工;编译器随性选了一种不合适的方法操作数据,搞砸。

所以这个时候就要通过使用的通配符,以及面向对象设计的基本原理来解决这个问题了。接下来用?代指使用泛型通配符时传入的具体类别,则对于<? extends Parent>,根据面向对象的特性有:

  1. ?类为任意固定的Parent类或其子类;
  2. ?类一定可以转换为Parent类(及其父类)的合法对象。

则根据第一条,可以发现,由于在Java中没有提供明确?类型的方法,出于安全考虑,实际上是不允许向<? extends Parent>类型的形参传入任何值的。相反,具有<? extends Parent>类型返回值的方法返回的都是Parent类型的对象。

因而,相当于使用了extends通配符的泛型类对象的所有泛型输入方法全部失效,泛型输出方法正常工作,但返回的是被extends的类对象。

那我要传入对象怎么办?

答案是反过来,使用super。而super又与extends恰好相反。在<? super Child>泛型类中:

  1. 任意固定的Child类或其父类为?类;
  2. Child类(及其子类)一定可以转化为?类的合法对象。

这也造成了与上面恰好相反的效果:使用了super通配符的泛型类对象的所有泛型输出方法全部失效,泛型输入方法正常工作,允许传入被super的类或其子类对象。

目前个人感觉super是为了弥补extends的缺陷,而不是作为设计的新功能而出现的。

红 蓝 黑 灰(我全都要)?

那还是回归最本源的面向对象吧,使用Object作为最稳固的桥梁将数据传递到位后,然后再做类型转换吧。当然,这种工作应该在保证方法安全性的前提下进行,比如List类型的indexOfcontains方法。

有时间了再来具体水水。现在对泛型理解还不够深,先码一下

在Eclipse中使用Gradle配置Web项目的时候,默认包含了junit测试工具,但是打开Project and External Dependencies的时候,发现junit-4.12.jar是灰色的,可能由于配置的原因,一开始在Eclipse里面甚至提示找不到junit包。

后来在网上查阅资料发现灰色表示引用了库,但是不参与编译,即对于java编译器来说是找不到该库的。显然找不到的类Eclipse是不会给提示的,但写个java没代码补全这日子咋过。

于是打算用各种方法,包括在build.gradle里把scope改成implementation,在Eclipse的build path里添加自带的junit库,总而言之要想办法把红线去掉,顺便把代码提示搞出来。<-这些都是错误示例。

最后重启了一下Eclipse就发现好了。

在初步接触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操作之后,问题顺利解决。