5.SSM框架整合
ssm:spring+spring mvc+mybatis
5.1 整合的场所:web.xml
通过监听器配置mybatis,通过servlet配置mvc。
web.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<display-name>Archetype Created Web Application</display-name>
<!--这是对mybatis的整合配置-->
<!--上下文配置路径-->
<context-param>
<param-name>contextConfigLocation</param-name>
<!--mybatis的配置文件-->
<param-value>classpath:spring-mybatis.xml</param-value>
</context-param>
<!--配置上下文加载监听器,用来监听mybatis的配置文件-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--这是对spring mvc的整合配置-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<!--mvc的配置文件-->
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--设置过滤器进行字符编码-->
<filter>
<filter-name>encodingFilter</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>
<init-param>
<!--强制编码-->
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
5.2 mvc和mybatis的配置文件:
5.2.1 spring-mvc.xml:( resources文件夹下)
<?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:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--加载配置文件,加载进来就可以通过@Value注解来获取属性的值-->
<context:property-placeholder location="classpath:config.properties" />
<!--视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/" />
<property name="suffix" value=".jsp" />
</bean>
<!--字符串消息转换器-->
<bean id="stringHttpMessageConverter" class="org.springframework.http.converter.StringHttpMessageConverter" />
<!--JSON消息转换器-->
<bean id="jsonHttpMessageConverter" class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<!--需要配置编码-->
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
<!--开启MVC注解驱动-->
<mvc:annotation-driven>
<!--消息转换器-->
<mvc:message-converters>
<ref bean="stringHttpMessageConverter" />
<ref bean="jsonHttpMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
<!--扫描控制器所在的包-->
<context:component-scan base-package="com.dream.ssm.controller" />
</beans>
5.2.2 spring-mybatis.xml:( resources文件夹下)
<?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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--加载properties属性文件-->
<context:property-placeholder location="classpath:jdbc.properties" />
<!--Druid数据源配置 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}" />
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!--这个类是由mybatis-spring提供,主要用来整合-->
<bean id="sessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--配置工厂需要数据源,数据源没有?-->
<property name="dataSource" ref="dataSource" />
<!--Mapper接口映射文件配置-->
<property name="mapperLocations" value="classpath:mapper/*Mapper.xml" />
<!--类型需要取别名的包-->
<property name="typeAliasesPackage" value="com.dream.ssm.model" />
<!--插件配置-->
<property name="plugins">
<array>
<!--分页插件-->
<bean class="com.github.pagehelper.PageInterceptor" />
</array>
</property>
<!--了解这个配置即可-->
<property name="configuration">
<bean class="org.apache.ibatis.session.Configuration">
<property name="logImpl" value="org.apache.ibatis.logging.stdout.StdOutImpl" />
</bean>
</property>
</bean>
<!--Mapper接口扫描器-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--接口所在的包-->
<property name="basePackage" value="com.dream.ssm.mapper" />
<!--这里配置的是SqlSessionFactoryBean的名字-->
<property name="sqlSessionFactoryBeanName" value="sessionFactoryBean" />
</bean>
<!--数据源事务管理器配置-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--管理的数据源-->
<property name="dataSource" ref="dataSource" />
</bean>
<!--开启事务注解驱动-->
<tx:annotation-driven />
<!--扫描service层-->
<context:component-scan base-package="com.dream.ssm.service" />
</beans>
5.3 拦截器配置
5.3.1 HandlerInterceptor接口:
import org.springframework.web.servlet.HandlerInterceptor;
public interface HandlerInterceptor {
//前置拦截,在控制器执行之前做事
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
//后置拦截,在控制器执行后做事
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
//在视图被渲染后做事
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}
5.3.2 拦截器应用
最常见的拦截器使用都是应用前置拦截,经常用作角色登录超时和权限验证。
5.3.2.1 登录超时
拦截器位于控制器之前,可以做对登录状态信息的验证功能,即在使用控制器方法之前,需要判断角色是否在登录状态。
这里将角色的信息存放在Session中,在拦截器中得到Session,进行角色信息判断。
public class TimeoutInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
String username = (String) session.getAttribute("username");
if(username == null){
//自己设置响应状态码,如400,401,402.....
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); //未授权
PrintWriter pw = response.getWriter();
pw.print("登录超时");
pw.flush();
pw.close();
return false;
}
return true;
}
}
需要单独在spring-mvc.xml配置文件中进行拦截器配置:
<!--拦截器配置-->
<mvc:interceptors>
<!--这里的配置顺序就是拦截器执行的先后顺序-->
<mvc:interceptor>
<!--要拦截的请求-->
<mvc:mapping path="/**"/>
<!--要放行的,不被拦截的请求-->
<mvc:exclude-mapping path="/"/>
<mvc:exclude-mapping path="/user/login"/>
<bean class="com.dream.ssm.interceptor.TimeoutInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
5.3.2.2 权限检测
控制器中的方法,不是任何角色都能够进行访问使用,所以需要进行角色的权限验证。这里提供有:
5.3.2.2.1 通过请求的url地址划分权限:
即在数据库中使用表的形式,存贮角色权限,权限对应url请求地址,改变角色权限时只需要改变数据库表即可。
1.最简单的权限验证方法是通过拦截器获取角色信息,再通过角色信息在使用业务层方法判断该角色是否有对应权限数据库中查询,
示例:
public class PermissionInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//拿到当前登录的用户名
String username = (String) request.getSession().getAttribute("username");
//获取当前访问的uri
String uri = request.getRequestURI();
//获取上下文路径,也可能是虚拟路径
String contextPath = request.getContextPath();
//访问的uri中需要将上下文路径去掉
uri = uri.replace(contextPath, "");
//去数据库查询当前登录用户是否具有访问该URL地址的权限
if(!userService.hasPermission(username, uri)){
//自己设置响应状态码,如400,401,402.....
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); //未授权
PrintWriter pw = response.getWriter();
pw.print("权限不足,请联系管理员!");
pw.flush();
pw.close();
return false;
}
return true;
}
}
2.也可以在实体类中增加包含权限的属性信息,全部存储在Session中,然后进行权限验证,就避免了经常访问数据库。
比较简单,不做示例演示。
弊端:上述的这两种方式,能够在数据库中对每个用户进行精确的访问控制,但由于所有控制器中的url可能会很多,且每个角色都需要对应能够访问的所有url,数据库表会显得十分臃肿。
5.3.2.2.2 通过注解划分权限等级:
为了解决以上的弊端,对所有角色进行划分角色等级,如admin(管理员权限),user(普通用户权限),test(测试员权限)…等。
在控制器方法上使用注解标识所需要的权限,通过handler参数获取当前执行的方法,再获取方法上的注解参数,来获得对应方法执行所需要的权限。而用户角色的权限,依旧从数据库中得来,原理可以参照5.3.2.2.1的两种方式。
弊端:这种方式无法精确的对每一个角色进行访问控制,涉及到新增的权限以及更加精细的划分权限时,可能会需要更改代码。
定义的权限注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface HashAuthority {
//这个属性就是用来设置访问的角色
String[] value() default {};
}
通过注解对控制器中的方法设置访问权限
@HashAuthority({"ADMIN","USER"})
@GetMapping("/search")
public Result search(UserCondition userCondition){
return null;
}
@HashAuthority("TEST")
@GetMapping("/test")
public Result test(){
return null;
}
拦截器代码:
public class PermissionInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
//如果handler是一个方法的handler
if(handler instanceof HandlerMethod){
//获取拦截的方法
Method method = ((HandlerMethod) handler).getMethod();
HashAuthority authority = method.getAnnotation(HashAuthority.class);
if(authority != null){
//获取注解中设置的角色
String[] roles = authority.value();
HttpSession session = request.getSession();
User user = (User) session.getAttribute("user");
List<String> userRoles = user.getRoles();
boolean valid = Arrays.stream(roles).anyMatch(userRoles::contains);
if(!valid){
response.setContentType("text/html;charset=UTF-8");
response.setStatus(HttpStatus.UNAUTHORIZED.value());
PrintWriter writer = response.getWriter();
writer.print("权限不足,请联系管理员");
writer.flush();
writer.close();
}
return valid;
}
}
return true;
}
}
需要在spring-mvc.xml配置文件中进行拦截器配置(注:配置顺序决定拦截的顺序):
<!--拦截器配置-->
<mvc:interceptors>
<!--这里的配置顺序就是拦截器执行的先后顺序-->
<mvc:interceptor>
<!--要拦截的请求-->
<mvc:mapping path="/**"/>
<!--要放行的,不被拦截的请求-->
<mvc:exclude-mapping path="/"/>
<mvc:exclude-mapping path="/user/login"/>
<bean class="com.dream.ssm.interceptor.TimeoutInterceptor" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/" />
<mvc:exclude-mapping path="/user/login" />
<mvc:exclude-mapping path="/user/init" />
<bean class="com.dream.ssm.interceptor.PermissionInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
5.3.2.3 拦截器执行流程
拦截器与过滤器的区别:拦截器是在servlet中,控制器方法之前生效,而过滤器是在servlet之前生效。