关于Spring的理论基础,此博客不作过多解释,但这些理论知识非常重要,尤其是 AOP 和 IoC 等,具体介绍可参考其他博客。本文简单回顾下Spring实战中的一些基础的编写方法。后面有时间将写一套更为系统的实战方法和源码解读。
解耦合在Spring中运用得淋漓尽致,AOP 和 IoC 是Spring项目中非常重要的设计思维(Martin flower将IoC换了个名字:依赖注入DI,见大佬于2004年写的博客:https://martinfowler.com/articles/injection.html)。
另外,各类 design pattern 也在Spring中充分地使用(比如工厂模式、单例模式、代理模式、责任链模式等等),因此这些知识是Spring的重要基础。
本篇博客偏重于实战,这些理论基础是前提知识,此处不再阐述。
Spring可以理解为 面向Bean 的编程(BOP,Bean Oriented Programming)。Bean的书写有一套标准,此处不累述了。此处简单回顾性注解方式下的Bean(xml方式用得越来越少了)。
Bean如何装配?
第一步:获取容器,即得到一个context。根据Bean的配置不同,可以选择基于注解的、基于xml的等5种方式,这里以基于注解为例,获取一个AnnotationConfigApplicationContext类的实例context。然后可以使用这个context获取Bean。比如,写了一个UserBean类,那么就写:context.getBean(UserBean.class) 来获取一个UserBean。
第二步:标注Bean的依赖,通过@Autowired之类的方法进行注解。
第三步:配置装配方法,也就是写一个Config类,这个类上方需要注解为@Configuration和@ComponentScan(可以自动化扫描@Autowired注解)。当然也可以不使用@ComponentScan和配套的@Autowired,而采用手写依赖注入也是可以的,即在这个Config类里,实现“获取各个Bean”的方法,注意进行依赖注入,另外对于这些方法都需要标注为@Bean即可。(如果基于xml,也有对应的配置方法)
关于被依赖变量的@Autowired,可以写在3个地方(任选一个即可):被依赖变量在内部声明的上方、构造函数上方、setter上方。
很常见的问题是,如果某个Bean有多个依赖可选,此时需要指定“优先依赖”或者说“默认依赖”(自动的“默认依赖”是根据名字匹配的,看哪个Bean的类名和实例对象名一致)。方法1:可以设置@Primary标记好“优先依赖”;方法2:或者可以使用@Qualifier("name")标记各个依赖 然后在@Autowired出新增一个对应的@Qualifier("name")【用这个name来标识选哪个Bean】。
Bean的生命周期
Bean可通过@PostConstruct和@PreDestroy等方法截获Bean的相应“生命周期状态”。
Bean的作用域
可以通过@Scope()设置Bean是单例模式(默认为单例模式)还是原型模式之类的。
Spring是实现基本servlet的,或者说是管理Bean的容器,而SpringMVC是一个框架(一个壳子)。在SpringMVC的框架下,编写的servlet 被称为Controller。纯 Spring 渲染页面是比较随性的(比如直接在servlet的doGet中返回一个jsp文件或html文件给浏览器),而在SpringMVC下,Controller通过ModelAndView来连接Model和View,即:Controller通过Model提供的数据去修改ModelAndView实例 从而设置View中的值 并进行渲染(参考下述Thymleaf 模板部分)。另外,SpringMVC使用DispatcherServlet 将请求分配(路由)给各个Controller ,而这个DispatcherServlet是默认配置的,不需要写代码。值得注意的是,SpringMVC中一个Controller似乎可以完成多个servlet的功能(如果把一个handler理解成一个servlet的话),也就是说路由更加灵活,比如:Controller可以负责导引第一级路由(适合将Controller进行归类),Controller下面的各个handle可以具体负责第二级路由(URI的第二级)。
原先的Spring部分,目前被集中写在spring.core中;因此,广义的spring是包括Spring MVC的。
SpringBoot 简化了配置流程,通过各种starter即可轻松对各种依赖进行配置,使得项目开发变得更加方便。因此,实际开发中一般使用SpringBoot进行代码编写。可参考其他博客如:Spring,Spring boot和SpringMVC关系串想
SpringCloud 实现了微服务的思想,通过微服务注册中心、微服务提供者和消费者等各个模块,轻松搭建微服务框架。
Spring 及 Spring MVC 中非常重要的特点是:使用大量的注解完成解析操作和修饰操作!包括Request的解析、数据库对接时数据的Mapping(见ORM部分)、模板页面的填充(见Thymleaf 模板)等。
Spring 中各个 servlet 一般注解为 @Component,而 Spring MVC中将 servlet 直接注解为@Controller。SpringMVC中的注解更为丰富了,有很多Bean的注解,如@Service、@Repository等等(其实某些是可以混用的,但不推荐)。
路由的解析,Spring中一般使用@router注解;而 Spring MVC中使用 @RequestMapping家族(包括GetMapping等)对Controller的各个behavior进行注解,以及使用@ResponseBody修饰behavior的返回值为HTML页面的直接渲染。值得注意的是,@RequestMapping家族的注解可以同时在class上和method上进行标记(其含义为二者进行路径拼接),以及支持参数注解@PathVariable获取URL路径中的变量。总而言之,任何一个Request的各个信息都可以通过注解获取,不再需要自己写解析。
ORM层主要有两个流派:MyBatis 和 Hibernate,对应的就是两大架构:SSM和SSH。目前MyBatis和SSM框架用得比较多了。
MyBatis 和 Hibernate之间有什么区别呢?Hibernate其实以前很常见,就是 MySQL数据库表 与 POJO(Plain Old Java Object 一个正规的Java对象)的映射。而MyBatis 则是直接将MySQL语句映射给POJO,又称 SqlMapping 的ORM实现,非常方便。
SSH(Struts,Spring,Hibernate) 和 SSM(SpringMVC,Spring,MyBatis)的区别。
SSH 通常指的是 Struts2 做前端控制器,Spring 管理各层的组件,Hibernate负责持久化层;
SSM 则指的是 SpringMVC 做前端控制器,Spring 管理各层的组件,MyBatis 负责持久化层。
共同之处是是使用了Spring的依赖注入 DI 来管理各层的组件,使用了面向切面编程 AOP 来实现日志管理,权限认证,事务等通用功能的切入;
不同之处是 Struts2 和 SpringMVC 做前端控制器的区别 ,以及 Hibernate 和 MyBatis 做持久化的区别,但是 Struts2 也可以 和 MyBatis 搭配使用,SpringMVC 也可以和 Hibernate 搭配使用。
参考自:SSH和SSM的区别
SSM是目前最常用的架构,属于Spring后端基本功。SSH中的Struts2 毕竟比不过Spring自家的SpringMVC,但其对应的Hibernate也是值得学习的(以后再看吧)。本文关注的是SSM架构。
基本步骤:MyBatis 需要进行一些配置,写在一个application.property文件中,用于连接数据库。然后再编写Mapper,值得注意的是这些Mappers都是接口类型(直观上有点不可思议,但其实是使用了动态代理类进行了实现,AOP常规操作)。
基本编写规则:
Mapper接口中,编写相应的数据操作函数,这些操作函数不需要写函数体,完全使用annotation即可。首先是MySQL操作的annotation,如@Select("select * from user"),即可将查询的结果注入Results中。再继续使用@Results注解,可以将数据库形式的结果映射成Java类(如果有多行结果,则映射成 List)。最终的映射结果将赋值给“操作函数”的返回值。因此,这个“操作函数”在别的地方愉快地调用(一般在DataService类中调用),并可以直接拿到数据库操作结果的映射结果了。
主要繁杂的工作在于编写Results。除了使用注释方法编写(又称Java-based的规则),还可以使用XML进行编写。
Spring官方推荐的文件组织方式为,将Controller对应的文件放在一起组成一个包,包括:Controller类文件、DataModel类文件、DataMapper接口文件、DataService类文件。【微信小程序中也是将一个页面的各个文件放在一起】
值得一提的是,还有一个Spring DAO的概念,DAO指的是 Data Access Object,又称为数据持久层。DAO层可以借助ORM获取数据。Web项目可以分为三层,即Web层,Service层和DAO层。DAO层的概念稍微广泛一点,指的是单纯用于获取数据的代码层(不涉及任何业务逻辑操作),那么ORM其实就是事先DAO的一种方式。
简介:Thymleaf 模板可以理解为 一个构建 html 页面的模板引擎(之前用过python中的 jinja2 模板)。常使用 th开头的属性标记对标签进行重写。这模板一个非常好的特点在于,模板本身的标记并不影响 Html在浏览器中的显示(浏览器直接忽略没有意义的Thymleaf标记)。
基本步骤:Thymleaf 需要进行一些配置,写在一个application.property文件中。然后写好前端页面代码,再在需要动态渲染的地方加入Thymleaf标记,并在Controller中编写相应的Mapping。
基本编写规则:
Spring给每个Request生成一个model;也可以自己手动生成model,即 new ModelAndView("path/html")。但这两种方式的使用有些细微的区别,具体参考Thymleaf 官网。通过给model进行setAttribute(),可以设置 ${} 标记里的值。
有些值可以全局复用的,如Html的title,这种可以使用message方式进行处理(#{} 标记)。即在application.property文件中配置message,然后将message的键值对存在message.property文件中,再构建一个messageSource的bean,去链接相应的配置。具体代码有点麻烦,网上搜一搜吧。另外,这个message方式可以用来实现网页的多语言版本。
Thymleaf 还可以根据 Controller 的 method 自动生成 链接 href ,通过 #mvc.url('MyController#myMethod') 自动生成,并可通过arg设置parameter。注意,链接是根据method上方对应的@RequestMapping家族中的URI生成的。
对于post的数据,可以通过@ModelAttribute 获取,可构建一个类实例去获取表单数据(映射式获取)。
SpringFramework中自带了security模块,加载相应的包即可。以下是代码编写步骤:
第一步:配置Adapter,即配置哪些地方以怎样的形式进行安全认证。
构建一个类继承WebSecurityConfigureAdapter,里面重写configure(HttpSecurity http) 方法,然后给 http进行各种设置即可,比如哪些URL需要登录认证,哪些URL需要什么样的角色,登录页面是哪个URL(注意安全认证依赖于登录)以及用户名的key应该是什么,登录成功后需要进行说明处理、需要跳转到哪里。
第二步:构建安全认证的逻辑,即验证用户状态和密码等。
注意到,上述类在进行注册时,需要一个AuthenticationProvider的实例,也就是提供安全认证具体逻辑的Bean(类内private了一个AuthenticationProvider的实例,并标注了@Autowired)。也就是说我们需要自己写“安全认证服务”。而安全认证服务,有需要获取用户详细信息,那么就还需要写一个“用户信息获取服务”。而获取用户信息,需要相应的UserMapper。具体操作如下:
构建一个SecurityProvider类并实现AuthenticationProvider接口,作为一个Service型的Bean。这个Bean是给上面那个类提供用户认证判断逻辑的(密码对不对啊之类的),这些逻辑封装在authenticate方法中(该方法接收一个Authentication类的参数,这个参数是用户登录时提供而构建的 且可以获知提供的用户名和密码等,该方法返回)。该类依赖于一个提供“用户信息”的服务(内部@Autowired了一个UserDetailService实例)。
而为了获得用户详细信息,需要构建一个AuthUserDetailService类并实现UserDetailService接口(根据“功能单一原则”,将这两个功能 [逻辑判断、获取用户详细信息] 分别封装),也作为一个Service型的Bean。这个Bean通过调用相应的Mapper来查询数据库,并通过loadUserByUsername函数返回用户信息(为AuthUserDetails类,实现了GrantedAuthority接口)。而SecurityProvider类中需要调用loadUserByUsername方法。
还需构建一个类:AuthUserDetails类,并实现GrantedAuthority接口(为了保证这个类具备某些安全认证能力)。这个类将以 依赖注入的方式提供给AuthUserDetailService类的实例。
上述这么多类,是不是有点晕,这么归纳一下就好了:第一,凡是提供逻辑处理的类,都称为Service,而POJO类提供Model。第二,功能单一原则,一个Service只提供一类服务,上述安全任证服务和用户信息服务是完全不同类型的服务,只是说安全任证服务依赖于用户信息服务。而这两种服务都要按照一定的规范书写(即需实现预先提供的接口)。
一些小工具
Postman用于模仿post请求
MySQL workbench 用于MySQL数据库可视化和代码管理
Spring 项目初始化工具:Spring initializr [https://start.spring.io/]
写在最后
SpringMVC中的视图层是静态前端的(虽然是动态生成,但传给浏览器的是一个静态HTML文件)。目前前端技术已经兴起,前后端分离已是趋势,前端负责的功能越来越多,后端逐渐退化为API服务器,相应地 Thymleaf 等后端模板技术在很多项目中被Vue等前端框架替代。