1. 基本语法
1. 变量 var
1 | var name:string="hello"; |
2. 常量 val
1 | val name="string"; |
3. 这里是没有 ++ 操作和 – 操作的
4. 函数调用
不用使用对象来调用,导入包以后直接调用即可
1 | import scala.math._ |
5.apply()
默认调用了object的apply函数
1 | "hello"<6>; |
静态资源直接放在 webapp/web 下,而我们的模板一般是在 WEB-INF 下,但是 WEN-INF 下的东西一般不让访问的,模板之所以能访问到是因为有模板引擎的映射,但是我们的警惕资源应该是直接能访问的东西,直接放在 web 下类似于 jsp 直接访问,而不能放在 WEB-INF 下,并且我们要开启静态资源访问 <mvc:default-servlet-handler/>
一开始除了乱码想着直接在 web.xml 中配置编码过滤器,接着发现根本没用还是一样的乱码,并且返回的是 ISO-8859-1的西欧字符集。后来想着如果是不是 SpringMVC 的问题,那么可能就是因为视图是被 Thymeleaf 渲染的导致的问题,然后找到 Thymeleaf 在 SpringMVC 中的配置,然后更改编码。重新启动项目才行。
今天一开始直接用了 Idea 来创建一个 SpringMVC+Spring+Mybatis+Thymeleaf 的项目,一开始还是挺顺利的,除了在 Thymeleaf 那个地方卡了一下,后面项目还是顺利跑起来了。
接着想用 Maven 搭建,因为一开始用 Idea 生成的项目使用的手动导入 jar 这样非常费力,为了一劳永逸和简单就采用了 maven 来构建项目。最后发现自己陷入了一个大坑,好久没有跳上来。
接着我就把用 Maven 搭建 SpringMVC + Spring + Mybatis + Thymeleaf 项目过程写下来,免得日后再采坑,以后项目直接拷贝就可以了不用再配置了!
在这里面勾选上 web 模块,接着我们会看到我们的项目中多了一个 web 目录也就是最重要的 WEB-INF 等等。
我们把 web 模块移动到这里,方便观看。以及在 maven 项目之内,接着我们需要改一下项目配置,因为我们移动了这个模块。如果移动的时候更新了依赖就无需这步操作。
创建配置文件如上。
1 |
|
1 |
|
数据库配置文件 properties.properties
1 | url=jdbc:mysql://localhost:3306/dbspring |
1 |
|
这步很重要,否则会一直报错,说找不到 class。
双击选中,三击选中整行,选中即复制。即任何选中状态的字符串都被放到了系统剪切板中。
命令 | 说明 |
---|---|
command + t | 新建标签 |
command + w | 关闭标签 |
command + 数字 command + 左右方向键 | 切换标签 |
command + enter | 切换全屏 |
command + f | 查找 |
command + d | 垂直分屏 |
command + shift + d | 水平分屏 |
command + option + 方向键 command + [ 或 command + ] | 切换屏幕 |
command + ; | 查看历史命令 |
command + shift + h | 查看剪贴板历史 |
ctrl + u | 清除当前行 |
ctrl + l | 清屏 |
ctrl + a | 到行首 |
ctrl + e | 到行尾 |
ctrl + f/b | 前进后退 |
ctrl + p | 上一条命令 |
ctrl + r | 搜索命令历史 |
1 | git clone https://github.com/zsh-users/zsh-autosuggestions ~/.oh-my-zsh/custom/plugins/zsh-autosuggestions.git |
在 .zshrc 中添加 zsh-autosuggestions
接着执行如下命令
1 | ➜ ~ source .zshrc |
即可使用了!
<!—more–>
DispatcherServlet
HandlerMapping
查找对应的 Controller
或者说 Handler
Controller
就让 HandlerAdaptor
去执行 handler
handler
以后返回的就是 ModelAndView
对象。ViewResovr
去解析在 web.xml 中我们需要配一个 Servlet 和一个 Listener ,这个 Servlet 其实就是我们的路由调度器,然后 Listener 则是上下文监听器。还有一个初始化参数就是指定 spring 的配置文件的位置。
其实这些配置基本都是固定的,在使用 idea 建立 SpringMVC 项目的时候他会自动的帮我们配置好,但是我们还是需要在进行一些配置。主要的配置如下:
1 |
|
接着就是配置 spring 配置文件,可以看到在上面的 web.xml 中我们在初始化参数中指定了 spring 的配置文件就是 applicationContext.xml 放在了 WEB-INF 路径下面。
然后需要配置一些核心的 bean 让 spring 进行自动加载,以需要配置扫描的包。
1 |
|
1 |
|
这样我们的程序就能够跑起来了。
一般的我们无法直接使用 RESTful 风格的请求,但是在 SpringMVC 中有一个过滤器可以帮我们把一个 post 请求转化成为 PUT
或者 DELETE
请求。具体的做法如下:
1 | <filter> |
1 | <form method="post"> |
这个注解主要就是用来做方法和类的 url 映射的,也就是相当于一个路由映射文件。这个注解可以使用在类上面也可以使用在方法上面,在类上面的话就是我们访问每一个方法的时候都需要加上类的 url 前缀。
他有几个比较重要的属性,用来管理 url 的:
value: 这个是默认的,是 url 地址
method:这个是用来指定请求方式的 RequestMethod.GET/POST/DELETE/PUT ….
params:这个是用来规定我们的 url 中携带的参数的他是一个数组,可以放多个值
1 | params = {"username","age!=10"} params 是包含 username age!=10 headers 也是如此只是规定了请求头而已 |
这个注解的作用是用来传递参数的,我们不仅仅可使用 ?
来传递参数,还可以使用更优雅的 /paramName/value
的方式来传递参数,并且能够直接在方法中绑定这些参数。
1 |
|
这里的 id 必须和 url 中的保持一致,但是无需和方法的参数保持一致。
但是有一个问题,当我们传递过来的参数是通过 ?
的形式传递过来的,那么我们又该怎么去获取他呢?是的这里我们可以使用 @RequestParam
方法来获取这些值,当我们有些参数不是必须传递的我们就可以使用 require=false
来规定不用必传,这个时候如果我们没有传值,这个参数刚好是一个引用类型的就会是 null
但是如果是一个基本数据类型,就会报错。我们必须手动的设置一个默认值。
1 | Integer age |
上面的参数就可以获取到 http://localhost/hello?age=10
这种的 url 的参数了、
用法同上!
1 | String content |
这个使用同上,获取 Cookie 的值。
我们的表单提交过来的数据会自动的被封装到 POJO 对象中,我们只需要配置好表单的 name 值和 Bean 的属性值一致即可,如果说里面有级联的属性,我们就使用 proA.proB
来封装。例如:
1 | <form method="post"> |
这个表单就会被封装成一个 POJO 对象,这个对象里面有另外一个类的引用就导致了级联属性的出现,我们是就是使用了点的方式完成的封装。
它支持比较多的原生的 Servlet 的 API ,其实是在他内部调用了 request 对象的一些方法获取到的。
一般我们需要在控制器里面绑定一些数据到视图中,然后我们可以在视图里面采用标签来获取 Controller 里面的数据从而展示这些数据,在 SpringMVC 中有几种方法可以达到这个目的。
这个东西其实就像他的名字一样,他是 Model 数据和模型的结合体,我们可以往里面添加数据(Model),也可以把要转发的页面放在里面让视图解析器去渲染。所以说这个对象里面有一个 Model 属性,这个属性就是用来存放数据的,其实就是一个 Map 。Map 里面的这些数据都会被遍历然后放到 request 域对象之中,我们只需要在请求域中获取就好。
1 | /** |
其实三个东西类型都是 Map 类型的,然后SpringMVC 在真正的传入的对象显然就是他们的实现类,这里我们不过分纠结,基层肯定是一个 Map 。Map 里面的这些数据都会被遍历然后放到 request 域对象之中,我们只需要在请求域中获取就好。
这个用起来也比较简单,就是在方法的入参里面传入这个一个东西就行了,而不是采用的返回值。Map 的具体的泛型就是 string 和 object。看下面的例子。
1 | public String modelMap(Map<String,Object> map){ |
这个注解只能放在类上面,然后我们使用 value 属性或者 types 属性来规定哪些属性需要被放在 Session 域中,这个两个属性其实都是一个数组,所以我们可以方多个值。
value 这个属性,就是当我们在放入 map 中的一个键名的时候我们就可以把它放到 session 域中。而 types 属性则是当我们放一个 class 的时候他会自动抓取处于 map 中的同类型的数据。
1 |
这个注解是标识在方法上的,这个注解标识的方法会在所有的方法调用前被调用。在这个被标识的方法里面我们需要从数据库中获取对应的对象,然后把这个对象放到 map 里面,但是注意 map 中的键必须要是我们的 Model 类对应的小写的一个字符串才能起作用。当我们使用其他的方法来进行某个 model 的修改动作的时候我们某个字段不传的话这时候在 map 中的那个对象的对应字段挥起一个补充作用,把对应字段填上。
执行流程:
视图的解析步骤:
可以手动配置路由,不经过 controller 就可以访问到对应的视图。在 spring 配置文件里写上如下内容:
1 | <mvc:annotation-driven /> |
那么我们访问 http://localhost:8080/success
就被转发到 WEB_INF/templates/hello.jsp 具体的目录取决于我们配置的视图解析器的前缀和后缀。
我们一般采用的就是 InternalResourceViewResolver
这个视图解析器,我们也可以自定义视图。自定义视图则需要一个特殊的视图解析器完成解析视图的工作,就是 BeanNameViewResolver
就是通过视图的 bean 的 name 来获取视图的。由于我们配置了多个视图解析器则需要定义一个优先级,哪个视图解析器先工作,使用 order 属性。
1 | <!--自定义的 Bean视图解析器 直接通过 bean 的 name 获取视图--> |
下面是我们使用 bean 定义的一个视图。
1 |
|
我们一般在 controller 中写的东西默认都会转发的,我们需要重定向的话我们只需要在人绘制前面加上 redirect 就可以。
1 |
|
对于静态资源我们需要直接获取而不需要进行映射,所以说我们在获取静态资源的时候回出现 404 ,我们就配置一个
<mvc:default-servlet-handler/>
这个就会自动的处理没有映射的 url 。
表单数据在提交以后实际上是依赖于 SpringMVC 里面一个 WebDataBinder 类进行的数据绑定,这个 WebDataBinder 里面有很多其他的对象的引用其中就有数据格式化、数据校验、数据转换的对象,也就是说在这个数据转换的过程我们是可以添加一些对象来手动的控制数据的绑定的。
1 |
|
只需要在方法上加上 @ResponseBody 就可以,返回值是一个 List 或数组。
1 | <!--配置拦截器--> |
1 | public class MyInterceptor implements HandlerInterceptor { |
统一异常处理就采用对应的 handler 来处理异常,使用 @ExceptionHandler 注解,然后标注要处理的异常类型,但是如果说我们需要把异常带到错误页面我们不能使用 map 而只能使用 ModelAndView 不然那就会报错。
1 |
|
如果他在当前的 controller 中找不到对应的 ExceptionHandler 就去查找对应的 @ControllerAdvise 注解表示的类,中的ExceptionHandler 注解方法。也就是默认的处理器。
NioEventLoop 的核心就在于它的 run()
他是在第一次添加任务的时候开始执行。那我们先看看第一次添加任务的地方,其实第一次添加任务的地方是在父类中的 execute()
方法。所以先去分析一下 SingleThreadEventExecutor
的execute()
方法。我把代码精简了贴出来,只看核心的部分。
1 | public void execute(Runnable task) { |
很明显,也就是我们往线程池中添加任务的时候,首先要看看我们的线程是不是已经启动了,没有的话首先我们需要启动一下线程。接下来要看看 startThread()
方法干了什么事。里面做了一些检查也就是线程只能被 start
一次,然后直接调用了 NioEventLoop
封装的 thread
的 start()
很简单!
但是,等等这个线程是从哪来的?我们并没有显示的传入,来到 SingleThreadEventExecutor
构造方法,我们会发现他在构造器中进行了初始化,但是不是直接 new Thread
而是使用了我们传的线程工厂,然后在工程里面 new 了这个线程需要执行的任务。
他的任务就是先执行一下 run 方法,然而他的 run 方法是抽象的,自然就调用到子类去了,这也就解释了为什么说是第一次添加任务的时候调用了 NioEventLoop
的 run
方法。
贴一下对 thread
初始化的代码(精简过后的):
1 | // new 了一个新的线程 |
那好,我们在上面已经看到了我们在创建一个 NioEventLoop
的时候会创建一个线程,这个线程的任务就是去调用子类的 run 方法。当我们执行 execute( task )
方法,添加一个新任务去运行的时候,就会判断当前线程是不是启动了,否则启动我们一开始创建的那个线程。用一张图说明一下!!!
好的现在正式的看一下 run 方法,还是贴一下核心代码:
1 | protected void run() { |
可以看到在代码里面的死循环中值做了三件事:select、processSelectedKeys、 runAllTasks .借一张图来看:
具体做的事情放到下面一一道来!
如果有任务的话直接去 selectNow() 也就是不进行等待的 select() ,而没有任务的时候就进行自旋等待的 select() 。下面是 select() 的核心代码,可以看待里面调用了 selectNow() 所以说这个就是一个自旋的 selectNow() 。
1 | /** |
wakenUp
表示是否应该唤醒正在阻塞的 select 操作,netty在进行一次新的loop之前,都会将 wakeUp 被设置成false,标志新的一轮loop的开始。