准备工作
- 学习前提
- 了解单例模式
- 了解代理模式
一、spring
1、 spring是什么?
简单说spring就是一个容器;也是一个框架提,供了一系列的功能组件(控制反转[依赖注入]、事件、资源、i18n、验证、数据绑定、SpEl表达式、类型转换、aop面向切面等)。
spring提供的各种功能帮助我们将复杂的问题简单化。
高内聚、低耦合,能够让你的代码读起来更加的简洁、优雅
2、spring的两个核心
2.1、IoC--控制反转(DI 依赖注入)
Inversion of Control (简称 Ioc)是编程的一种设计原则
IoC(控制反转)的主要实现方式有两种
- 依赖注入
- 依赖查找
其中,最常见的实现该原则的方式是 依赖注入
控制反转简单理解:原本是我要干的事情,我现在不干了,交给别人去干。
例:
大家之前写程序经常要创建对象,是吧?java程序创建对象其实是一件比较消耗资源的事情,而且要写一个严谨的单例也是一件很麻烦的事情,这么麻烦的事情我才不干呢?交给spring去做。何况spring帮你干还不要钱,以后我们程序所有实例化对象这么好的事情就都交给它了。
spring实现控制反转这个编程原则的 模式是依赖注入的模式。我们知道,程序在做某一件事情的时候,对象与对象之间可能存在关系,就像大家的爸妈一样,因为大家的爸妈有那层关系,才能完成某件事情,把你给造出来了吧?
spring 就是通过依赖注入 创建对象,并将对象与对象之间的关系给关联起来。
2.2.1、spring ioc使用
注:为什么说spring 是个容器,其实这个容器指的就是 它的ioc容器
- pom.xml 加入spring容器的依赖(可以理解为创造一个spring运行环境)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.0.3.RELEASE</version>
</dependency>
- 我打算让spring来帮我造一辆爱车,我先来告诉spring我爱车的模型
- 定义Car类
/**
* 汽车类
*/
public class Car {
//我爱车的引擎
private Engine engine;
//轮胎
private List<String> lunTaiList;
//map集合注入示例
private Map<String,String> map;
public Engine getEngine() {
return engine;
}
public void setEngine(Engine engine) {
this.engine = engine;
}
public List<String> getLunTaiList() {
return lunTaiList;
}
public void setLunTaiList(List<String> lunTaiList) {
this.lunTaiList = lunTaiList;
}
public Map<String, String> getMap() {
return map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public Car(){
System.out.println("spring示例化了Car类");
}
public void init(){
System.out.println("----产出了我的爱车");
}
public void destroy(){
System.out.println("----我的爱车报废了");
}
public void show(){
System.out.println("=======================");
System.out.println("大家好,这是我的三轮爱车。");
System.out.println("爱车配置如下:");
System.out.println("引擎:"+this.engine.toString());
System.out.println("轮胎:"+this.lunTaiList);
System.out.println(this.map);
System.out.println("=======================");
}
}
-
- 定义Engine类
/**
* 引擎
*/
public class Engine {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Engin{" +
"name='" + name + '\'' +
'}';
}
}
- 然后告诉spring,我的爱车具体内容的配置。创建beans.xml配置文件,该配置文件内容如下
<bean>
标签属性- id: 该bean的唯一标识,spring通过该标识找到bean
- class: 完整类路径,原理是 根据该完整类路径 利用java反射 实例化对象
- scope:
- singleton 在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,默认值
- prototype 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行new XxxBean()
- request 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境
- session 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean,仅适用于WebApplicationContext环境
- global-session 一般用于Portlet(一种容器,就像tomcat的servlet容器一样)应用环境,该运用域仅适用于WebApplicationContext环境
- init-method:指定bean被创建后调用的初始化方法,另一种方式是实现 InitializingBean 接口中的 afterPropertiesSet方法
- destroy-method: 指定bean被销毁后调用的方法,另一种方式是实现 DisposableBean 接口中的 destroy方法
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation=
"http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!--
告诉spring,实例化一个Car类
spring实例化出来的类对象都是单例
init-method:在spring实例化对象完成之后立马调用init-method指定的方法
-->
<bean id="car" class="com.zd.beans.Car"
init-method="init"
destroy-method="destroy"
>
<!--将引擎对象注入到 Car中,特别需要注意的是:
spring中的注入是调用setXxx方法,所以你们对要注入的属性一定要记得生成他们的get/set方法
-->
<property name="engine" ref="engineId"/>
<!--list 类型注入-->
<property name="lunTaiList">
<list>
<value>左前轮</value>
<value>右前轮</value>
<value>左后轮</value>
<value>右后轮</value>
</list>
</property>
<!--map集合注入示例-->
<property name="map" >
<map>
<entry key="key1" value="value1" />
<entry key="key2" value="value2" />
</map>
</property>
</bean>
<!--spring实例化爱车引擎-->
<bean id="engineId" class="com.zd.beans.Engine">
<!--给字符串类型注入值-->
<property name="name" value="地球梦" />
</bean>
</beans>
- 该告诉的都告诉了,接下来要spring这个大工厂要开工了。创建jave main方法,初始化 spring容器。main方法中核心代码如下
public static void main(String[] args) {
//根据类路径去读取 xml配置文件,来初始化spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("conf/beans.xml");
//根据ID获取spring容器中的 bean(spring帮我们实例化的一个类对象)
Car car = (Car)context.getBean("car");
car.show();
AbstractApplicationContext c = (AbstractApplicationContext)context;
//为了保证spring正常退出,能够正常的销毁bean,需要加入下面的代码
c.registerShutdownHook();
}
2.2、AOP --面向切面编程
分离业务的主逻辑和次逻辑的一种思想(日志记录、请求拦截器、事物控制)。
就这么理解吧,假如你和女友约会,
女友出门之前需要 洗澡,洗头,化妆,约会内容(吃饭、喝饮料、去游乐园),卸妆,洗澡,睡觉
你能接触的,就是和女友吃喝玩乐。你着重关心的是和女朋友一起活动的内容。面向切面能够把
你和你女朋友一起活动这块内容给抽取出来,让你着重的去设计它。
2.2.1、aop 使用
这里就用上面那个例子
- pom.xml 依赖如下
<!--spring 环境支持-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.18.RELEASE</version>
</dependency>
<!--spring aop支持-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.18.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.18.RELEASE</version>
</dependency>
- 我现在有一个专门为女友服务的类,此类专门实现我与女友一起玩耍
GrilService.java
public class GrilService {
/**
* 约会
*/
public void yueHui(String content){
System.out.println("今天和女朋友去做了["+content+"]");
}
/**
* 和女朋友看电影
*/
public void watchMovie(){
System.out.println("今天和女朋友去看了一场电影");
}
/**
* 和女朋友一起玩耍
*/
void play(){
System.out.println("今天和女朋友一起玩耍");
}
}
- 但是每次找女友约会,女友都要早上起来刷牙、漱口、化妆、 和我约会完了之后她回家后也要 刷牙、漱口、卸妆、睡觉 等,这些事情 我是不关心的,因为这不是我的主要业务。而且这些次要的事情都是重复的,我们可以将其抽取出来,定义到一个切面类中
GrilExecutor.java
/**
* 切入 女朋友约会 中做的事情具体实现类
*/
public class GrilExecutor {
/**
* 切入业务后的实现方法
* @param pjp
* @return
*/
public Object handler(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("女朋友起床");
System.out.println("女朋友早上洗漱");
System.out.println("女朋友化妆");
//真正主体业务(对女朋友的服务)
Object ret = pjp.proceed();
System.out.println("女朋友晚上洗漱");
System.out.println("女朋友卸妆");
System.out.println("女朋友睡觉");
return ret;
}
}
- 然后配置 切面与切入点的关系,xml 配置文件如下
<aop:config ref=""></aop:config>
所有切面的配置必须都放在该元素之内<aop:aspect></aop:aspect>
一个切面的配置<aop:pointcut expression="" id=""/>
切入点配置<aop:around method="" pointcut-ref=""/>
包裹的切入模式,以及指定包裹的方法
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!--aop配置start-->
<!--切面的具体实现类-->
<bean id="grilExecutor" class="com.zd.service.GrilExecutor" />
<aop:config>
<!--配置一个切面
ref:切面的具体实现类
-->
<aop:aspect ref="grilExecutor">
<!--
切入点
expression :execution(切入的位置表达式)
-->
<aop:pointcut id="pointCutGril"
expression="execution(* com.zd.service.GrilService.*(..))"/>
<!--
around标签将被切入的方法包裹起来
method:指定切面的具体实现类中的哪一个方法为次要业务实现
-->
<aop:around method="handler" pointcut-ref="pointCutGril"/>
</aop:aspect>
</aop:config>
<!--aop配置end-->
<!--为女朋友服务类-->
<bean id="grilService" class="com.zd.service.GrilService"/>
</beans>
- 然后运行,看看效果
public static void main(String[] args) {
//根据类路径去读取 xml配置文件,来初始化spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("conf/beans.xml");
//根据ID获取spring容器中的 bean(spring帮我们实例化的一个类对象)
GrilService service = (GrilService)context.getBean("grilService");
//周一和女朋友做的事情
// service.yueHui("吃饭");
//周二和女朋友做的事情
// service.watchMovie();
service.play();
AbstractApplicationContext c = (AbstractApplicationContext)context;
//为了保证spring正常退出,能够正常的销毁bean,需要加入下面的代码
c.registerShutdownHook();
}
2.2.2、aop 注解使用
二、spring mvc
1、什么是mvc(model view controller)?
mvc是一种设计模式。是英文model(模型)、view(视图)、controller(请求控制)缩写。
(画图说明mvc流程)
2、为什么要用mvc?
大家在此之前都了解过三层架构,其实都一样。
为了符合 高内聚、低耦合的思想。为了让程序容易 移植、维护。增强扩展性。职责单一。逻辑复用等等。
3、编写一个示例
在编写示例之前,先向同学们介绍一下spring mvc的父子容器
- 前面我们了解过spring的ioc容器,但如果我们使用spring mvc的框架,它有2个容器,且这两个容器具备父子关系,父容器访问不到子容器对象,子容器能访问父容器中的对象
- 子容器:同学们可以将其理解为 就是在配置 DispatcherServlet (后面会介绍到DispatcherServlet)时指定的配置文件,此文件中可以配置 Controller(控制器)、ViewResolver(视图解析器)、HandlerMapping(映射处理器)
- 父容器:就是spring的ioc容器,同学们可以将其理解为配置ContextLoaderListener时指定的配置文件,里面可以配置我们的service、dao、数据源...等
3.1、spring mvc maven依赖jar
<!--Spring mvc支持-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!--servlet的支持-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<!--jsp的支持-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
</dependency>
<!--jsp的jstl标签库支持-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
3.2、核心调度的servlet
核心类:
org.springframework.web.servlet.DispatcherServlet
初始参数:
- contextConfigLocation //指定controller配置文件
web.xml中配置示例:
<!--spring web mvc 的核心servlet-->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--spring mvc配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<!--此配置文件中实例化的对象在子容器中-->
<param-value>classpath:applicationContext.xml</param-value>
</init-param>
<!--tomcat启动就创建servlet-->
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<!--
注:spring的 DispatcherServlet 对url-pattern做过特殊处理。
/ 匹配任意路径,但是.jsp文件不会被拦截
/* 能匹配任意路径,包括 .jsp
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--启动spring web mvc 的注解-->
<mvc:annotation-driven/>
<!--扫描包-->
<context:component-scan base-package="com.zd.controller"/>
<!--配置一个视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
<!--解决静态资源被拦截的问题
mapping : url路径
location: 映射给指定的本地路径
-->
<mvc:resources mapping="/js/**" location="/js/" />
<mvc:resources mapping="/css/**" location="/css/" />
<mvc:resources mapping="/img/**" location="/img/" />
<!--文件上传解析器
特别要注意: spring 将 CommonsMultipartResolver类注入到相应属性中是根据 id匹配的,
所以这个bean的id必须是 multipartResolver
-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--文件的编码方式-->
<property name="defaultEncoding" value="UTF-8" />
<!--单个文件的大小限制(20 m),单位:字节-->
<property name="maxUploadSizePerFile" value="20971520"/>
<!--上传文件的总大小限制(100 m),单位:字节-->
<property name="maxUploadSize" value="104857600" />
</bean>
</beans>
3.3、spring mvc环境初始化监听器
核心类:
主要用于在启动时加载相关配置文件
org.springframework.web.context.ContextLoaderListener
全局参数:
- contextConfigLocation //可以指定例如 service、dao、数据源、事物等配置文件
web.xml中配置示例:
<!--全局参数-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-*.xml</param-value>
</context-param>
<!--spring加载配置文件的监听器
此监听器会读取 全局参数 contextConfigLocation 中的配置文件.可以将其理解为spring的父容器
-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
3.4、字符编码过滤器
核心类:
org.springframework.web.filter.CharacterEncodingFilter
初始参数:
- encoding //指定字符集
web.xml中配置示例:
<!--加入字符集过滤器-->
<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>
3.5、视图解析器
核心类:
org.springframework.web.servlet.view.InternalResourceViewResolver
属性:
- prefix //前缀
- suffix //后缀
示例:
<!--配置一个视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
3.6、注解相关配置
3.6.1、启用Spring MVC注解
如果你使用了注解,那么一定要记得在子容器的xml中加上启用注解的配置
虽然不写表面上看上去没事,但是一定要记得写,不然可能会有莫名其妙的问题
<mvc:annotation-driven />
3.6.2、注解扫描
扫描指定包下的类,然后根据你们自己打在类上的注解,自动装配和初始化
比如:
- 扫描到 @controller注解的类,spring会将其当成controller处理
- 扫描到 @RequestMapping注解,spring会为其建立路径映射
- 扫描到 @Autowired注解,spring会为其注入对象实例
- 扫描到 @ResponseBody注解,spring会为将其转为restful风格的数据展示
- 扫描到 @Service注解,spring会将其当成service处理
<context:component-scan base-package="包名/类名" />
4、spring mvc常用注解
4.1、组件型注解:
以下四种注解都是注解在类上的,被注解的类将被spring初始话为一个bean,然后统一管理。
- @Component 在类定义之前添加@Component注解,他会被spring容器识别,并转为bean。
- @Repository 对Dao实现类进行注解 (可以将其理解为一种特殊的@Component)
- @Service 用于对业务逻辑层进行注解, (可以将其理解为一种特殊的@Component)
- @Controller 用于控制层注解 , (可以将其理解为一种特殊的@Component)
4.2、请求和参数型注解:
-
@RequestMapping:用于处理请求地址映射,可以作用于类和方法上。
-
value:定义request请求的映射地址
-
method:定义地request址请求的方式,包括【GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.】默认接受get请求,如果请求方式和定义的方式不一样则请求无法成功。
-
params:定义request请求中必须包含的参数值。
-
-
@RequestParam:用于获取传入参数的值
- value:参数的名称
- required:定义该传入参数是否必须,默认为true,(和@RequestMapping的params属性有点类似)
-
@PathViriable:用于定义路径参数值
- value:参数的名称
- required:定义传入参数是否为必须值
-
@ModelAttribute:用于把参数保存到model中,可以注解方法或参数,注解在方法上的时候,该方法将在处理器方法执行之前执行,然后把返回的对象存放在 session(前提时要有@SessionAttributes注解) 或模型属性中,@ModelAttribute(“attributeName”) 在标记方法的时候指定,若未指定,则使用返回类型的类名称(首字母小写)作为属性名称。
-
@SessionAttributes
- 默认情况下Spring MVC将模型中的数据存储到request域中。当一个请求结束后,数据就失效了。如果要跨页面使用。那么需要使用到session。而@SessionAttributes注解就可以使得模型中的数据存储一份到session域中。配合@ModelAttribute("user")使用的时候,会将对应的名称的model值存到session中
4.3、spring mvc 转发和重定向
4.3.1.通过ModuleAndView实现转发和重定向
设置ModelAndView对象 , 根据view的名称 , 和视图解析器跳到指定的页面 .
页面 : {视图解析器前缀} + viewName +{视图解析器后缀}
示例:某controller方法返回如下
//转发
ModelAndView mv = new ModelAndView("/index");
return mv;
//重定向
ModelAndView mv = new ModelAndView("redirect:/index");
return mv;
4.3.2.通过ServletAPI实现转发和重定向
4.3.3.通过Springmvc实现转发和重定向
示例:某controller方法返回如下
//转发
return "forward:/WEB-INF/jsp/test.jsp";
//重定向
return "redirect:/index.jsp";
5、JSR 303 后台数据校验
5.1、jsp中引入标签库
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
- 注意启用spring mvc注解
<mvc:annotation-driven />
5.2、空检查
- @Null 验证对象是否为null
- @NotNull 验证对象是否不为null, 无法查检长度为0的字符串
- @NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
- @NotEmpty 检查约束元素是否为NULL或者是EMPTY.
5.3、Booelan检查
- @AssertTrue 验证 Boolean 对象是否为 true
- @AssertFalse 验证 Boolean 对象是否为 false
5.4、长度检查
- @Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
- @Length(min=, max=) Validates that the annotated string is between min and max included.
5.5、日期检查
- @Past 验证 Date 和 Calendar 对象是否在当前时间之前
- @Future 验证 Date 和 Calendar 对象是否在当前时间之后
- @Pattern 验证 String 对象是否符合正则表达式的规则
5.6、数值检查
建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为"",Integer为null
- @Min 验证 Number 和 String 对象是否大等于指定的值
- @Max 验证 Number 和 String 对象是否小等于指定的值
- @DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
- @DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
- @Digits 验证 Number 和 String 的构成是否合法
- @Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
- @Range(min=, max=) 验证数字是否为 min ~ max区间之内.
- @Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
- @CreditCardNumber信用卡验证
- @Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
- @ScriptAssert(lang= ,script=, alias=)
- @URL(protocol=,host=, port=,regexp=, flags=)
5.7、JSR303 代码示例
- 我有一个User 实体类如下
/*
用户的实体类
*/
public class User {
//姓名
@NotBlank(message = "用户姓名不能为空") //校验name属性不为空字符串
@Length(min = 0,max = 50,message = "姓名长度不能超过50个字符") //校验字符串长度在0~50之间
private String name;
//年龄
@NotNull(message = "年龄不能为null")
// @Max(value = 200,message = "年龄不能超过200岁")
// @Min(value = 10,message = "年龄不能小于10岁")
@Range(min = 10,max = 100,message = "年龄只能在10~100之间")
private Integer age;
//生日
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
//学习的课程
private List<String> courseList;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public List<String> getCourseList() {
return courseList;
}
public void setCourseList(List<String> courseList) {
this.courseList = courseList;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", birthday=" + birthday +
", courseList=" + courseList +
'}';
}
}
- 测试校验的controller代码如下
/**
* jsr 303 校验测试
*/
@RequestMapping("/validate")
@Controller
public class ValidateController {
//跳转 jsr303页面
@RequestMapping("/toJsr303")
public String toJsr303(Model model){
//可以做一些数据初始化的工作
User u = new User();
// u.setAge(18);
// u.setName("小龟龟");
//
// //
// List<String> list = new ArrayList<String>();
// list.add("01");//默认选中java
// list.add("03");
// u.setCourseList(list);
//将u对象放入request作用域中
model.addAttribute("user",u);
return "jsr303";
}
/**
* 测试方法1
* @return
*/
@RequestMapping("/test1")
public String test1(Model model,
/*
@Valid使用jsr303 校验user对象中的属性
*/
@Valid
User user,Errors erros){
// System.out.println("进入 jsr 。。。name:"+name);
if(erros.hasErrors()){
System.out.println("校验后有错误信息:");
//输出错误信息
// List<ObjectError> errorList = erros.getAllErrors();
// //遍历集合,输出所有的校验错误信息
// errorList.stream().forEach(
// //定义了一个匿名的方法
// (obj) ->{
// System.out.println(
// obj.getCode()
// +":"+obj.getDefaultMessage());
// }
// );
//获取指定属性的校验的错误信息
// FieldError fieldError = erros.getFieldError("name");
// System.out.println("name:"+fieldError.getDefaultMessage());
//
// fieldError = erros.getFieldError("age");
// System.out.println("age:"+fieldError.getDefaultMessage());
// fieldError = erros.getFieldError("birthday");
// System.out.println("birthday:"+fieldError.getDefaultMessage());
// fieldError = erros.getFieldError("age");
// System.out.println(fieldError.getDefaultMessage());
//如果你有错误,我就将错误信息存放到requset作用域中
// model.addAttribute("erros",erros);
//如果有错误,就继续让对方填写表单
return "jsr303";
}
//表单提交成功
return "jsr303Sucess";
}
}
- 页面代码如下
- jsr303.jsp
<%@ page import="com.zd.entity.User" %>
<%@ page
pageEncoding="UTF-8"
contentType="text/html;charset=UTF-8" language="java" %>
<!--引入spring的form标签库-->
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
<title>Title</title>
<style>
.error{
color: red;
}
</style>
</head>
<body>
<div>
看看全局监听器中存储的contextPath:${contextPath}
<!--
modelAttribute: spring会去request里面寻找对应key的值,
如果找不到会报错,如果找到了,将自动填充数据
-->
<form:form method="post"
modelAttribute="user"
action="${contextPath}/validate/test1">
<!--path就想当与input中的name-->
姓名:<form:input path="name" />
<form:errors path="name" cssClass="error"/>
<br/>
年龄:<form:input path="age" />
<form:errors path="age" cssClass="error" />
<br/>
生日:<input name="birthday" type="date" value="${dateStr}"/>
<br/>
课程:
<label>
<!--复选框,相当于 <input type="checkbox"/>-->
<form:checkbox path="courseList" value="01"/>
java
</label>
<label>
<form:checkbox path="courseList" value="02"/>
html
</label>
<label>
<form:checkbox path="courseList" value="03"/>
c#
</label>
<label>
<form:checkbox path="courseList" value="04"/>
数据库
</label>
<br/>
<button type="submit">提交</button>
</form:form>
</div>
</body>
</html>
-
- jsr303Sucess.jsp
<%--
Created by IntelliJ IDEA.
User: zhuoli
Date: 2020/11/15
Time: 15:10
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
提交成功!
</body>
</html>
6、SpringMVC-RESTFUL风格+对应注解
6.1、什么是RESTFUL?
Restful就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。
特点包括:
- 每一个URI代表1种资源;
- 客户端使用GET、POST、PUT、DELETE4个表示操作方式的动词对服务端资源进行操作:
- GET用来获取资源,
- POST用来新建资源(也可以用于更新资源),
- PUT用来更新资源,
- DELETE用来删除资源;
- 通过操作资源的表现形式来操作资源;
- 资源的表现形式是JSON/XML/HTML;
- 客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息。
6.2、示例
6.2.1、添加maven依赖:
<!-- bean转json支持,因为restful可能返回json格式的数据 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
6.2.2、静态资源访问问题
<!--静态资源映射路径
mapping : url路径
location: 映射给指定的本地路径
-->
<mvc:resources mapping="/js/**" location="/js/"/>
6.2.3、注解
- @PostMapping ---等同于---- @RequestMapping + 限制请求方式为 post
- @PutMapping ---等同于---- @RequestMapping + 限制请求方式为 put
- @DeleteMapping ---等同于---- @RequestMapping + 限制请求方式为 delete
- @GetMapping ---等同于---- @RequestMapping + 限制请求方式为 get
- @RestController ---等同于--- @Controller + @ResponseBody 组合
- @RequestBody -----主要用来接收前端传递给后端的json字符串中的数据的(请求体中的数据的)
- @ResponseBody ----controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区,通常用来返回JSON数据或者是XML数据。默认返回的是json格式数据,如果要返回xml格式数据,如要在返回对象的类上使用 Xml的相关注解。
返回json格式,实体类上的常用注解:
//日期转换
@JsonFormat(pattern = "yyyy-MM-dd")
- 示例某实体类对象对转为json格式数据,其Date类型字段注解配置
//restful 转json时 日期格式处理注解
@JsonFormat(pattern = "yyyy-MM-dd")
private Date stuBirthday;
返回xml格式数据,实体类上的常用注解:
//文档更根元素
@XmlRootElement(name = "data")
//指定包装集合的标签名称(该注解强烈建议放在对应属性的getXxx方法上)
@XmlElementWrapper(name = "items")
//指定包装集合中每一项的标签名称(该注解强烈建议放在对应属性的getXxx方法上)
@XmlElement(name = "item")
- 示例某实体类对象转换为xml格式数据,其注解配置
@XmlRootElement(name = "items")
public class XmlDo {
private List<Student> list = null;
@XmlElement(name = "item",type = Student.class)
public List<Student> getList() {
return list;
}
public void setList(List<Student> list) {
this.list = list;
}
}
注意
- put/delete 请求方式后台需要使用 @RequestBody注解来接收数据,
- 前端使用ajax技术,请求方式为put/delete时,需要设置请求头中的 'Content-Type'(内容类型)为 'application/json' ,才能提交json格式的数据给服务器
这里示例一段 jquery发送put请求方式的代码
function testPut(){
//将一个js对象转换成json格式的字符串
let jsonStr = JSON.stringify({
id:10,
stuName:'小丁丁',
stuAge:18,
stuBirthday:'2020-11-22'
});
//System.out.println();
console.info(jsonStr);
$.ajax({
type:'put',//请求方式为 post/put/DELETE/get
url:'${root}/rest/',//请求的路径
//请求头参数
headers:{
//告诉浏览器,我提交给服务器的数据是json格式的数据
'Content-Type':'application/json'
},
data:jsonStr,
/*
告诉jquery,返回的数据格式是json,
让其帮我们将json格式的字符串解析为js对象
*/
dataType:"json",
//请求成功调用的函数
success:function(data){
console.info("服务器返回的数据",data);
},
//请求失败调用的函数
error:function(error){
console.info('error',error)
}
});
}
7、拦截器
7.1、什么是拦截器?
Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理。例如通过拦截器可以进行权限验证、记录请求信息的日志、判断用户是否登录等。
7.2、示例
7.2.1、自定义拦截器需要实现HandlerInterceptor接口
该接口中定义了一下几个方法
- preHandle() 方法
- 该方法会在控制器方法前执行,其返回值表示是否中断后续操作。
- 当其返回值为true时,表示继续向下执行;
- 当其返回值为false时,会中断后续的所有操作(包括调用下一个拦截器和控制器类中的方法执行等)。
- postHandle()方法
- 该方法会在控制器方法调用之后,且解析视图之前执行。
- 可以通过此方法对请求域中的模型和视图做出进一步的修改。
- afterCompletion()方法
- 该方法会在整个请求完成,即视图渲染结束之后执行。
- 可以通过此方法实现一些资源清理、记录日志信息等工作。
示例登录拦截器:
/**
* 登录的拦截器
*/
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
System.out.println("在调用controller的方法之前,preHandle");
//判断用户是否登录
// Object user = request.getSession().getAttribute("user");
// if(user == null){
// String root = request.getContextPath();
// //没有登录,重定向登录页面
// response.sendRedirect(root + "/home.jsp");
// return false;
// }
//返回true,不会拦截. 返回false 拦截请求不往下走
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
System.out.println("调用完controller方法之后,postHandle");
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
System.out.println("在页面渲染完毕之后,afterCompletion");
}
}
示例日志记录拦截器:
/**
* 日志拦截器
*/
public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
//在这里记录一下开始时间
//系统当前时间
long start = System.currentTimeMillis();
request.setAttribute("start",start);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object o, ModelAndView modelAndView) throws Exception {
//获取系统当前时间
long end = System.currentTimeMillis();
long start = (long)request.getAttribute("start");
System.out.println( request.getRequestURI() + "请求耗时"+(end - start)+"ms");
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
7.2.2、xml文件相关配置
在spring mvc子容器中配置好拦截器
<!--拦截器都配置在这对标签之中-->
<mvc:interceptors>
<!--配置一个拦截器-->
<mvc:interceptor>
<!--配置拦截器的作用路径-->
<mvc:mapping path="/**"/>
<!--排除的路径-->
<mvc:exclude-mapping path=""/>
<!--定义在<mvc:interceptor>下面的表示匹配指定路径的请求才进行拦截-->
<bean class="实现了HandlerInterceptor接口的实现类"/>
</mvc:interceptor>
</mvc:interceptors>
8、spring security
8.1、spring security 是什么?
是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。(安全访问控制框架)
spring security的简单原理:
使用众多的过滤器/拦截器 对url拦截,实现安全访问控制。
如:登录验证的拦截器、权限验证拦截器、记住密码拦截器、csrf安全保护拦截器、css安全保护拦截器...
注:csrf --跨站请求伪造,css --跨站脚本攻击
8.2、依赖jar
<!-- spring security 支持 start-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>4.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.2.3.RELEASE</version>
</dependency>
<!-- spring security 支持 end-->
8.3、配置
8.3.1、web.xml配置 spring security过滤器链
前面讲过,spring security 是基于一系列 过滤器/拦截器 串成一个链条来实现的,所以我们要使用它的话,先要配置这根链条
核心filter-class
org.springframework.web.filter.DelegatingFilterProxy
- filter-name为:
springSecurityFilterChain
,注意:该过滤器名称只能是这个
说明:该过滤器需要拦截所有的url,即对所有的url都要实现安全访问控制。DelegatingFilterProxy是一个委托的过滤器类,这个委托的类会根据名字(springSecurityFilterChain)去初始化对应的过滤器链条,所以springSecurityFilterChain 这个名字必须是固定的。
配置示例:
<!--spring security 过滤器链
springSecurityFilterChain是固定的,
DelegatingFilterProxy 这个委托类会根据名字去初始化对应的过滤器链条,即判断 名字如果是
springSecurityFilterChain 那么就会初始化spring security过滤器链
-->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<!--对所有的url都实现安全访问控制-->
<url-pattern>/*</url-pattern>
</filter-mapping>
8.3.2、建立spring-security.xml配置文件
注意该配置文件要加入如下schema约束
xmlns:sec="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd"
8.3.2.1、核心<http>
标签说明及
-
security会对访问我们系统的人进行一个身份认证,既然要认证,那么认证的入口在哪?
-
在
<http>
标签内配置一个基于<form-login>
表单认证入口,并指定如下属性:- login-page --- 登录的页面
- login-processing-url --- 登录处理的url,即:登录页面表单提交的 地址,security就是拦截这个地址进行登录认证
- authentication-failure-url --- 登录认证失败跳转的地址
- default-target-url ---登录认证成功默认跳转的地址
<!--security的 核心标签 use-expressions:如果为true,下面的access属性就能使用spel表达式 spel表达式:SpEL(Spring Expression Language),即Spring表达式语言,是比JSP的EL更强大的一种表达式语言,同学们想知道其具体用法自行百度即可 --> <sec:http use-expressions="true"> <!--...省略...--> <!--身份认证的入口 <form-login> 基于表单登录的身份认证的标签 login-page:登录的表单页面 login-processing-url:登录处理的url,一般是和登录表单页中的action一致 username-parameter:表单用户名的参数key password-parameter:表单密码的参数key authentication-failure-url:认证失败跳转的页面 default-target-url:认证成功之后,默认跳转的页面 --> <sec:form-login login-page="/jsp/login.jsp" login-processing-url="/user/login" username-parameter="userName" password-parameter="password" authentication-failure-url="/jsp/login.jsp?error" default-target-url="/jsp/home.jsp" /> <!--...省略...--> </sec:http>
-
-
认证入口有了,那么认证的方式是什么呢?
- 所以这里我们要在配置一个认证管理器,用于管理我们的认证方式,标签:
<authentication-manager>
- 所以这里我们要在配置一个认证管理器,用于管理我们的认证方式,标签:
-
认证管理器中可以管理很多种认证方式(spring提供了 cas、ldap、jaas、dao、remote等认证方式),这里我们使用dao认证方式,标签
<authentication-provider>
<!--认证管理器
管理身份认证的各种方式(cas、dao、jaas、....)
-->
<sec:authentication-manager id="authenticationManager">
<!--
今天只做一种,配置dao认证方式,
user-service-ref:bean id,实现获取用户信息对象
-->
<sec:authentication-provider user-service-ref="userDetailService">
<!--dao认证方式,指定密码加密校验的方式-->
<sec:password-encoder ref="pwdEncoder"/>
</sec:authentication-provider>
</sec:authentication-manager>
- 实现dao认证方式,写一个类 实现 UserDetailsService 接口
/**
* dao认证方式 ,获取用户信息的实现类
*/
public class UserDetailServiceImpl
implements UserDetailsService {
/**
* security在用户进行登录身份认证的时候,会调用这个方法。
* @param s 用户名
* @return
* @throws UsernameNotFoundException
*/
//选中类/接口 ,快捷键 ctrl + t 可以查看其子类结构
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//我们可以在这里面去查询数据库,根据用户名 把这个用户的信息加载出来
//TODO 我这里就模拟一下查询数据库
System.out.println("security传递过来的用户名:"+s);
String userName = s;
//是从数据库里面查得密码 (下面这串密文是:123456)
String password = "$2a$10$hJsaxmYOv3xs/ts1pFqs4eavmTeF9G9K7zexd99ueywjTmUlo5VDi";
User u = null;
//角色集合,你们可以从数据库查询出来,我这里就模拟一下
Collection<SimpleGrantedAuthority> coll =
new ArrayList<SimpleGrantedAuthority>();
//后台资源角色
//SimpleGrantedAuthority是GrantedAuthority接口的实现类
SimpleGrantedAuthority auth1 = new SimpleGrantedAuthority("ROLE_BACK");
//前台资源角色
SimpleGrantedAuthority auth2 = new SimpleGrantedAuthority("ROLE_FRONT");
if(s.equals("admin")){
//赋予后台资源访问的角色
coll.add(auth1);
//赋予前台资源访问的角色
coll.add(auth2);
//admin账号
u = new User(userName, password, coll);
//能找到用户,直接返回
return u;
}else if("front".equals(s)){
//赋予前台资源访问的角色
coll.add(auth2);
//front账号
u = new User(userName, password, coll);
//能找到用户,直接返回
return u;
}
//如果程序执行到这里,表示找不到用户,那么就认证失败,
//认证失败的话,抛出一个异常即可
//throw new UsernameNotFoundException("用户名或密码不对");
//如果你不想让你的异常被security给抓住,你可以抛下面这个异常
throw new RuntimeException("用户名或密码错误");
// return null;
}
public static void main(String[] args) {
BCryptPasswordEncoder pwdEncoder = new BCryptPasswordEncoder();
//你们在注册用户的时候,肯定是将人家输入的密码加密后入库的
//加密
String pwd= pwdEncoder.encode("123456");
System.out.println(pwd);
}
}
-
<password-encoder>
配置密码加密,加密 核心类:org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
此类的加密算法是bcrypt,bcrypt 使用的是布鲁斯·施内尔在1993年发布的 Blowfish 加密算法
<!--dao 认证方式中,获取用户信息对象的bean-->
<bean id="userDetailService" class="com.zd.security.UserDetailServiceImpl" />
<!--dao 认证方式中,密码加密校验方式的bean-->
<bean id="pwdEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
到这里,security 大的框框就配置完了。这里说明下spring security怎么样将我们实现的这个dao认证方式加入到spring security中呢?
因为spring security是使用众多的过滤器形成过滤器链条来拦截url实现的安全访问控制
过滤器链中有一个 UsernamePasswordAuthenticationFilter 一个基于用户名、密码认证的过滤器,该过滤完美契合dao认证方式,如果你需要更多自定义的额外的功能,你可以继承这个类,自定义一个认证过滤器来实现你想要的功能。
注意:如果你要自定义过滤器,那么在链条之中的位置必须在表单提交之前,即设置before="FORM_LOGIN_FILTER"
如果你是继承UsernamePasswordAuthenticationFilter实现的自定义过滤器,那么有如下几个属性你必须了解:
- filterProcessesUrl属性:该身份认证过滤处理的url,一般指定登录表单提交的url即可
- authenticationManager属性:指定该身份认证的过滤使用的 认证管理器
- authenticationFailureHandler属性:认证失败处理器
- authenticationSuccessHandler属性:认证成功处理器
- usernameParameter属性:security在表单认证请求中获取用户名的参数 key(即登录表单中用户名的input框的name值)
- passwordParameter属性:security在表单认证请求中获取密码的参数 key(即登录表单中密码的input框的name值)
8.3.2.2、细节配置
我们完成第8.3.2.1点配置后我们访问任意的url,是不是感觉还是和以前一样,没任何区别?
那是因为我们还没有个url赋予访问权限。来吧
- 在
<http>
标签内 加入<intercept-url/>
标签,此标签表示拦截url,其拥有属性如下- patern属性:表示匹配的url
- access属性:表示匹配的url访问的权限。这里介绍几个常用的,下面都是spel表达式,注意将
<http>
标签中的属性 use-expressions="true",不然不能使用:- access属性值:isAuthenticated() --- 表示访问这些url必须进过身份认证之后
- access属性值:isAnonymous() --- 表示访问这些url可以以一个匿名的身份访问,即不需要身份认证(如:不登录)也能访问
<intercept-url>
标签可以配置任意个,拦截访问控制按照配置从上至下,其中一旦有一个匹配成功即对其进行相应的访问权限校验,校验后返回,不在进行后续权限控制匹配<intercept-url>
可以配合access-denied-handler
标签来使用
<intercept-url>
配置示例
<!--security的 核心标签
use-expressions:如果为true,下面的access属性就能使用spel表达式
spel表达式:SpEL(Spring Expression Language),即Spring表达式语言,是比JSP的EL更强大的一种表达式语言,同学们想知道其具体用法自行百度即可
-->
<sec:http use-expressions="true">
<!--后台页面访问用户必须要有 ROLE_BACK 角色 -->
<sec:intercept-url pattern="/jsp/back/**"
access="hasAnyRole('ROLE_BACK')" />
<!--前台页面访问用户必须要有 ROLE_FRONT 角色-->
<sec:intercept-url pattern="/jsp/front/**"
access="hasRole('ROLE_FRONT')" />
<!--要求所有url都要进过 身份认证(登录)
pattern:url规则,可以使用ant表达式
access:访问的角色/权限
-->
<sec:intercept-url pattern="/**"
access="isAuthenticated()" />
<!--
没有权限或出现了其他安全方面错误导致访问失败处理的页面配置
error-page:跳转的错误页面
-->
<sec:access-denied-handler
error-page="/jsp/error.jsp"/>
<!--。。。省略。。。-->
</sec:http>
- 做完上面的配置后,我们发现一个问题
- 那就是
pattern="/**"
这个规则拦截了我们所有的url,即客户访问我们系统任意url都需要经过认证才能访问 - 但是实际情况是我们有一些url是不需要经过认证也能让客户访问的。如:登录页面、登录失败页面、错误页面...等
- 那就是
- 想要解决上面的问题,我们只需指定某些url不做security安全访问控制即可
示例:
<!--
security="none"表示不对指定的url规则做security安全访问控制-->
<!--登录页面-->
<sec:http pattern="/jsp/login.jsp*" security="none"/>
<!--登录失败页面-->
<sec:http pattern="/jsp/failure.jsp" security="none"/>
<!--错误页面-->
<sec:http pattern="/jsp/error.jsp" security="none"/>
备注:
老版本的security 对于设置为security="none"的url无法共享security管理的session,即这种url对应的controller或jsp你取不到security管理的session中保存的值。新版本没有这个问题。
- 到这里,我们用着用着发现我们提交表单数据时,它老是给我们提示出现 CSRF 攻击,这是因为spring security默认开启CSRF保护,我们却没有按照它的机制去实现CSRF保护,但是要实现此功能是件非常麻烦的事情,如何实现同学们可自行百度,这里我们就先直接禁用掉它了
- 禁用防 CSRF 攻击功能
<!--security的 核心标签
use-expressions:如果为true,下面的access属性就能使用spel表达式
spel表达式:SpEL(Spring Expression Language),即Spring表达式语言,是比JSP的EL更强大的一种表达式语言,同学们想知道其具体用法自行百度即可
-->
<sec:http use-expressions="true">
<!--...省略...-->
<!--禁用csrf 安全保护-->
<sec:csrf disabled="true" />
<!--...省略...-->
</sec:http>
-
前面我们登录认证等都配置好了,那么我们如何样做登出呢?毕竟现在登录认证又不是我们实现的
- 别急,原来spring security给我们提供了
<logout>
标签,让我们做登出配置
- 别急,原来spring security给我们提供了
-
<logout>
有以下属性:- invalidate-session --- true表示登出后令之前的session失效
- logout-url --- 访问指定的url即登出该用户
- logout-success-url --- 登出成功后跳转的url
<!--security的 核心标签 use-expressions:如果为true,下面的access属性就能使用spel表达式 spel表达式:SpEL(Spring Expression Language),即Spring表达式语言,是比JSP的EL更强大的一种表达式语言,同学们想知道其具体用法自行百度即可 --> <sec:http use-expressions="true"> <!--...省略...--> <!--登出的配置 invalidate-session:登出后让其session失效 logout-url:调用登出功能的url --> <sec:logout invalidate-session="true" logout-url="/user/logout"/> <!--...省略...--> </sec:http>
-
假如 如果你想让你的网站像QQ一样,只能在一台电脑上登录(类似单点登录),那又该怎么做呢?
- 我们可以思考一下,只能让我在一台电脑上登录,是不是就是我这个账户(用户名、密码)只能绑定服务器中的一个session对象
- spring security提供了
<session-management>
标签,让我们来配置管理session - 配置示例如下:
<!--security的 核心标签 use-expressions:如果为true,下面的access属性就能使用spel表达式 spel表达式:SpEL(Spring Expression Language),即Spring表达式语言,是比JSP的EL更强大的一种表达式语言,同学们想知道其具体用法自行百度即可 --> <sec:http use-expressions="true"> <!--...省略...--> <!--session管理--> <sec:session-management> <!-- max-sessions: 限制一个账号允许的session最大数量,设置为1就是只能在一台电脑上登录 error-if-maximum-exceeded: 为true表示超过session最大数量限制就报错,报错就会跳会登录页 为false,那么当超过session数量最大值,就会将之前的session挤下去(即令之前的一个session失效) expired-url:当session失效的时候跳转的页面 --> <sec:concurrency-control max-sessions="1" error-if-maximum-exceeded="false" expired-url="/jsp/sessionExpired.jsp"/> </sec:session-management> <!--...省略...--> </sec:http
-
很多网站都有记住我这个功能,利用security如何实现此功能呢?
- 在登录页面加入一个 checkbox框,该checkbox的name值需要为 remenber-me
- 在
<http>
标签内加入<remember-me>
标签,此标签具有如下属性- remember-me-parameter --- 绑定的checkbox参数key,默认为"remenber-me",如果你要自定义自己的参数key可以在这里指定。
- token-validity-seconds --- 默认remeber me能记住14天,如果你要自定义记住的时间可以通过这个属性指定,该属性必须是一个数字,单位:秒
<remember-me>
配置示例
<!--security的 核心标签
use-expressions:如果为true,下面的access属性就能使用spel表达式
spel表达式:SpEL(Spring Expression Language),即Spring表达式语言,是比JSP的EL更强大的一种表达式语言,同学们想知道其具体用法自行百度即可
-->
<sec:http use-expressions="true">
<!--...省略...-->
<!--记住我
remember-me-parameter:
参数key,和前台登录页中的 记住我 复选框的name保持一致
token-validity-seconds:
默认remeber me能记住14天,如果你要自定义记住的时间可以通过这个属性指定,该属性必须是一个数字,单位:秒
-->
<sec:remember-me
token-validity-seconds="604800"
remember-me-parameter="remenberMe"/>
</sec:http>
注意:登录页面 记住我 的复选框 name 属性值必须和 remember-me-parameter 值一致
8.3.2.3、spring security中获取用户相关信息的代码
- 认证失败的异常信息保存在request作用域key为: SPRING_SECURITY_LAST_EXCEPTION 中
- 获取用户信息
SecurityContextHolder.getContext().getAuthentication()
- 获取用户名
request.getRemoteUser(); // Servlet标准,推荐使用
SecurityContextHolder.getContext().getAuthentication().getName(); //spring security提供
- 获取用户权限信息
SecurityContextHolder.getContext().getAuthentication().getAuthorities();
- 判断用户是否拥有指定权限
request.isUserInRole("ADMIN");
8.4、spring security动态拦截和权限实现
注:这是额外课题,要同学们理解可能具备一定难度,若时间充足可讲。
- 首先,我们要在security链条中嵌入我们自定义实现的过滤器
配置如下
<!--
use-expressions 是否使用SPEL表达式的配置,
如果声明为true,那么在access属性要用hasRole('ROLE_XXX')这样写
如果声明为false(默认),那么access直接就是 'ROLE_XXX'
-->
<sec:http use-expressions="true" >
<!--。。。省略。。。-->
<!--自定义动态url访问权限控制得 过滤器,过滤器器的位置是 url权限拦截匹配之前,身份认证之后-->
<sec:custom-filter ref="urlInterceptFilter" before="FILTER_SECURITY_INTERCEPTOR" />
</sec:http>
<!--自定义url拦截 的过滤器,该过滤具备以下几个功能:
1、角色/权限 能访问的 url资源 管理
2、角色/权限 的访问策略
-->
<bean id="urlInterceptFilter" class="com.zd.security.UrlInterceptFilter" >
<!--注入自定义的 url规则 与 权限映射-->
<property name="securityMetadataSource" ref="customSecurityMetadataSource" />
<!--访问策略-->
<property name="accessDecisionManager" ref="customAccessDecisionManager" />
<!--由于这个过滤器内部 使用了身份认证的功能,所以这里要讲认证管理器也注入给它-->
<property name="authenticationManager" ref="authenticationManager" />
</bean>
<!--自定义的 url规则 与 权限映射-->
<bean id="customSecurityMetadataSource" class="com.zd.security.CustomFilterInvocationSecurityMetadataSource" />
<!--决策访问器-->
<bean id="customAccessDecisionManager" class="com.zd.security.CustomAccessDecisionManager" />
<!--认证管理器-->
<sec:authentication-manager id="authenticationManager" >
<!--认证供应商(认证方式) -->
<sec:authentication-provider user-service-ref="userDetailService">
</sec:authentication-provider>
</sec:authentication-manager>
<!--dao认证方式实现-->
<bean id="userDetailService" class="com.zd.security.UserDetailServiceImpl"/>
- UrlInterceptFilter代码如下
注意:上面的配置我是将此过滤器嵌入在security链条中,其位置在 登录认证之后,权限拦截之前
/**
* 动态 url拦截映射访问权限 过滤器
*/
public class UrlInterceptFilter extends AbstractSecurityInterceptor implements Filter {
//xml配置文件注入
private SecurityMetadataSource securityMetadataSource;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//security的过滤器链调用 对象
FilterInvocation fi = new FilterInvocation(request, response,chain);
//调用链条前的一个处理,这里处理有2个动作
//1、调用 SecurityMetadataSource 对象中的getAttributes方法获取可以访问url该 url 的所有 角色/权限
//2、调用 AccessDecisionManager(访问策略) 对象中的decide方法来 验证访问的权限是否充足
//得到是拦截的一个状态 标记
InterceptorStatusToken token = super.beforeInvocation(fi);
try{
//执行security过滤器链中的下一个过滤器
fi.getChain().doFilter(request,response);
}finally {
//执行完链条之后要对 拦截状态 这个标记进行一个处理
super.afterInvocation(token,null);
}
}
/**
* 调用 security 过滤器链条需要使用 FilterInvocation类,这里直接返回 FilterInvocation.class类型即可
* @return
*/
@Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
/**
* 得到security url资源 与 角色/权限 的关系 对象
* @return
*/
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
public void setSecurityMetadataSource(SecurityMetadataSource securityMetadataSource) {
this.securityMetadataSource = securityMetadataSource;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
- CustomFilterInvocationSecurityMetadataSource 代码如下
该类表明url资源与角色/权限的关系
/**
* 自定义的 security url资源 与 角色/权限 关系类
*/
public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
//url 与 角色映射的集合
private Map<String,Collection<ConfigAttribute>> resouceMap;
//spring利用反射实例化类对象,反射实例化调用的就是这个默认的构造方法
public CustomFilterInvocationSecurityMetadataSource(){
//在这里我们可以去数据库中将 每个url 所对应的 角色/权限 都加载进来
//这里我模拟一下,去查询了数据吧
//key:url value:能访问url的角色 集合
Map<String,Collection<ConfigAttribute>> map = new HashMap<String,Collection<ConfigAttribute>>();
//能够访问 /jsp/backstage/** 后台的角色
Set<ConfigAttribute> backstageSet = new HashSet<ConfigAttribute>();
backstageSet.add(new SecurityConfig("ROLE_ADMIN"));
backstageSet.add(new SecurityConfig("ROLE_BACKSTAGE"));
//缓存起来
map.put("/jsp/backstage/**", backstageSet);
//能够访问 /jsp/front/** 前台的角色
Set<ConfigAttribute> frontSet = new HashSet<ConfigAttribute>();
frontSet.add(new SecurityConfig("ROLE_FRONT"));
//缓存起来
map.put("/jsp/front/**",frontSet);
//设置到对象属性中,因为 getAttributes 要用这个map
this.resouceMap = map;
}
//获取哪些 角色/权限 可以访问当前url
@Override
public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
FilterInvocation fi = (FilterInvocation)o;
//获取当前 url
String currentUrl = fi.getRequestUrl();
//获取能访问 该url 的权限集合
Set<ConfigAttribute> set = new HashSet<ConfigAttribute>();
//遍历resouceMap 的key, 这个key 也是url
Iterator<String> it = resouceMap.keySet().iterator();
while(it.hasNext()){
//这个key也是url规则,而且可以是ant表达式
String pattern = it.next();
//这里我们要使用ant工具类来校验 当前url是否能匹配上key
AntPathMatcher matcher = new AntPathMatcher();
//匹配 currentUrl 是否符合 pattern规则,如果符合 则返回true,反正返回false
boolean b = matcher.match(pattern,currentUrl);
if(b){
//取得可以访问这个url规则的 角色集合
Collection<ConfigAttribute> coll = this.resouceMap.get(pattern);
//添加进入
set.addAll(coll);
}
}
//进过上面的遍历,能访问该url的角色都得到了,在这里返回 交给security即可
return set;
}
//这个不用管
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
//本类是否支持security过滤器链
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
- CustomAccessDecisionManager 代码实现
该类表明访问的策略
/**
* 自定义的 决策访问器
*/
public class CustomAccessDecisionManager implements AccessDecisionManager {
/**
* 访问策略,你可以在这个方法中实现你想要的一种访问策略
* @param authentication 当前用户的认证信息
* @param o 当前url
* @param collection 访问当前url 所需的角色集合,该集合数据来源于
* CustomFilterInvocationSecurityMetadataSource.getAttributes 方法
* @throws AccessDeniedException
* @throws InsufficientAuthenticationException
*/
@Override
public void decide(Authentication authentication, Object o,
Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
//这里我们就做一个简单访问策略算了
//判断当前用户 只要有 collection 中的一种角色,就让它能访问url资源
if(collection == null || collection.isEmpty()){
//如果集合为空,那么访问这个url不需要任何权限
return;
}
//遍历访问当前url 需要的权限集合
Iterator<ConfigAttribute> it = collection.iterator();
while(it.hasNext()){
//角色配置对象
ConfigAttribute ca = it.next();
//访问url需要的角色名称
String needRoleName = ca.getAttribute();
//获取当前用户 所拥有的 角色列表
Collection<? extends GrantedAuthority> list = authentication.getAuthorities();
for(GrantedAuthority ga : list){
//用户的这个角色名称
String userRoleName = ga.getAuthority();
if(needRoleName.equals(userRoleName)){
//如果需要的这个 角色名称 与用户所拥有的这个角色名称一致,那么表示当前用户有权限访问这个url
//直接返回即可
return;
}
}
}
//进过上面的代码,程序若还运行到此处,那么表示 这个用于没有权限访问这个url
//没有权限 直接抛出一个AccessDeniedException异常,
// 让security跳转<access-denied-handler>标签中的错误页面
throw new AccessDeniedException("没权限,你不配");
}
//该类是否支持security 角色/权限 访问策略
@Override
public boolean supports(ConfigAttribute configAttribute) {
return true;
}
//该类是否支持securit过滤器链
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
- UserDetailServiceImpl 样例代码
/**
* dao认证方式实现
*/
public class UserDetailServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String loginName) throws UsernameNotFoundException {
// if(1 == 1){
// throw new RuntimeException("用户名错误");
// }
//这里你可以根据登陆名 去查询数据库,把用户的信息都加载出来
//TODO 模拟查询数据库
//登录名
String lname = loginName;
//姓名
String uname = "张三";
//密码
String password = "123456";
//这个用户所拥有的角色,可以从数据库里面查询出来,注意角色必须以ROLE_开头
//可以访问前端页面的角色
GrantedAuthority auth1 = null;
if(loginName.equals("admin")){
auth1= new SimpleGrantedAuthority("ROLE_ADMIN");
}else if(loginName.equals("backstage")){
auth1= new SimpleGrantedAuthority("ROLE_BACKSTAGE");
}else if(loginName.equals("front")){
auth1= new SimpleGrantedAuthority("ROLE_FRONT");
}
//一个用户的角色是可以有多个的
List<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
if(null!=auth1) {
auths.add(auth1);
}
/*
根据 登录名、密码、角色来创建这个用户并返回给spring security,
security会调用密码校验器对密码进行校验
*/
User user = new User(lname, password, auths);
return user;
}
}
- 图解 自定义过滤器类、url资源与角色/权限关系类、访问决策器类、dao认证实现类 他们的关系
ps:有时间我在画图吧