Spring + SpringMVC+SpringBoot
Spring入门
早期是EJB模式:面向接口开发,后面多了本地服务器
- 微服务和分布式: 微服务是一种架构风格,将应用程序拆分为小型、自治的服务,而分布式是指多个计算机或服务器之间的网络通信和协作。微服务通常是基于分布式系统的实现方式之一,但并不局限于分布式环境,也可以在单机部署中使用。
spring的概念:spring是一个开源框架,底层是用Servlet写的
spring的特点:
- 轻量级
- 依赖注入
- 面向切面编程
- 是容器(仓库,先注册再放对象,代码放在Git)
- 是框架
- 一站式
spring的功能:
- spring容器提供了IOC机制可以创建对象以及管理对象之间的调用关系,避免了硬编码造成的程序耦合
- 提供了AOP(面向切面编程)功能,可以实现很多特有的功能
- 声明式事务控制处理(控制事务提交还是回滚)
- 对JDBC进行了轻量级的封装,可以更加灵活地去操作数据库
- 提供了MVC设计模式的解决方案
- 提供了文件上传、定时器等常用工具类
- 对于其他优秀框架的支持(如:Hibernate、Mybatis等)
Spring的体系结构:
一个灵活的可热插拔的组件结构
核心组件
bean: 即组成应用的主体及由IOC容器所管理的对象。BeanFactory是IOC的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象之间的依赖。
//手动扫描配置文件来创建仓库
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
Student s=(Student) ac.getBean("b1"); //获取对象
bean的作用域:
context: 它的核心是ApplicationContext接口,该接口由BeanFactory接口派生而来,并包括了BeanFactory的所有功能,所以通常优先采用ApplicationContext(仓库管理员)。
BeanFactory与ApplicationContext的其他区别:
- 延迟加载: BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean())才对该Bean进行加载实例化,这样我们就不能发现一些存在的spring的配置问题。而ApplicationContext则相反,它是在容器启动时,一次性创建了所有的Bean,也就是直接加载,这样在容器启动时,我们就可以发现Spring中存在的配置错误。
- BeanFactroy需要手动注册,ApplicationContext则是自动注册。
IOC
是Spring的核心!
IOC概念:即控制反转,与传统(程序之间的关系由程序代码控制)相反,由容器控制程序之间的关系。IOC≈反射+内省+工厂模式
思想转变:本来对象是由程序员创建,现在由Spring容器创建并储存,权力转移,不再需要程序员创建,然后我要什么你给我什么
DI概念:即依赖注入,组件之间的依赖关系由容器在运行期决定,形象的来说,即由容器动态地将某种依赖关系注入到组件中。
思想转变:要/给对象的一个过程;电脑依赖U盘传文件
两者的区别:
- 是对同一件事情的不同描述
- IOC:从容器的角度描述,容器控制应用程序,由容器反向地向应用程序注入应用程序所需的外部资源。
- DI:是从应用程序的角度描述,应用程序依赖容器创建并注入它所需的外部资源。
AOP
概念: 即面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
在不改变原来的代码增加新的特定功能。没有IOC就没有AOP。
事务
概念: 事务首先是一系列操作组成的工作单元,该工作单元内的操作是不可分割的,即要么所有操作都做,要么所有操作都不做,这就是事务。
分为显式事务和隐式事务:
- 显式事务需要手动提交
- 隐式事务会自动提交
事务必需满足ACID(原子性、一致性、隔离性和持久性)特性,缺一不可: 面试经常问
- 原子性(Atomicity)
即事务是不可分割的最小工作单元,事务内的操作要么全做,要么全不做 - 一致性(Consistency)
在事务执行前数据库的数据处于正确的状态,而事务执行完成后数据库的数据还是处于正确的状态,即数据完整性约束没有被破坏;如银行转帐,A转帐给B,必须保证A的钱一定转给B,一定不会出现A的钱转了但B没收到,否则数据库的数据就处于不一致(不正确)的状态 - 隔离性(Isolation)
并发事务执行之间无影响,在一个事务内部的操作对其他事务是不产生影响的,这需要事务隔离级别来指定隔离性 - 持久性(Durability)
事务一旦执行成功,它对数据库的数据的改变必须是永久的,不会因比如遇到系统故障或断电造成数据不一致或丢失
在实际开发中数据库操作一般都是并发执行的,即有多个事务并发执行,并发执行常见问题如下:
- 丢失更新
两个事务同时更新一行数据,最后一个事务的更新会覆盖掉第一个事务的更新,从而导致第一个事务更新的数据丢失,这是由于没有加锁造成的 - 脏读
一个事务看到了另一个事务未提交的更新数据 - 不可重复读
在同一事务中,多次读取同一数据却返回不同的结果;也就是有其他事务更改了这些数据 - 幻读
一个事务在执行过程中读取到了另一个事务已提交的插入数据,即在第一个事务开始时读取到一批数据,但此后另一个事务又插入了新数据并提交,此时第一个事务又读取这批数据但发现多了一条,即好像发生幻觉一样
常用第三个隔离级别read_committed
事务的传播策略: 一般用第一个required
IOC实现
IOC里面用到的设计模式有:
- 单例模式
- 工厂模式
- 观察者模式
依赖注入常见的几种方式
- Setter注入
- 构造器注入
- 工厂注入
- 接口注入
DI 依赖注入,为对象赋予属性普通值,或者维护对象之间的关系,看到有ref要想到给对象
setter注入:
<!-- setter方法注入,先通过类中的无参构造方法实例化对象并初始属性值,再通过类中setter方法来为属性赋值 -->
<bean id="b1" class="com.cwl.study.Animal">
<property name="name" value="大象"></property>
<property name="sex" value="公"></property>
<property name="age" value="5"></property>
</bean>
<!-- 还可以换成命名空间的写法:-->
<bean id="b1" class="com.cwl.study.Animal" p:name="大象" p:sex="公" p:="5"></bean> <!-- 加p:dog-ref="d"表示关联的对象,ref是references-->
<bean id="d" class="com.cwl.study.dog" p:name="萨摩耶" p:sex="母" p:="2"></bean>
构造器注入:
<!-- 构造器注入,通过构造方法来为属性赋值并实例化对象 -->
<bean id="b2" class="com.cwl.study.Animal">
<constructor-arg name="name" value="熊猫"></constructor-arg>
<constructor-arg name="sex" value="母"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<!-- 还可以通过属性类型和在有参构造方法中的下标为其赋值,其中type方式可能会出现类型顺序不匹配问题-->
<constructor-arg type="java.lang.String" value="熊猫"></constructor-arg>
<constructor-arg index="0" value="熊猫"></constructor-arg>
</bean>
工厂注入:
<!-- 工厂注入,通过事先声明好的工厂方法来获取实例化的对象 -->
<bean id="b3" class="com.cwl.study.Animal" factory-method="getAnimalFactory"></bean>
接口注入: 通过实现接口,在接口中定义依赖的setter方法,然后在配置文件中注入实现类
反射和内省
反射和内省是IOC的部分底层
- 反射主要是用来获取一个类的初始化对象
- 内省主要是用来获取对应的属性和方法
反射: 可以通过类所在的路径去加载类中的所有信息(属性、方法、构造方法)
Class cla=Class.forName("com.cwl.study.Animal");//包含当前类的所有信息的字节码对象,注意C是大写
//通过字节码对象来获取类中的属性、方法、构造方法
Field[] fs=cla.getDeclaredFields(); //获取属性
for (Field field : fs) {
System.out.println(field);
}
Method[] ms=cla.getDeclaredMethods(); //获取方法
for (Method method : ms) {
System.out.println(method);
}
Constructor[] cs=cla.getDeclaredConstructors(); //获取构造方法
for (Constructor constructor : cs) {
System.out.println(constructor);
}
要使用Class类的方法,必须先获得Class类的实例,获得Class类实例的常用方法有如下三个:
- Object类中的getClass方法:适用于通过对象获得Class实例的情况
任何类都继承到了getClass方法,任意对象可以调用getClass方法获得Class实例 - 类名.class方式:适用于通过类名获得Class实例的情况
任何类名加.class即返回Class实例,例如 Class clazz=String.class; - Class类的静态方法 forName(String name):适用于通过类型获得Class实例的情况,尤其类名是变量例如:Class.forName(className);
示例说明:
String s="hello";
//使用对象名获得Class实例
Class clazz1=s.getClass();
//使用类名获得Class实例,类名必须是常量
Class clazz2=String.class;
try {
//使用类名获得Class实例,类名可以是变量
Class clazz3=Class.forName("java.lang.String");
} catch (ClassNotFoundException e)
e.printStackTrace();
}
内省: 在字节码对象之中直接获取属性以及对应的setter等方法
BeanInfo bi=Introspector.getBeanInfo(obj.getClass(), obj.getClass().getSuperclass());
PropertyDescriptor[] pds=bi.getPropertyDescriptors(); //拿到所有的属性详情
//遍历输出显示属性名、类型、方法
for (PropertyDescriptor propertyDescriptor : pds) {
System.out.println(propertyDescriptor.getName());
System.out.println(propertyDescriptor.getPropertyType().getCanonicalName());
System.out.println(propertyDescriptor.getWriteMethod());
}
bean的管理
bean的生命周期:
- init()方法是在调用构造方法实例化对象初始化完成后才执行的,做一个补充初始化
- destroy()是在容器调用close()方法关闭时才开销毁
- 补充:想要容器优雅地关闭(释放所有之前的资源),可在JVM里面注册一个“关闭钩子”ShutdownHook
方法写在哪个bean上就只有哪个bean会调用,要全局bean都能调用就写在最外面的beans上
Xml配置
Spring有三种配置方式:
- Xml配置
- 注解配置
- 硬编码配置
以上的依赖注入都是手动注入,还有自动装配Autowire(注入)
自动注入的几种方式:
- byName
- byType
<!-- 手动注入 -->
<bean id="student" class="com.cwl.study.pojo.Student" p:name="小陈" p:sex="母" p:age="18" p:dog-ref="dog"></bean>
<bean id="dog" class="com.cwl.study.pojo.Animal" p:name="傻狗"></bean>
<!-- byNmae自动注入代表找寻bean的对象属性的名称,以此来IOC容器寻找对应的bean,如果有则自动注入,没有就为null -->
<!-- 前提是找寻的bean的类型和id名称和原始的对象要保持一致,否则有异常抛出 -->
<!-- 在多方里找一方 -->
<bean id="yg" class="com.cwl.sutdy.pojo.Yuangon" p:name="小陈" p:sex="女" p:age="20" autowire="byName"></bean>
<!-- id/name要和对象关联时的命名一样 -->
<bean id="gs" class="com.cwl.sutdy.pojo.Gonsi" p:no="01" p:bumen="保洁"></bean>
<!-- byType自动注入代表找寻bean的对象属性的类型,以此来IOC容器寻找对应的bean,如果有则自动注入,没有就为null -->
<!-- 前提是找寻此类型的bean有且仅有一个,如果有多个可以在不需要参与绑定的bean上加autowire-candidate="false"属性即可 -->
<bean id="yg" class="com.cwl.sutdy.pojo.Yuangon" p:name="小陈" p:sex="男" p:age="21" autowire="byType"></bean>
<bean id="g" class="com.cwl.sutdy.pojo.Gonsi" p:no="02" p:bumen="经理"></bean>
注解配置
首先在xml中添加context命名空间:
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p" 复制,把所有p改为context的命名空间,如下行
xmlns:context="http://www.springframework.org/schema/context" 下行复制引号里面的内容并把所有的beans改为context,如下下行
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
</beans>
或者点开Namespace勾选context即可
在xml中添加注解扫描器:
<!-- 使用注解的前提是必须要有注解扫描器,否则注解无法生效 -->
<context:component-scan base-package="com.cwl.sutdy.pojo"></context:component-scan>
<!-- 注意只是扫描到包名 -->
再把类注册为bean并为属性赋值:
//一方
@Component("gs") //该组件把类注册为一个名为“gs”的bean
public class Gonsi {
@Value("01") //Set注入调用Set方法给属性赋值,其中@Value还可以写在Setter方法上面修饰,一样是为了赋值,但不能写在Get方法上面
private Integer no;
@Value("开发")
private String bumen;
//多方,外键建在多方,并找一方(多方关联一方)
@Component("yg")
@Scope("prototype") //修改默认的单例模式,变为可以创建多个对象
public class Yuangon {
@Value("小陈")
private String name;
@Value("女")
private String sex;
@Value("20")
private Integer age;
@Autowired //常用,默认使用byType自动装配(注入),它同样可以修饰Setter方法
//@Resource //报错无法解决时用@Resource(java自带的),相当于byName,要注意命名要一致
private Gonsi gs;
补充:
@Qualifier指定bean的名字,通过byName方式自动装配,解决多个同类型bean的问题;也可以直接把@Autowired换成@Resource解决
AOP实现
主要应用在日志和事务中,使用AOP利用一个代理对象包装起来,然后用该代理对象取代原始对象,将日志处理的代码织入原始对象的方法执行代码中,用的是代理模式
AOP中的重要组成:
- 切面(Aspect): 杀人计划(在吃饭时等)
- 连接点(Joinpoint): 线人
- 通知(Advice): 杀人时机
- 切入点(Pointcut): 吃饭/地点
- 引入(Introduction)
- 目标对象(Target Object)
- AOP代理(AOP Proxy)
- 织入(Weaving):整体串联在一起
Xml配置
杀手例子: 在一个方法的执行之前或之后做了哪些事情,怎么去做~
//目标人物
package com.cwl.study.domain;
public class People {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public People(String name) {
super();
this.name = name;
}
public People() {
super();
}
public void eat(){
System.out.println(this.name+"正在努力干饭ing");
}
public void xizhao(){
System.out.println(this.name+"正在洗刷刷ing");
}
public String toString() {
return "People [name=" + name + "]";
}
}
//杀手
public class Killer {
public void toudu(){
System.out.println("准备投毒!!");
}
public void fangdian(){
System.out.println("准备放电!!");
}
public void shot(){
System.out.println("准备开枪射击!!");
}
}
xml的配置:
<beans>
<bean id="p" class="com.cwl.study.domain.People" p:name="小陈"></bean>
<bean id="k" class="com.cwl.study.domain.Killer"></bean>
<aop:config>
<!-- (地点)执行某个方法时 -->
<aop:pointcut expression="execution(* com.cwl.study.domain.People.eat(..))" id="sk"/>
<!-- <aop:pointcut expression="execution(* com.cwl.study.domain.People.xizhao(..))" id="xz"/> -->
<!-- (计划)哪个对象来做,针对哪个对象,和采用什么方式 -->
<aop:aspect ref="k">
<aop:before method="toudu" pointcut-ref="sk"/>
<!-- <aop:after method="fangdian" pointcut-ref="xz"/> -->
</aop:aspect>
</aop:config>
</beans>
测试类:
public class Test {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
People p=ac.getBean("p",People.class);
p.eat();
/*p.xizhao();*/
}
}
//运行结果为
//准备投毒!!
//石敏正在努力干饭ing
execution(* * *)其中的三个 * 分别代表 访问修饰符、包、方法
execution(* com.cwl.study.domain.People.eat(..))
“..”可以匹配有参和无参方法
深刻理解以下的FBI监视例子:
xml配置方式:
//目标类
public class People {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public People(String name) {
super();
this.name = name;
}
public People() {
super();
// TODO Auto-generated constructor stub
}
@Override
public String toString() {
return "People [name=" + name + "]";
}
public void smSleep() /*throws ArithmeticException*/ {
//int a=10/0;
System.out.println(name+"正在安详地睡觉中!");
}
public String smMgeeage(){
System.out.println(name+"得知危险情报!");
return "绝密信息!";
}
public void smRun(){
System.out.println(name+"准备跑路!");
}
}
//增强类
public class FBI {
/* public void jianshi(Object str){
System.out.println("正在监视中~");
System.out.println("得到的信息为:"+str);
}*/
/* public void jianshi(Exception e){
System.out.println("正在监视中~");
System.out.println("得到的异常为:"+e);
}*/
public void jianshi(ProceedingJoinPoint pjp) throws Throwable{ //线人,JoinPoint的增强版线人
//需要知道目标在做什么,从而决定是否拦截
System.out.println("FBI正在持续监视中~");
//around环绕增强默认会阻止目标方法的执行
//pjp.getTarget(); //获取目标对象
//pjp.getArgs(); //获取目标方法参数
//pjp.getSignature(); //获取目标方法
//pjp.proceed(); //运行表示执行目标方法,否则不执行
People p=(People) pjp.getTarget();
String methodName=pjp.getSignature().getName();
System.out.println("监视到"+p.getName()+"正在"+methodName);
if (methodName.equals("smRun")) {
System.out.println("发现目标准备逃跑,已立即阻止!");
} else {
pjp.proceed();
}
System.out.println("继续监视!");
}
}
xml:
<beans>
<bean id="p" class="com.cwl.study.pojo.People" p:name="石敏"></bean>
<bean id="FBI" class="com.cwl.study.pojo.FBI"></bean>
<aop:config>
<!-- 切点 -->
<aop:pointcut expression="execution(* com.cwl.study.pojo.People.sm*(..))" id="aa"/>
<!-- 切面 -->
<aop:aspect ref="FBI">
<!-- after-returning可以在目标方法后增强,并且能够获取目标方法的返回值 -->
<!-- <aop:after-returning method="jianshi" pointcut-ref="aa" returning="str" /> -->
<!-- after-throwing可以获取目标方法抛出的异常,但如果目标方法没有异常抛出,则不会触发 -->
<!-- <aop:after-throwing method="jianshi" pointcut-ref="aa" throwing="e"/> -->
<!-- 环绕增强 -->
<aop:around method="jianshi" pointcut-ref="aa"/>
</aop:aspect>
</aop:config>
</beans>
测试类:
public class Test {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
People p= (People) ac.getBean("p");
p.smMgeeage();
}
}
注解配置
上面例子的注解配置方式:
//目标类
@Component("p")
public class People {
@Value("小陈")
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public People(String name) {
super();
this.name = name;
}
public People() {
super();
// TODO Auto-generated constructor stub
}
@Override
public String toString() {
return "People [name=" + name + "]";
}
public void smSleep() /*throws ArithmeticException*/ {
//int a=10/0;
System.out.println(name+"正在安详地睡觉中!");
}
public String smMgeeage(){
System.out.println(name+"得知危险情报!");
return "绝密信息!";
}
public void smRun(){
System.out.println(name+"准备跑路!");
}
}
//增强类
@Component("FBI") //注册为bean
@Aspect //注册为切面
public class FBI {
/* public void jianshi(Object str){
System.out.println("正在监视中~");
System.out.println("得到的信息为:"+str);
}*/
/* public void jianshi(Exception e){
System.out.println("正在监视中~");
System.out.println("得到的异常为:"+e);
}*/
//注册为一个切点
@Pointcut("execution(* com.cwl.study.pojo.People.sm*(..))") //该注解只能写在方法上面,切点可以有多个
public void PointcutOne (){};
//监视并且做拦截
@Around("PointcutOne()")
public void jianshi(ProceedingJoinPoint pjp) throws Throwable{ //线人,JoinPoint的增强版线人
//需要知道目标在做什么,从而决定是否拦截
System.out.println("FBI正在持续监视中~");
//around环绕增强默认会阻止目标方法的执行
//pjp.getTarget(); //获取目标对象
//pjp.getArgs(); //获取目标方法参数
//pjp.getSignature(); //获取目标方法
//pjp.proceed(); //运行表示执行目标方法,否则不执行
People p=(People) pjp.getTarget();
String methodName=pjp.getSignature().getName();
System.out.println("监视到"+p.getName()+"正在"+methodName);
if (methodName.equals("smRun")) {
System.out.println("发现目标准备逃跑,已立即阻止!");
} else {
pjp.proceed();
}
System.out.println("继续监视!");
}
}
xml:
<beans>
<!-- 扫描IOC的注解 --> <!-- 注意包的范围是所有用到bean的地方包括Test类 -->
<context:component-scan base-package="com.cwl.study.*"></context:component-scan>
<!-- 自动扫描AOP的注解 -->
<aop:aspectj-autoproxy />
</beans>
测试类和xml方式一样
Spring源码分析
主要分析了ContextLoaderListener监听器
ContextLoaderListener的源码执行过程:
- 当Web应用启动时,Servlet容器会根据web.xml配置文件中的监听器配置,创建ContextLoaderListener对象,并调用其contextInitialized()方法
- 在contextInitialized()方法中,ContextLoaderListener会创建一个新的WebApplicationContext对象,并将其作为ServletContext的一个属性进行设置
- ContextLoaderListener会根据web.xml中配置的contextConfigLocation参数,读取Spring配置文件的位置
- ContextLoaderListener会使用XmlWebApplicationContext类来创建WebApplicationContext对象,并将配置文件的位置作为参数传递给它
- WebApplicationContext对象会根据配置文件中的内容,加载并解析Spring的配置信息,创建相应的Bean对象,并将其注册到容器中
- ContextLoaderListener会将创建好的WebApplicationContext对象保存到ServletContext的一个属性中,以便其他组件可以获取并使用它
- 最后,ContextLoaderListener会调用WebApplicationContext对象的refresh()方法,完成容器的初始化工作
SpringMVC
是Spring的一个子框架!
MVC架构设计:
转发和重定向的区别: 地址栏,请求,请求的参数
- 转发的url地址栏不会发生改变;重定向的url地址栏会发生改变
- 转发实际上是用上一次的请求做的;而重定向是要求浏览器重新发送一次新的请求
- 转发可以携带上一次请求的参数;而重定向无法携带上一次请求的参数
在Servlet的基础上做简化:
后续在SpringBoot框架连配置都不用,直接写代码即可
补充:配置文件加载的结果就是上下文(context)
- getHandler:找到对应路径所在的位置(对应哪个方法)
- getHandlerAdapter:适配器, 不同的handler运行需要不同的处理器、适配器来运行
- Handler:最后由它来做运行,并可以获取参数,再由ViewResolver来渲染显示
SpringMVC八大原理流程
(面试经常问,一定要背熟再去理解为自己的话语,特别重要!!)
-
用户向服务器发送请求,请求被Spring前端控制ServeltDispatcherServlet捕获
-
DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回
-
DispatcherServlet根据获得的Handler,选择一个合适的HandlerAdapter。执行相应的Controller(附注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(…)方法)
-
提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)。在填充Handler的入参过程中,根据配置,Spring将帮你做一些额外的工作:HttpMessageConveter
• 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息;
• 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等;
• 数据格式化:对请求消息进行数据格式化。如将字符串转换成格式化数字或格式化日期等;
• 数据验证:验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中 -
Handler执行完成后,向DispatcherServlet返回一个ModelAndView对象
-
根据返回的ModelAndView,选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet
-
ViewResolver结合Model和View,来渲染视图
-
将渲染结果返回给客户端
总结概括为: 所有的请求交给dispatcherServlet,将请求解析通过HandlerMapping寻找对应的Handler,再次找到handler对应的HandlerAdapter(适配器),再通过参数的获取(argumentResolvers)以及转换交给对应的handler方法,紧接着执行;将得到的结果ModelAndView交给dispatcherServlet控制的viewResovler视图渲染器进行渲染,最后返回给用户。
图解:
Spring运用了很多设计模式: IOC容器应用工厂设计模式、Bean的管理应用单例设计模式、AOP切面编程应用代理设计模式、HandlerAdapter应用适配器设计模式、不同的HandlerMapping找寻对应的Handler应用策略设计模式、参数的获取以及转换AgurmentResovler应用责任链设计模式、监听器应用了观察者设计模式等,设计模式详细说明移步JAVA笔记查看
注解
入门例子及相关注解、配置的解读
web.xml:
<listener> <!-- 上下文(bean)加载的监听器 -->
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value> <!-- 加载配置文件applicationContext.xml -->
</context-param>
<servlet>
<servlet-name>DispatcherServlet</servlet-name> <!-- 访问时先经过DispatcherServlet -->
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/springmvc.xml</param-value> <!-- 加载配置文件springmvc.xml -->
</init-param>
<load-on-startup>1</load-on-startup> <!-- 为1时容器启动时就会加载该servlet,执行init方法;为0时当第一次有请求时才会加载 -->
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern> <!-- servlet的访问路径要是动态的,不能写死!这里表示匹配所有以.do结尾的路径 -->
</servlet-mapping>
<!-- 编码过滤器,处理乱码 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
applicationContext.xml:(和下面的springmvc.xml文件配置是建议分开写的)
<context:component-scan base-package="com.cwl.study.*">
<!-- 扫描不包括controller,这样可以避免重复扫描两次注解controller的问题 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
</beans>
springmvc.xml:
<context:component-scan base-package="com.cwl.study.controller">
<!-- 扫描包括controller,这样可以避免重复扫描两次注解controller的问题 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 注解驱动 -->
<mvc:annotation-driven />
<!-- 静态请求的处理 -->
<mvc:default-servlet-handler/>
</beans>
Controller:
@Controller /*控制型的bean,注意这是Spring的注解,非SpringMVC的!*/
public class DemoController {
//替代doget/dopost等方法
@RequestMapping("/index.do") //配置访问方法的路径,是SpringMVC的注解,任何注解都需要加载(注解扫描器)
public String one(){ //这是一个Handler(服务方法)
System.out.println("已经访问到了!");
return "Login.jsp"; //相对视图路径
}
@RequestMapping("/login.do") //这也是一个Handler
public String cheakLogin(String username,String password,HttpSession session){
System.out.println("获取到的账号为:"+username+",密码为:"+password);
session.setAttribute("name",username);
return "index.jsp";
//路径可带参数
@RequestMapping("/index/{msg}/{id}.do") //可带多个路径参数,需要在uri中设定占位符{xxx}
public String two(@PathVariable("msg") String str,@PathVariable("id") Integer id){ //@PathVariable接收路径参数
System.out.println(str +"and"+ id);
return "../../Login.jsp"; //相对的视图路径,注意跳包
}
//method用于限定请求方法
@RequestMapping(value="/index/{id}.do",method=RequestMethod.GET) //限定请求方法为GET
public String three(@PathVariable("id") Integer id){
System.out.println("已经访问到了!参数为:"+id);
return "../Login.jsp";
}
//params用于限定参数
@RequestMapping(value="/user/regist.do",method=RequestMethod.POST,params={"account","password"}) //这里限定请求体要有两个参数,可以有多个
public String four(@RequestParam("account") String name,@RequestParam("password") String pwd){
//@RequestParam主要是用来映射对应参数名并另取别名,不取别名可以直接写成如下:
//public String four(String account,String password){
System.out.println("注册成功!用户名为:"+name+",密码为:"+pwd);
return "../Login.jsp";
}
//headers用于限定头部信息
@RequestMapping(value="/test.do",method=RequestMethod.GET,headers="abc=aaaa") //限定请求头中必须包含"abc=aaaa"这个键值对信息
public String five(){
return "/index.jsp";
}
//@ModelAttribute获取所有参数,将封装类型的参数一一赋值,会自动将此对象存入request域中,有点类似于@RequestParam
@RequestMapping(value="/student.do",method=RequestMethod.POST)
public String addStudent(@ModelAttribute("stu") Student stu){ //需要有对应的实体Student类
System.out.println(stu);
return "index.jsp";
}
//ModelAndView可以返回视图和参数/数据
@RequestMapping(value="/test.do",method=RequestMethod.GET)
public ModelAndView mavtest(){
ModelAndView mav=new ModelAndView("index.jsp"); //ModelAndView还是在request域
mav.addObject("str", "测试数据");
return mav;
}
@RequestMapping(value="student/list.do",method=RequestMethod.GET)
@ResponseBody //将java对象转换为json数据格式,要借助jackson包
public Object getList(){
List<Student> list=new ArrayList<Student>();
list.add(new Student("陳不錯","男",20));
list.add(new Student("陳大大","男",33));
list.add(new Student("小陳","女",44));
return list;
//以下html会发送AJAX请求到这
<!-- <body>
<script type="text/javascript" src="./js/jquery-3.1.1.min.js"></script>
<script type="text/javascript">
$(function(){
$.ajax({
url:"student/list.do", //请求的路径
type:"get", //请求的方法类型
data:{}, //参数
dataType:"json",
success:function(data){ //成功执行时的回调函数
console.log(data);
}
})
})
</script>
</body> -->
}
}
Login.jsp:
<body>
<form action="login.do" method="post">
用户名:<input type="text" name="username" /><br>
密码:<input type="password" name="password" /><br>
<input type="submit" value="登录" />
</form>
</body>
index.jsp:
<body>
This is my JSP page. ${name}<br> <!-- EL表达式可以直接从session域里面取数据,但EL表达式只能在jsp页面生效,html不行 -->
</body>
重要注解讲解
Spring2.5引入的MVC重要注解:
Spring3.0引入RESTful架构风格支持(通过@PathVariable注解和一些其他特性支持),且又引入了更多的注解支持:
- @Controller注解表示该类是一个控制器,在Spring MVC框架中用于处理HTTP请求
- @Autowired注解表示通过自动装配方式将UserService实例注入到UserController实例中。这意味着我们可以在UserController实例中直接使用us对象,而不需要手动创建UserService实例
- @RequestMapping注解用于映射HTTP请求路径到具体的方法上。也可以标注在类定义上;value属性用于指定请求路径,method属性用于指定请求方法
- @ResponseBody注解表示将方法的返回值(JSON数据格式)直接写入HTTP响应体中,而不是由ViewResolver解析对应的视图文件
@Controller
public class UserController {
@Autowired
private UserService us;
@RequestMapping(value="/login",method=RequestMethod.POST)
@ResponseBody
public Object login(){}
学生信息管理系统
只是一个粗略系统,没连数据库,但有前后端的重要代码功能讲解,务必吸收透!
三个配置文件都略了
Student实体类:
public class Student {
private Integer id;
private String name;
private String sex;
private Integer age;
//其他基础方法略
}
User实体类:
public class User {
private Integer id;
private String username;
private String password;
//其他基础方法略
}
没连数据库所以没有mapper(dao)层
StudentService业务逻辑层:
public interface StudentService {
public List<Student> getAll();
public void dropStu(Integer id);
public void addstu(Student stu);
}
UserService业务逻辑层:
public interface UserService {
public User login(String username,String password);
}
StudentServiceImpl业务实现类:
@Service //注册为一个业务型的bean,方便自动注入并调用其中的方法
public class StudentServiceImpl implements StudentService {
List<Student> list=new ArrayList<Student>();
{ //代码块会自动执行一次
list.add(new Student(1,"陳大大","男",20));
list.add(new Student(2,"哈哈","女",18));
list.add(new Student(3,"QQ","男",25));
list.add(new Student(4,"滴滴","女",16));
}
public List<Student> getAll() { //获取所有学生
return list;
}
public void dropStu(Integer id) { //删除单个学生
Student s=null;
for (Student stu : list) {
if (stu.getId().equals(id)) {
s=stu;
}
}
list.remove(s); //不能在循环里面做删除操作,得在外面,不然会报错
}
public void addstu(Student stu) {
list.add(stu);
}
}
UserServiceImpl业务实现类:
@Service //注册为一个业务类型的bean,方便在控制层自动注入再调用
public class UserServiceImpl implements UserService {
public User login(String username, String password) {
if ("admin".equals(username)&&"123456".equals(password)) {
User u=new User();
u.setId(1);
u.setUsername(username);
u.setPassword(password);
return u;
}
return null;
}
}
StudentController控制类:
@Controller
public class StudentController {
@Autowired
private StudentService ss;
private Map<String, Object> map=new HashMap<String, Object>(); //用map集合存放数据
@RequestMapping(value="student/list",method=RequestMethod.GET)
@ResponseBody
public Object getAll(){ //获取所有学生信息
map.clear(); //先清空之前的数据,避免混淆
map.put("data", ss.getAll());
map.put("code", 2);
map.put("msg", "成功获取所有学生信息");
return map; //返回出去以便于前端发送ajax/axios请求后有数据(参数)返回,便于前端操作
}
//前端访问时会有路径带参
@RequestMapping(value="student/{id}",method=RequestMethod.DELETE)
@ResponseBody
public Object deleteOne(@PathVariable("id") Integer id){ //删除学生,@PathVariable用来接收路径参数
map.clear();
ss.dropStu(id);
map.put("data", null);
map.put("code", 2);
map.put("msg", "成功删除一个学生信息");
return map;
}
//添加学生
@RequestMapping(value="/student",method=RequestMethod.POST)
@ResponseBody //将返回的数据放在响应体里面
public Object addStu(@RequestBody Student stu){ //@RequestBody从请求体中拿取数据,因为get和post请求的数据放在请求体中
map.clear();
System.out.println(stu);
ss.addstu(stu);
map.put("data", null);
map.put("code", 2);
map.put("msg", "成功添加一个学生信息");
return map;
}
}
UserController控制类:
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService us;
@RequestMapping(value="/login",method=RequestMethod.POST)
@ResponseBody
public Object checkLogin(String account,String password){
Map<String, Object> map=new HashMap<String, Object>(); //用map集合在存放
User u=us.login(account, password);
if (u!=null) {
map.put("data", u);
map.put("code", 2);
map.put("msg", "登录成功!");
} else {
map.put("data", null);
map.put("code", 4);
map.put("msg", "登录失败!");
}
System.out.println(account);
System.out.println(password);
return map;
}
}
前端部分:
login.html登录页:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<form>
<label>用户名:</label><input type="text" name="username" /><br />
<label>密码:</label><input type="password" name="password" /><br />
<button type="button">登录</button>
</form>
<script type="text/javascript" src="js/jquery-3.1.1.min.js"></script>
<script type="text/javascript">
$(function(){
$("button:first").click(function(){
let name=$("input:first").val();
let pwd=$("input:eq(1)").val();
$.ajax({
url:"user/login",
type:"post",
data:{account:name,password:pwd},
success:function(data){ //成功时执行的回调函数
if (data.code==2) {
location.href="index.html"; //跳转主页
}else{
alert("用户名或密码错误!!");
}
}
})
})
})
</script>
</body>
</html>
index.html主页:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<div id="app">
<h1>首页</h1>
<a href="addmsg.html">增加学生信息</a>
<table>
<tr>
<th>编号</th>
<th>姓名</th>
<th>性别</th>
<th>年龄</th>
<th>操作</th>
</tr>
<tr v-for="(s,index) in slist">
<td>{{s.id}}</td>
<td>{{s.name}}</td>
<td>{{s.sex}}</td>
<td>{{s.age}}</td>
<td><a href="#" @click="dropStu(index)">删除</a><a href="#">修改</a></td> </tr>
</table>
</div>
<script type="text/javascript" src="js/vue.min.js"></script>
<script type="text/javascript" src="js/axios.min.js"></script>
<script type="text/javascript">
let app=new Vue({
el:"#app",
data:{
slist:[
{
id:0,
name:"",
sex:"",
age:0
},
]
},
methods:{
dropStu:function(index){
axios.delete("student/"+this.slist[index].id).then(res =>{
this.slist.splice(index,1);
})
}
},
mounted() { //页面执行完会自动执行的函数,只能写一次
axios.get("student/list").then(res =>{
this.slist=res.data.data;
})
}
})
</script>
</body>
</html>
addmsg.html 添加学生学习表单页:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<form id="app"> <!-- v-model双向绑定 -->
<label>学生姓名:</label><input type="text" v-model="stu.name" /><br />
<label>性别:</label>
<input id="ip1" type="radio" name="sex" value="男" v-model="stu.sex" /><label for="ip1">男</label>
<input id="ip2" type="radio" name="sex" value="女" v-model="stu.sex" /><label for="ip2">女</label><br />
<label>年龄:</label><input type="text" v-model.number="stu.age" /><br />
<input type="button" value="增加学生信息" @click="addstu"/>
</form>
<script type="text/javascript" src="js/vue.min.js"></script>
<script type="text/javascript" src="js/axios.min.js"></script>
<script type="text/javascript">
let app=new Vue({
el:"#app",
data:{
stu:{
name:"",
sex:"",
age:0
}
},
methods:{
addstu:function(){ //在请求路径后面还可用传参
axios.post("student",this.stu).then(res=>{ //then相当于回调函数,当请求成功并返回响应时,该回调函数会被执行
if (res.data.code==2) { //res是一个响应对象(参数),它包含了从服务器返回的响应数据
location.href="index.html"; //重定向(跳转)到index.html页面
}
})
}
}
})
</script>
</body>
</html>
风格规范
- restful风格: restful接口的uri风格,程序员之间默认的风格,语义化(就是代码别人一看就懂)
- ant风格: 以*号匹配
- 正则表达式风格: 格式为{变量名:正则表达式}
AJAX
AJAX是发送异步请求
回调函数:当响应回来会触发的一个函数
SSM整合
项目管理工具:
- Ant
- Maven
- Gradle
Maven介绍:是Apache下的项目管理工具,它由纯Java语言开发,可以帮助更方便地管理和构建Java项目。
Maven的概念是指Maven是目前市场上最流行的包管理工具、项目构建工具。通过Maven可以管理整个项目从创建、开发到编译、测试、打包、发布的整个流程,进行标准化开发。特别是通过依赖机制可以优雅的解决项目开发中包的依赖问题,大大简化了项目开发、管理流程。Maven基于项目对象模型(POM)概念,利用中央信息片段管理一个项目的构建、生成、报告等等步骤,是目主流的项目构建工具。
使用Maven的优点有:
- jar包管理:从Maven中央仓库获取标准的规范的jar包以及相关依赖的jar包,避免自己下载到错误的jar包;本地仓库统一管理jar包,使jar包与项目分离,减轻项目体积
- 跨平台:Maven可以在window、linux上使用
- 清晰的项目结构:采用清晰的目录结构,可以使多人协同开发时能更好地管理各个模块
- 多工程开发:将模块拆分成若干工程,利于团队协作开发
- 一键构建项目:使用命令可以对项目进行一键构建
SSM整合有五个配置文件,详细解读如下:
都是在Maven项目里的xml配置文件
pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.chinasofti</groupId>
<artifactId>ssmdemo</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>ssmdemo Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<!--junit(测试框架):用于编写和运行单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- spring-webmvc:用于提供MVC(Model-View-Controller)框架,包括了Spring的核心功能和SpringMVC的相关组件,用于构建Web应用程序 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
<!--spring-context:这是Spring框架的核心库,提供了Ioc容器功能,用于依赖注入和管理对象;它还集成了AOP功能。提供了应用程序上下文,用于管理和配置Spring Bean-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.8.RELEASE</version>
<exclusions> <!--排除-->
<!-- Exclude Commons Logging in favor of SLF4j -->
<exclusion> <!--这里排除了commons-logging,建议使用SLF4j作为日志框架-->
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- spring-tx(事务管理):提供了基于Spring的事务管理功能,用于管理数据库事务 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
<!-- spring-jdbc(Spring的JDBC模块):封装了JDBC操作,简化了数据库访问的操作 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
<!--commons-dbcp2:用于管理数据库连接池,提高数据库性能-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.1.1</version>
</dependency>
<!--jstl(JSP 标准标签库):用于在JSP页面中使用标准的标签和函数,简化JSP页面的开发,但JSP基本已经淘汰了-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!--servlet-api:提供了Java Servlet的接口和类,是构建基于Servlet的Web应用程序所必需的。且范围为provided,这意味着它通常由Servlet容器(如Tomcat)提供,而不是包含在最终的部署包中-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<!--提供了JSP页面的接口和类-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
<!-- MyBatis(持久层框架):用于将SQL语句与Java对象的映射,简化数据库操作 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.1</version>
</dependency>
<!-- mysql-connector-java(MySQL 驱动):用于连接MySQL数据库 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!-- pagehelper(分页插件):用于实现数据库查询结果的分页功能 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.2</version>
</dependency>
<!-- MyBatis-Spring整合(连接)包:提供了MyBatis和Spring框架的集成支持 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<!-- jackson-core和jackson-databind(JSON 处理库):用于在Java对象和JSON数据之间进行相互转换 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.8.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.8</version>
</dependency>
<!-- 日志管理接口:提供了统一的日志接口,可以与不同的日志实现框架进行整合 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
<scope>compile</scope>
</dependency>
<!-- logback-classic(日志实现框架):一种基于 SLF4J 的日志实现框架,用于记录应用程序的运行日志 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.0.13</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<finalName>ssmdemo</finalName>
</build>
</project>
web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>Archetype Created Web Application</display-name>
<!-- 监听器:用于初始化spring的IOC容器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value> <!-- 初始化该路径.xml(初始化Spring)文件 -->
</context-param>
<!-- 定义了一个 servlet,用于处理所有的 HTTP 请求 -->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value> <!-- 初始化springmvc -->
</init-param>
<load-on-startup>1</load-on-startup> <!-- 定义一开始就加载,值为0就在需要(被访问)的时候再加载 -->
</servlet>
<!-- 定义了servlet的映射关系,将所有的请求都交给名为"DispatcherServlet"的servlet处理,即"/"根路径的请求 -->
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 编码过滤器,防止乱码 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern> <!-- 将过滤器应用到所有的请求(路径)上,即 "/*" 模式 -->
</filter-mapping>
</web-app>
applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 扫描注解,用于自动扫描指定包下的类,并将其作为 Spring 的组件进行注册 -->
<context:component-scan base-package="com.chinasofti.demo.*">
<!-- 过滤器,这里限定不扫描被 @Controller 注解标记的类 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 连接数据库 -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!--配置MyBatis的SqlSessionFactory对象,用于创建和管理数据库会话,并扫描加载两个配置文件-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:MyBatis-config.xml"></property>
<property name="dataSource" ref="dataSource"></property>
<!-- 分配置文件的路径 -->
<property name="mapperLocations" value="classpath:/mapper/*.xml"></property>
</bean>
<!-- 自动扫描mapper接口,动态生成mapper接口的实现类对象,会放在IOC容器中存储 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
<!-- mapper接口的路径 -->
<property name="basePackage" value="com.chinasofti.demo.mapper"></property>
</bean>
<!-- 管理事务 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="txManager"/>
</beans>
springmvc.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<!-- 扫描注解,用于自动扫描指定包下的类,并将其作为 Spring 的组件进行注册 -->
<context:component-scan base-package="com.chinasofti.demo.controller">
<!-- 过滤器,这里限定只扫描被 @Controller 注解标记的类 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 启用 Spring MVC 的注解驱动功能。它会为使用注解的控制器类注册相关的处理器和适配器,简化了开发配置 -->
<mvc:annotation-driven/>
<!-- 启用默认的Servlet处理器处理静态资源。当DispatcherServlet找不到对应的请求映射时,会将请求交给容器默认的Servlet处理。这通常用于处理静态资源(如CSS、JS文件)的请求,确保它们能够被正确地访问 -->
<mvc:default-servlet-handler/>
</beans>
MyBatis-config.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 全局设置 -->
<settings>
<!--具体的设置项,通过name属性指定设置的名称,通过value属性指定设置的值。在这里,useGeneratedKeys设置为true,表示使用数据库的自动生成键(比如自增主键)-->
<setting name="useGeneratedKeys" value="true"/>
</settings>
<!-- 配置类型别名,用于简化 SQL 映射中的类名引用,默认是类名小写 -->
<typeAliases>
<package name="com.chinasofti.demo.domain"/> <!-- 配置该包下的类 -->
</typeAliases>
</configuration>
注意涉及联表操作需要中间表,或者建立关联关系
pagehelper(分页插件)
用法如下:
- 在pom.xml文件中引入依赖:
<!-- pagehelper(分页插件):用于实现数据库查询结果的分页功能 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.2</version>
</dependency>
- 在Mybatis-config.xml文件里配置:
<!--配置分页-->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="reasonable" value="true" />
</plugin>
</plugins>
是物理查询,无关sql,直接在StudentService里加:
//分页查询
public PageInfo<StudentClassesDetail> getAllMessageByPage(Integer pageNo,Integer pageSize);
在StudentServiceImpl里面写:
public PageInfo<StudentClassesDetail> getAllMessageByPage(Integer pageNo, Integer pageSize) {
PageHelper.startPage(pageNo, pageSize); //pageNo当前页数,pageSize每页显示的条数
List<StudentClassesDetail> stulist=studentMapper.selectAllMessage(); //查
PageInfo<StudentClassesDetail> pi=new PageInfo<StudentClassesDetail>(stulist); //包装一下,自动做分页
return pi;
}
最后在Controller层传入两个参数(pageNo、pageSize)直接调用即可
JSTL标签
简介:JSTL(JavaServer Pages Standard Tag Library)是sJavaServer Pages(JSP)的标准标签库,提供了一组用于常见Web应用程序操作的自定义标签。通过使用Jstl,开发人员可以简化JSP页面的代码,并使代码更易于维护。
基础用法示例:
- <c:out> 标签用于输出值到页面上。例如:
jsp<c:out value="${message}" />
在这个例子中,${message}是Jsp页面的内置变量,其值将被输出到页面上。
- <c:set> 标签用于设置值到变量中。例如:
jsp<c:set var="message" value="Hello, World!" />
在这个例子中,message变量被设置为"Hello, World!"。
- <c:remove> 标签用于删除Jsp页面的内置变量。例如:
jsp<c:remove var="message" />
在这个例子中,message变量被删除。
- <c:catch> 标签用于捕获异常并处理。例如:
jsp<c:catch var="exception"><c:out value="${exception}" /></c:catch>
在这个例子中,如果Jsp页面抛出异常,<c:catch>标签将捕获该异常并将其输出到页面上。
- <c:if> 标签用于条件判断。例如:
jsp<c:if test="${condition}">True</c:if><c:else>False</c:else>
在这个例子中,如果condition为真,则输出"True",否则输出"False"。
- c:forEach:循环标签,用于迭代集合或数组中的元素,并执行标签体中的内容。
<c:forEach items="${users}" var="user">
<p>Hello, ${user.name}!</p>
</c:forEach>
SpringMVC也有自己的标签库如< from:from >,但是用在JSP中,它已淘汰了很少用
数据转换与校验
转换
SpringMVC通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法的参数中。数据绑定的核心部件是DataBinder,运行机制如下:
从Spring3开始,可以使用如下架构进行类型转换、验证及格式化:
@DateTimeFormat注解可对java.util.Date、java.util.Calendar、java.long.Long 时间类型进行标注:
- pattern 属性:类型为字符串。指定解析/格式化字段数据的模式,如:”yyyy-MM-dd hh:mm:ss”
- iso 属性:类型为 DateTimeFormat.ISO。指定解析/格式化字段数据的ISO模式,包括四种:ISO.NONE(不使用) – 默 认、ISO.DATE(yyyy-MM-dd) 、ISO.TIME(hh:mm:ss.SSSZ)、ISO.DATE_TIME(yyyy-MM-dd hh:mm:ss.SSSZ);
- style 属性:字符串类型。通过样式指定日期时间的格式,由两位字符组成,第一位表示日期的格式,第二位表示时间的格式:S:短日期/时间格式、M:中日期/时间格式、L:长日期/时间格式、F:完整日期/时间格式、-:忽略日期或时间格式;
@NumberForma注解可对类似数字类型的属性进行标注,它拥有两个互斥的属性:
- style:类型为 NumberFormat.Style。用于指定样式类型,包括三种:Style.NUMBER(正常数字类型)、Style.CURRENCY(货币类型)、 Style.PERCENT(百分数类型);
- pattern:类型为 String,自定义样式,如patter=“#,###”;
校验
JSR 303是Java为Bean数据合法性校验提供的标准框架,它已经包含在 JavaEE6.0中;JSR 303通过在 Bean 属性上标注类似于@NotNull、@Max等标准的注解指定校验规则,并通过标准的验证接口对 Bean进行验证;
JSR303的重要校验注解:
用法示例:
主要是用在属性上面,前端传参或赋值时会有相应的限制
public class Student {
@NotNull
@Length(max = 4,min = 2,message = "姓名应该在2-4个长度")
private String name;
@NotNull
private String gender;
@Pattern(regexp = "^[1-9][0-9]{5}(18|19|20)[0-9]{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)[0-9]{3}([0-9]|(X|x))$")
private String idCard;
@DateTimeFormat(pattern = "yyyy-MM-dd") //前端向后端传参数时才能转换
@Past
@JsonFormat(pattern = "yyyy-MM-dd",timezone = "zh")//后端向前端返回json数据时转换格式
private Date birthday;
@Range(min = 18,max = 30,message = "年龄不符合要求")
private Integer age;
//其他基础方法略
}
在项目的pom.xml文件中加入依赖:
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.12.Final</version>
</dependency>
对应的控制类:
@Controller
public class TestController {
@RequestMapping(value="/test",method =RequestMethod.POST )
@ResponseBody
public Object test(@Validated Student stu,BindingResult br) { //加@Validated注解指定要校验的对象
Map<String, Object> map=new HashMap<String,Object>();
if(br.getFieldErrors().size()>0) {
map.put("code",4);
List<String> list=new ArrayList<String>();
for (FieldError fr : br.getFieldErrors()) {
list.add(fr.getDefaultMessage());
}
map.put("msg", list);
return map;
}
map.put("data", stu);
map.put("code", 2);
map.put("msg","符合要求");
return map;
}
}
拦截器
用法示例:
//拦截器
public class TestInterceptor implements HandlerInterceptor { //实现接口HandlerInterceptor
//前
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("执行handler之前运行了!!!!!!!!!");
//登录判断/鉴权/token校验
return true; //返回true执行handle,false则不执行
}
//中
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("执行handler之后并返回但是还没有给视图渲染器渲染之前运行了!!");
}
//后
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("整体方法执行完毕,做收尾工作");
}
}
在springmvc.xml文件中添加:
<!--拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/test"/> <!-- 拦截的路径,hander的访问路径 -->
<bean class="com.chinasofti.demo.interceptor.TestInterceptor"></bean> <!-- 谁来执行拦截 -->
</mvc:interceptor>
</mvc:interceptors>
SpringMVC源码分析
主要还是针对SpringMVC的八大原理流程做分析
DispatcherServlet执行过程的源码介绍:
- 当客户端发送请求时,DispatcherServlet首先会通过Servlet容器的Servlet API接口调用doService()方法
- 在doService()方法中,DispatcherServlet会根据请求的HTTP方法调用对应的doGet()或者doPost()方法
- 在doGet()或者doPost()方法中,DispatcherServlet会调用HandlerMapping组件的getHandler()方法,根据请求的URL和HTTP方法查找对应的HandlerExecutionChain对象
- 如果找到了HandlerExecutionChain对象,则DispatcherServlet会调用HandlerAdapter组件的handle()方法执行请求处理器Controller
- Controller执行完毕后,DispatcherServlet会将处理结果封装成ModelAndView对象,并调用ViewResolver组件的resolveViewName()方法查找对应的View对象
- 如果找到了View对象,则DispatcherServlet会将ModelAndView对象传递给View对象进行渲染,生成最终的响应结果并返回给客户端,完成请求处理
继承关系如下:
实现Controller的三大方式:
- 实现org.springframework.web.servlet.mvc.Controller接口并重写handleRequest方法
- 实现org.springframework.web.HttpRequestHandler接口并重写handleRequest方法
- 使用@Controller注解 (目前使用)
以下主要是通过DispatcherServlet展开:
SpringBoot
入门
提示:更多SpringBoot笔记详情移步微服务(SpringBoot)笔记查阅
简介: 是Spring5.0版本后的叫法,主要是内置了许多Spring和SpringMVC的配置,如SSM中的web.xml、applicationContext.xml、springmvc.xml和MyBatis-config.xml等,目前只需要两个配置文件即可使用:pom.xml(用来导入相关依赖)和application.properties(主要配置数据库连接和各种属性)
实操用法: 每次创建都有一个基础核心包文件(不能删)例如com.cwl.study包之下的:
注意: 如果该核心包文件在com.cwl.study包之下,那么其他的pojo、mapper、service等都必须建立在该在包之下,例如com.cwl.study.pojo
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication //加载所有基本配置,扫描当前目录下的所有注解
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
注意:src/main/resources是一个开包(可以被直接访问),要访问它之下的文件,需要在配置文件application.properties里添加:
//该代码可以使templates文件变为开包,访问时直接跳过并填该文件之下的文件路径即可 ,例如http://localhost:8080/index.html,在handler里面返回的视图也可以访问到
spring.resources.static-locations=classpath:/templates
安全性问题,可以通过包装一下:
public class People {
@JsonProperty("pname") //取别名,前端只显示该名称pname,原本的name被隐藏
private String name;
private String sex;
@JsonIgnore //使得age不会被打包成JSON数据格式,在前端就不显示age
private Integer age;
Controller层的变化
注意和之的SSM写法做区分!
@RestController是用于定义RESTful API的注解,将Java类标记为一个控制器,用于处理HTTP请求,并且默认情况下返回JSON格式的数据。它是@Controller和@ResponseBody两个注解的结合体。其中,@Controller注解标记了该类是一个控制器,用于处理HTTP请求;@ResponseBody注解表示方法的返回值(JSON数据格式)会直接写入HTTP响应体中,而不是由ViewResolver解析对应的视图文件。
SSM的写法:
@Controller
public class UserController {
@RequestMapping(value ="/login",method =RequestMethod.POST )
@ResponseBody
public Object checkLogin(String username,String password) {
return us.login(username, password);
}
}
SpringBoot的写法:
@RestController //是@Controller的集成版,它不能返回视图,想返回视图可以继续使用@Controller
public class Test {
//以下语句公共出来,方便其他地方使用
Map<String, Object> map = new HashMap<String, Object>();
@GetMapping("/people") //简写之前的写法,Post请求就用@PostMapping等,将HTTP GET请求映射到test方法上,数据返回到people里,前端直接访问people即可
public Object test() {
map.clear();
People p=new People();
p.setName("哈哈哈哈");
p.setAge(20);
p.setSex("男");
map.put("aaa", p);
return map;
}
}
工具包
有时候需要实体类的两个工具包:
- Result,新建java类放进去就行
package com.cwl.study.domain;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
public class Result<T> implements Serializable {
private Integer code;
private String msg;
private T data;
public Result() {
}
public Result(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public Integer getCode() {
return code;
}
public Result setCode(Integer code) {
this.code = code;
return this;
}
public String getMsg() {
return msg;
}
public Result setMsg(String msg) {
this.msg = msg;
return this;
}
public T getData() {
return data;
}
public Result setData(T data) {
this.data = data;
return this;
}
@Override
public String toString() {
return "Result{" +
"code=" + code +
", msg='" + msg + '\'' +
", data=" + data +
'}';
}
public Result setCode(ResultEnum resultEnum) {
this.code = resultEnum.code;
return this;
}
}
- ResultEnum
package com.cwl.study.domain;
public enum ResultEnum {
/**
* 成功
*/
SUCCESS(200),
/**
* 失败
*/
FAIL(400),
/**
* 接口不存在
*/
NOT_FOUND(404),
/**
* 服务器内部错误
*/
INTERNAL_SERVER_ERROR(500);
public int code;
ResultEnum(int code) {
this.code = code;
}
}
ResultUtil,另外新建包com.cwl.study.utils放ResultUtil工具类
package com.cwl.study.utils;
import com.cwl.study.domain.*;
public class ResultUtil {
public static <T> Result<T> defineSuccess(Integer code, T data) {
Result result = new Result<>();
return result.setCode(code).setData(data);
}
public static <T> Result<T> success(T data) {
Result result = new Result();
result.setCode(ResultEnum.SUCCESS).setData(data);
return result;
}
public static <T> Result<T> successDefineMessage(T data,String msg) {
Result result = new Result();
result.setCode(ResultEnum.SUCCESS).setData(data).setMsg(msg);
return result;
}
public static <T> Result<T> fail(String msg) {
Result result = new Result();
result.setCode(ResultEnum.FAIL).setMsg(msg);
return result;
}
public static <T> Result<T> defineFail(int code, String msg){
Result result = new Result();
result.setCode(code).setMsg(msg);
return result;
}
public static <T> Result<T> define(int code, String msg, T data){
Result result = new Result();
result.setCode(code).setMsg(msg).setData(data);
return result;
}
}
RESTful API
RESTful API的定义:
RESTful API是一种基于HTTP协议的Web API设计风格,是现代Web应用程序和移动应用程序开发中最常用的API设计规范之一。RESTful API的核心思想是将Web资源映射为唯一的URI,通过HTTP协议提供一组简单可预测的操作来对这些资源进行访问和操作。
RESTful API的设计遵循以下原则:
- 每个资源都有一个唯一的URI标识符
- URI表示的资源可以通过HTTP请求进行访问和操作
- 对资源的操作必须使用HTTP方法(GET、POST、PUT、DELETE等)来表示
- 资源的表述(如JSON、XML等格式)与URI分离,并通过HTTP消息头来传递
- 状态无关性:每次API请求都应该包括所有必要的信息,服务器不保留任何客户端状态信息
作用: 通过遵循RESTful API的设计原则,我们可以实现一组简单、统一的API接口,使得客户端和服务器之间的通信更加清晰、简洁、可维护、可扩展和可重用,从而提高Web应用程序和移动应用程序的开发效率和效果,降低系统开发和维护的成本和难度。
JSON数据格式
JSON(JavaScript Object Notation),是一种轻量级的数据交换格式,常用于前后端数据传输。JSON格式的数据由键值对构成,键和值之间用冒号分隔,键值对之间用逗号分隔,一对花括号{}表示一个对象。JSON格式还支持数组类型。数组使用中括号[]包裹,数组元素之间用逗号分隔。例如:
[
{
"name": "Tom",
"age": 20,
"sex": "male"
},
{
"name": "Lucy",
"age": 18,
"sex": "female"
}
]
Tomcat的SPI
Tomcat的SPI(Service Provider Interface)是一种服务提供发现接口,它是一种面向接口的编程,为接口去匹配具体服务实现的机制。
Tomcat SPI的主要目的是为了实现服务的动态加载和发现。在Tomcat中,每个服务都由一个服务接口定义,然后由不同的实现类提供具体的服务。这些实现类被放置在Tomcat的classpath中,由Java的SPI机制自动加载和发现。
具体来说,Tomcat SPI的实现需要以下几个步骤:
- 在Tomcat的META-INF/services目录下创建以服务接口全名为文件名的文件,文件内容是实现类的全名。
- 将实现类所在的jar包放在Tomcat的classpath中
- Tomcat启动时,通过java.util.ServiceLoader动态装载实现类,扫描META-INF/services目录下的配置文件得到实现类的全名,然后把类加载到JVM中。
- 实现类必须带无参构造。