首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

Java Web 拾遗

编程知识
2024年09月25日 09:35

许是年纪大了,老是回忆起以前的点点滴滴。翻看当初的代码,如同偶遇多年未见的前女友,曾经一起深入交流的情谊在颔首之间消散,令人烦躁。

今天就来聊聊老生常谈的 Java Web 开发。缘于一个简单的Spring Boot项目改造,笔者看着一坨注解和配置,苦于拾掇记忆的痛苦,择其一二记录,纪念逝去的青春。

本文对新手有一定帮助,大家笑过勿喷。

JSP + JavaBean

笔者学生时代接触了JSP,作为远古产物,现在已难觅踪迹,但与它一同出现的JavaBean,却一直留传了下来。

在任何开发模式下,都需要一套规范,JavaBean 就是符合这些规范的类/对象,比如:

  • 所有字段为 private(不允许外部直接访问,避免以后重命名/删除等操作引发依赖故障)
  • 提供默认构造方法(方便外部实例化)
  • 提供 getter 和 setter(自定义属性的读写逻辑)
  • 实现 serializable 接口(序列化支持)

注意,JavaBean 不是 POJO,因为它需要方法、事件等处理和响应业务。它包含所有的数据和业务逻辑,开发时在 HTML 中嵌入后端代码调用它们,如下所示:

<%@ page language="java" import="java.util.*,com.cy.bean.*" pageEncoding="utf-8"%>
<%
String path = request.getContextPath();
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=path%>">
  </head>

  <body>
    <%CheckUserBean cub=new CheckUserBean(); %>
  <jsp:useBean scope="request"></jsp:useBean>
  <jsp:getProperty property="name" name="user"/>
  <jsp:setProperty property="password" name="user"/>
  <%if(cub.checkUser(user)) {%>
  <jsp:forward page="success.jsp"></jsp:forward>
  <%}else{%>
  <jsp:forward page="fail.jsp"></jsp:forward>
  <%} %>
  </body>
</html>

上述有 UserBean 和 CheckUserBean 两个 JavaBean,其中 UserBean 用于展示数据及接收用户输入,CheckUserBean 用于判断用户是否合法。

后来,JavaBean 的一些特征被开发人员沿用下来,同时概念简化为Bean,推广至更多的框架。对大部分后起的语言(比如 C#)来说,因为有 Java 帮忙踩的坑,它们往往在语言设计之初就提供了语言特性来更方便自然地贴合这些规范。

Servlet

JSP + JavaBean 的模式有一个明显的缺点,即隐性的页面跳转(数据流转),提高了开发过程中的出错概率,比如同一个页面可能由多个不同页面跳转过来,而相应的数据结构并不相同,开发人员要考虑所有可能的情况,并提供相应的 JavaBean 承接这些数据。同样随着业务发展,这种跳转或数据结构都会经常发生变更,开发维护成本极高。

于是增加了Servlet(一般继承自HttpServlet,该类定义了几个简单明了的方法,此处不赘述)来处理请求、填充 JavaBean/调用 JavaBean 方法、选择返回哪个视图等,并且加上了路由的配置,形成了基础的MVC模式。

路由的配置在web.xml中,如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app>
    <!-- other configrations -->

    <!-- 声明 servlet -->
    <servlet>
        <servlet-name>login</servlet-name>
        <servlet-class>com.cy.servlet.LoginServlet</servlet-class>
    </servlet>
    <!-- 路由配置 -->
    <servlet-mapping>
        <servlet-name>login</servlet-name>
        <url-pattern>/login</url-pattern>
    </servlet-mapping>

</web-app>

值得一提的是,出现了Filter(过滤器)的概念,即在 servlet 处理请求之前和返回响应之后的中间处理器,可以提供与业务无关的通用功能,比如身份校验、限流、异常处理等。这种 AOP 理念非常好,也一直保留至今。

同样, Filter 也需要配置,如下:

<web-app>
    <!-- other configrations -->

     <filter>
         <filter-name>jsp</filter-name>
         <filter-class>com.cy.filter.DemoFilter</filter-class>
     </filter>
     <filter-mapping>
         <filter-name>jsp</filter-name>
         <url-pattern>/*</url-pattern>
     </filter-mapping>

</web-app>

注意,Servlet 须运行于 Servlet 容器(如Tomcat)中。

Struts

为了提高开发效率,在 Servlet 基础上,提供了一些通用模块和工具,制定一套规范,形成一个框架,最知名的当属Struts,它有 1、2 两个版本。这两个版本并非简单的升级,而是整个设计的更替。

Struts1

Struts1 使用一个单例核心ActionServlet接收所有请求,请求数据转化为ActionForm,然后依据配置(struts-config.xml中的ActionMapping)分发给不同的Action。Action 一般只包含一个 excute 方法用于处理业务。

Struts1 很明显的缺点导致现在基本没人会去用:

  • 配置繁琐
  • ActionServlet 单例模式,须考虑线程安全
  • 依赖 Web 容器,单元测试不方便

Struts2

于是Struts2被推出。

它使用Interceptor(拦截器) + Controller(即 Struts1 中的 Action)的模式,使得整个处理流程扩展性大大提高了。

同时它摈弃了单例模式,每次都会实例化新的 Controller 处理请求(其中可包含任意多的方法用以执行不同业务),不用担心线程安全问题,缺点是并发量高的时候对象实例激增内存吃紧。

框架借助本身的拦截机制,将请求和响应数据映射为 POJO,实现了 Controller 对HttpServletRequestHttpServletResponse这样的原生 Servlet 对象的剥离,即 Controller 不依赖于 Web 容器,可以方便地单元测试了。

还记得上面 Servlet 的过滤器吗,Struts2 拦截器和它的原理一样,只不过前者面对所有请求,后者针对的是某个具体的 Controller。当然,Struts2 同时使用了两者。

相比 Struts1,Struts2 有了质的飞跃,然而没过几年,它的荣光也被后起之秀所掩盖。

Spring MVC

说起Spring MVC,不得不先说说Spring

Spring

Spring是 Java 平台流行的 IOC 和 AOP 框架,虽然它本身不针对特定的使用场景,但是 Java 平台的 Web 基因一开始就影响着它,所以我们惯常使用它来开发后端服务。Spring 官方有专门的子项目Spring Web,Spring MVC 就是 Spring Web 的子模块。Spring Web 包含很多其它模块,如Spring WebFlux、Spring Web Service、Spring WebSocket等

Java 后半程在移动端大放异彩,有另一个 IOC 框架Dagger在背后默默支持,可参看笔者写的
从零开始撸一个App-Dagger2
,此处不赘述。

IOC

我们可以通过在 XML 文件(使用ClassPathXmlApplicationContext加载)中配置 Bean,然后在代码中使用@Autowired@Resource(来自 JSR-250,JDK 内置)注入 Bean 实例(作用域可通过scope设置,默认是单例)。

XML 配置稍显繁琐,Sping2.5 开始支持注解注入,只要在 XML 中配置<context:component-scan>(对应的有@ComponentScan注解),Spring 便会自动扫描指定包中的所有类,查找如@Component,@Service,@Repository,@Controller等注解修饰的类,并创建相应的 Bean。当然,这种方式只能配置本项目内的类。

为了使注解方式可以注入第三方类,从 3.0 开始,Spring 引入了@Configuration。使用 @Configuration 注解修饰的类(使用AnnotationConfigApplicationContext加载)中,可使用@Bean注解修饰返回 Bean 的方法。我们若要复用它处定义的配置类,可使用@Import注解,它的作用类似于将多个 XML 配置文件导入到单个文件。

XML 配置和注解配置也可以混用,比如使用@ImportResource注解引入 XML 文件。

AOP

Spring 还是提供了 AOP 功能。

AOP 分为静态 AOP 和动态 AOP。静态 AOP 是将切面代码直接编译到源代码中,如 Java 平台的AspectJ实现;动态 AOP 是指将切面代码运行时动态织入。Spring 的 AOP 为动态 AOP,实现的技术为 JDK 提供的动态代理技术CGLIB(动态字节码增强技术),两者区别如下:

  • JDK 动态代理利用拦截器(必须实现 InvocationHandler)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用 InvokeHandler 来处理;CGLIB 利用ASM框架,将目标类生成的 class 文件加载进来,通过修改其字节码生成子类来处理。
  • JDK 动态代理的目标类必须实现某个接口,只有接口中的方法才能够被代理;CGLIB 无此限制,但是因为采用的是继承模式,所以目标类或方法不能为 final。
  • 在 Java1.8 之后,大部分场景下,JDK 动态代理的效率都要优于 CGLIB。

两者尽管实现技术不一样,但都是基于代理模式,都是生成一个代理对象。

Spring 会根据目标类是否实现接口来决定使用 JDK 动态代理还是 CGLIB,当然在符合条件时也可以强制使用 CGLIB(<aop:aspectj-autoproxy proxyt-target-class="true"/>)。

Spring AOP 涉及到的注解包括@Aspect、@Pointcut、@Before、@After、@AfterReturning、@AfterThrowing、@Around、@EnableAspectJAutoProxy等,此处不详述。


Spring MVC 同样是基于 Servlet,像是 IOC 版的 Struts2,当然由于 IOC 的引入,两者的概念和组件大相径庭,但是处理请求的主干是一致的。

Spring MVC 支持的页面渲染实现,并不包含 JSP。而是ThymeleafFreemarker等。

Spring Boot

最后来谈谈 Spring Boot,它是建立在 Spring 之上的一个快速开发框架,旨在简化 Spring 应用的初始搭建以及开发过程。它通过提供默认配置、Starter dependencies等特性,极大地减少了项目的配置工作。

同样的,它不独属于 Web 开发,但我们主要还是在 Web 领域使用它。

@ConfigurationProperties

在 Spring Boot 项目中,我们常将大量的参数配置在 application.properties(Spring) 或 application.yml 文件中,然后通过@Value取值,如下:

@Value("${db.userName}")
private String userName;

其实通过@ConfigurationProperties注解,我们可以更清爽地获取这些参数值:

//@Component 注入
@ConfigurationProperties(prefix="db")
public class DbConfiguration{
  public String userName;
}

@ConfigurationProperties 并不表示成为 Spring Bean,除非配置类同时标注 @Component 之类的注解,或者在使用方标注@EnableConfigurationProperties注解(建议后者,即按需索取,而非全局可见):

@EnableConfigurationProperties(DbConfiguration.class)
public class Invoker{

    @Autowired
    DbConfiguration dbConfiguration;
}

spring.factories

如果你正在编写一个基于 Spring 的类库,其中很多对象都是以 Bean 的形式注入使用的,所以你当然希望使用这个类库的第三方项目可以将这些对象事先加载到容器中。

你可以在 ReadMe 中写明“XX 类及 XXX 类 及……必须在项目启动时实例化到容器中”,如此使用方知道他必须采用 XML 或 @Configuration 等方式写上一大段和业务无关的配置代码。

或者你可以使用 spring.factories 方案。spring.factories 其实是 Spring boot 提供的SPI机制,使用方的项目(需要在入口类中标注@EnableAutoConfiguration注解)会基于SpringFactoriesLoader检索ClassLoader中所有 jar(包括ClassPath下的所有模块)引入的META-INF/spring.factories文件,基于文件中的接口自动加载对应的 @Configuration 修饰的类并且注册到容器中。

spring.factories 为模块化、配置化提供了基石,我们经常引用的诸如“xxx-spring-boot-starter”的类库,基本上就是使用了该方案。

ps:自 Spring Boot 3.0 始,由META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports替代 META-INF/spring.factories,内容格式有所变化,原理不变。

Spring Boot 3.0 是一个比较大的改版,影响最大的改动是必须使用 JDK17 及以上版本。


由于我们常将 @ComponentScan、@SpringBootConfiguration(同 @Configuration)、@EnableAutoConfiguration 一起使用,Spring Boot 干脆出了一个@SpringBootApplication注解,将三者合一。


Spring Boot 对 AOP 的使用进行了一些改动,此处不赘述。

内置常见的服务器(如 Tomcat、Jetty),无需单独部署。


Spring Boot 虽然是一个非常成熟的拆箱即用框架,但在微服务场景下就显得过于笨重了。后续有缘的话笔者会再来聊聊 Java 平台更适合微服务运行的几个框架。

From:https://www.cnblogs.com/newton/p/18415700
本文地址: http://www.shuzixingkong.net/article/2288
0评论
提交 加载更多评论
其他文章 Java序列化、反序列化、反序列化漏洞
目录1 序列化和反序列化1.1 概念1.2 序列化可以做什么?3 实现方式3.1 Java 原生方式3.2 第三方方式4 反序列化漏洞 1 序列化和反序列化 1.1 概念 Java 中序列化的意思是将运行时的对象转成可网络传输或者存储的字节流的过程。而反序列化正相反,是把字节流恢复成对象的过程。 1
关于建表字段是否该使用not null这个问题你怎么看?
大家好,我是 V 哥,在数据库设计中,是否使用 NOT NULL 是一个非常重要的决策,直接影响数据完整性、查询性能以及业务逻辑的复杂度。使用 NOT NULL 的关键在于理解业务需求和具体场景。 下面V哥通过一些场景来分析什么时候应该使用 NOT NULL,什么时候允许 NULL。一起聊聊经验之谈
ArgoWorkflow教程(五)---Workflow 的多种触发模式:手动、定时任务与事件触发
上一篇我们分析了argo-workflow 中的 archive,包括 流水线GC、流水线归档、日志归档等功能。本篇主要分析 Workflow 中的几种触发方式,包括手动触发、定时触发、Event 事件触发等。 1. 概述 Argo Workflows 的流水线有多种触发方式: 手动触发:手动提交一
ArgoWorkflow教程(五)---Workflow 的多种触发模式:手动、定时任务与事件触发 ArgoWorkflow教程(五)---Workflow 的多种触发模式:手动、定时任务与事件触发 ArgoWorkflow教程(五)---Workflow 的多种触发模式:手动、定时任务与事件触发
从零开始学机器学习——了解回归
在本文中,我们探讨了回归分析在统计学和数据分析中的重要性和应用。线性回归和逻辑回归作为两种主要的回归分析方法,分别适用于不同类型的数据建模和预测需求。通过数学建模,它们能够揭示变量之间的关系,并且在实际应用中展现了强大的预测能力。
从零开始学机器学习——了解回归 从零开始学机器学习——了解回归 从零开始学机器学习——了解回归
C# 开源浏览器性能提升,体验Chrome级速度
前言 使用 C# 和 CefSharp 开发的全功能网页浏览器。 项目介绍 SharpBrowser 是目前最快的开源 C# 网页浏览器! 采用了轻量级的 CEF 渲染器,在呈现网页时甚至比 Google Chrome 更快。 我们对比了所有可用的.NET 浏览器引擎,最终选择了高性能的 CefSh
C# 开源浏览器性能提升,体验Chrome级速度 C# 开源浏览器性能提升,体验Chrome级速度 C# 开源浏览器性能提升,体验Chrome级速度
使用.NET并行任务库(TPL)与并行Linq(PLINQ)充分利用多核性能
前言 最近比较闲,(项目要转Java被分到架构组,边缘化人员,无所事事 哈哈哈哈) 记录一下前段时间用到的.NET框架下采用并行策略充分利用多核CPU进行优化的一个方法 起因是项目中有个结算的方法,需要汇总一个月的数据在内存中进行计算,统计,分组 ,然后产生新的数据 在某个客户那部署后发现,这个方法
使用.NET并行任务库(TPL)与并行Linq(PLINQ)充分利用多核性能 使用.NET并行任务库(TPL)与并行Linq(PLINQ)充分利用多核性能 使用.NET并行任务库(TPL)与并行Linq(PLINQ)充分利用多核性能
keycloak~关于授权码认证中的scope的实践
前言 1. scope 参数的作用 定义权限:scope 用于声明请求访问的资源和权限。常见的值包括 openid、profile、email 等。 影响返回的数据:如果你在授权请求中指定了某些 scope,在后续的 token 请求中,Keycloak 会根据这些 scope 返回相应的信息。 o
keycloak~关于授权码认证中的scope的实践
SelMatch:最新数据集蒸馏,仅用5%训练数据也是可以的 | ICML'24
数据集蒸馏旨在从大型数据集中合成每类(IPC)少量图像,以在最小性能损失的情况下近似完整数据集训练。尽管在非常小的IPC范围内有效,但随着IPC增加,许多蒸馏方法变得不太有效甚至性能不如随机样本选择。论文对各种IPC范围下的最先进的基于轨迹匹配的蒸馏方法进行了研究,发现这些方法在增加IPC的情况下很
SelMatch:最新数据集蒸馏,仅用5%训练数据也是可以的 | ICML'24 SelMatch:最新数据集蒸馏,仅用5%训练数据也是可以的 | ICML'24 SelMatch:最新数据集蒸馏,仅用5%训练数据也是可以的 | ICML'24