目录
7. 日志系统
7.1 重要意义
系统在运行过程中出了问题就需要通过日志来进行排查,所以我们在上手任何新技术的时候,都要习惯性的关注一下它是如何打印日志的。
7.2 技术选型
7.2.1 总体介绍
7.2.2 不同日志系统的整合
7.3 具体操作
7.3.1 初始状态
Spring 使用 commons-logging 日志包。打印的日志是下面这样的。不用细看,截图放在这是为了和后面日志打印的情况对比
7.3.2 加入 slf4j+logback
<!-- 日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
7.3.3 我们主动打印的日志
更换框架的日志系统之前
7.3.4 更换框架的日志系统之后
※使用日志打印信息和使用 sysout 打印信息的区别:sysout 如果不删除,那么执行到这里必然会打印;如果使用日志方式打印,可以通过日志级别控制信息是否打印。
7.3.4 更换框架的日志系统
-第一步:排除 commons-logging
1.选中该模块
2.点击show dependenties
3.切换试图
4.选中要排除的依赖,右击
5.选择Execlude,然后选择需要在哪个模块添加排除依赖
6.完成
也可以在这找对应的模块进行操作
效果图:
第二步:加入转换包
<!-- 其他日志框架的中间转换包 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>
7.3.5 logback 配置文件
logback 工作时的具体细节可以通过 logback.xml 来配置
<?xml version="1.0" encoding="UTF-8" ?>
<configuration debug="true">
<!--指定日志输出的位置-->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!--日志输出的格式-->
<!--按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行-->
<pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern>
</encoder>
</appender>
<!--设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR-->
<!--指定任何一个日志级别都只打印当前级别和后面级别的日志。-->
<root level="DEBUG">
<!--指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender-->
<appender-ref ref="STDOUT"/>
</root>
<!--根据特殊需求指定局部日志级别-->
<logger name="com.atguigu.crowd.mapper" level="DEBUG"/>
</configuration>
8. 声明式事务
8.1 目标
从事务角度:一个事务方法中包含的多个数据库操作,要么一起提交、要么一起回滚。也就是说事务方法中的多个数据库操作,有任何一个失败,整个事务全部回滚。从声明式角度:由 Spring 来全面接管数据库事务。用声明式代替编程式。
try {
// 核心操作前:开启事务(关闭自动提交)
// 对应 AOP 的前置通知
connection.setAutoCommit(false);
// 核心操作
adminService.updateXxx(xxx, xxx);
// 核心操作成功:提交事务
// 对应 AOP 的返回通知
connection.commit();
}catch(Exception e){
// 核心操作失败:回滚事务
// 对应 AOP 的异常通知
connection.rollBack();
}finally{
// 不论成功还是失败,核心操作终归是结束了
// 核心操作不管是怎么结束的,都需要释放数据库连接
// 对应 AOP 的后置通知
if(connection != null){
connection.close();
}
}
8.2 思路
8.3 操作
8.3.1 加入 AOP 依赖包
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<!-- AOP 所需依赖 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<!-- AOP 所需依赖 -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
</dependency>
8.3.2 第一步:创建 Spring 配置文件
8.3.3 第二步:配置事务管理器
<!-- 配置事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 装配数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
8.3.4 第三步:配置 AOP
<!-- 配置事务切面-->
<aop:config>
<!-- 考虑到后面我们整合SpringSecurity, 避免把UserDetailsService加入事务控制,让切入点表达式定位到serviceImpl-->
<aop:pointcut expression="execution(* *..*ServiceImpl.*(..))" id="txPointcut"/>
<!-- 将切入点表达式和事务通知关联起来-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
8.3.5 第四步:配置事务属性
<!-- 配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- 配置事务属性-->
<tx:attributes>
<!-- 查询方法:配置只读属性,让数据库知道这是一个查询操作,能够进行一定优化-->
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="count*" read-only="true"/>
<!-- 增删改方法:配置事务传播行为、回滚异常-->
<!--
propagation属性:
REQUIRED: 默认值,表示当前方法必须工作在事务中,如果当前线程上没有已经开启的事务,如果已经有了, 那么就使用这个已有的事务。
顾虑:用别人的事务有可能“被”回滚
REQUIRES_NEW:建议使用的值,表示当前方法必须工作在事务中,如果当前线程上没有已经开启的事务,则自己开新事务。就算是已经有了,也在自己开启的事务中运行。
(简洁:不管线程上有没有事务,都要自己开事务,在自己的事务中运行。)
好处:不会受到其他事务回滚的影响
-->
<!--
rollback-for属性: 配置事务方法针对什么样的异常回滚
默认:运行时异常回滚
建议:编译时异常和运行时异常都回滚
-->
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="remove*" propagation="REQUIRED"/>
<tx:method name="batch*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
细节:
8.4 spring-persist-tx.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"
>
<!-- 配置自动扫描的包:主要是为了把Service扫描到IOC容器中-->
<context:component-scan base-package="com.atguigu.crowd.service"/>
<!-- 配置事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 装配数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务切面-->
<aop:config>
<!-- 考虑到后面我们整合SpringSecurity, 避免把UserDetailsService加入事务控制,让切入点表达式定位到serviceImpl-->
<aop:pointcut expression="execution(* *..*ServiceImpl.*(..))" id="txPointcut"/>
<!-- 将切入点表达式和事务通知关联起来-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
<!-- 配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- 配置事务属性-->
<tx:attributes>
<!-- 查询方法:配置只读属性,让数据库知道这是一个查询操作,能够进行一定优化-->
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="count*" read-only="true"/>
<!-- 增删改方法:配置事务传播行为、回滚异常-->
<!--
propagation属性:
REQUIRED: 默认值,表示当前方法必须工作在事务中,如果当前线程上没有已经开启的事务,如果已经有了, 那么就使用这个已有的事务。
顾虑:用别人的事务有可能“被”回滚
REQUIRES_NEW:建议使用的值,表示当前方法必须工作在事务中,如果当前线程上没有已经开启的事务,则自己开新事务。就算是已经有了,也在自己开启的事务中运行。
(简洁:不管线程上有没有事务,都要自己开事务,在自己的事务中运行。)
好处:不会受到其他事务回滚的影响
-->
<!--
rollback-for属性: 配置事务方法针对什么样的异常回滚
默认:运行时异常回滚
建议:编译时异常和运行时异常都回滚
-->
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="remove*" propagation="REQUIRED"/>
<tx:method name="batch*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
</beans>
8.5 添加对应的类 以及各类都加上空(有)参方法、get方法、set方法、toString方法
8.6 AdminServiceImpl
@Service
public class AdminServiceImpl implements AdminService {
@Autowired
private AdminMapper adminMapper;
private Logger logger = LoggerFactory.getLogger(AdminServiceImpl.class);
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Override
public void saveAdmin(Admin admin) {
// 1.密码加密
String userPswd = admin.getUserPswd();
// userPswd = CrowdUtil.md5(userPswd);
userPswd = passwordEncoder.encode(userPswd);
admin.setUserPswd(userPswd);
// 2.生成创建时间
Date date = new Date();
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String createTime = format.format(date);
admin.setCreateTime(createTime);
// 3.执行保存
try {
adminMapper.insert(admin);
} catch (Exception e) {
e.printStackTrace();
logger.info("异常全类名" + e.getClass().getName());
if (e instanceof DuplicateKeyException){
throw new LoginAcctAlreadyInUseException(CrowdConstant.MESSAGE_LOGIN_ACCT_ALREADY_IN_USE);
}
}
}
8.7 LoginAcctAlreadyInUseException.java
/**
* 保存或更新Admin时如果检测到登录账号重复抛出这个异常
*/
public class LoginAcctAlreadyInUseException extends RuntimeException {
private static final long serialVersionUID = 1L;
public LoginAcctAlreadyInUseException() {
}
public LoginAcctAlreadyInUseException(String message) {
super(message);
}
}
测试
效果:
注意
事务没有了
9. 表述层工作机制
9.1 启动过程
9.2 访问过程
9.3 装配文件关系
10. 表述层环境搭建
10.1加入依赖
使用 SpringMVC 环境引入 spring-webmvc 依赖即可,同时可以把 spring-context 依赖去掉,因为根据依赖的传递性,spring-webmvc 会依赖 spring-context。
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
10.2web.xml 配置
10.2.1ContextLoaderListene
<!-- needed for ContextLoaderListener-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-persist-*.xml</param-value>
</context-param>
<!-- Bootstraps the root web application context before servlet initialization-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
10.2.2CharacterEncodingFilte
<!-- 配置 CharacterEncodingFilter 解决 POST 请求的字符乱码问题 -->
<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>
<!-- 强制请求设置字符集-->
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<!-- 强制响应设置字符集-->
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<!-- 这个Filter执行的顺序要在所有其他Filter前面-->
<!-- 原因如下:-->
<!-- request.setCharacterEncoding(encoding)必须在request.getParameter()前面-->
<!-- response.setCharacterEncoding(encoding)必须在response.getWriter前面-->
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
10.2.3HiddenHttpMethodFilter
遵循 RESTFUL 风格将 POST 请求转换为 PUT 请求、DELETE 请求时使用。
省略不配。
10.2.4DispatcherServlet
基本配置
<!-- 配置 SpringMVC 的前端控制器 -->
<!-- The front controller of this Spring Web application responsible for handing all application requests-->
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 以初始化参数的形式指定 SpringMVC 配置文件的位置 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-web-mvc.xml</param-value>
</init-param>
<!-- Servlet默认生命周期中, 创建对象是在第一次接受到请求时-->
<!-- 而DispatcherServlet创建对象后有大量的"框架初始化"工作,不适合在第一次请求时来做-->
<!-- 设置oad-on-startup 就是为了让DispatcherServlet在Web应用启动时创建对象 、初始化-->
<load-on-startup>1</load-on-startup>
</servlet>
10.3请求扩展名
10.3.1*.html 扩展名
举例
http://localhost:8080/atcrowdfunding02-admin-webui/save/emp.html
作用:伪静态
表面上看起来是一个访问静态资源的请求,但是实际上是由 SpringMVC
交给 handler 来处理的动态资源。
好处 1:有利于 SEO 优化让搜索引擎更容易找到我们的网站,有利于网站的推广
好处 2:隐藏后端技术实现细节给黑客入侵系统增加难度
好处 3:自动解决静态资源访问问题
访问 a.png 本身不符合*.html 这个 url-pattern,和 SpringMVC 完全没有关系,当前请求由 Tomcat 处理。如 果 url-pattern 映 射 了 “ / ”, 那 么 SpringMVC 中 还 需 要 配 置DefaultServletHandler。
缺陷:不符合 RESTFUL 风格
10.3.2*.json 扩展名
提出问题
描述问题
请求扩展名 http://localhost:8080/extra01-ajax/get/emp/by/ajax.html
服务器端打算返回的数据:JSON 格式二者不匹配!!!
分析问题
请求扩展名和响应体的数据格式不匹配!!!
解决问题
让请求扩展名和预计的响应体数据格式一致。
http://localhost:8080/extra01-ajax/get/emp/by/ajax.json
同时让 SpringMVC 在映射*.html 扩展名之外再映射*.json 扩展名,不然会返回 404
<!-- Map all requests to the DispatcherServlet for handling-->
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<!-- DispatcherServlet 映射的 URL 地址 -->
<!-- 大白话:什么样的访问地址会交给 SpringMVC 来处理 -->
<!-- url-pattern配置方式一:/表示拦截所有请求 -->
<!-- <url-pattern>/</url-pattern> -->
<!-- url-pattern配置方式二:配置请求扩展名 -->
<!-- 优点1: xxx.css、 xxx.js、 xxx.png等等静态资源完全不经过SpringMVC,不需要特殊处理-->
<!-- 优点2: 可以实现伪静态效果。表面上看起来是访问一个HTML文件这样的静态资源,但是实际上是经过Java代码运算-->
<!-- 伪静态作用1:给黑客入侵增加难度-->
<!-- 伪静态作用2:有利于SEO优化(让百度、谷歌这样的搜索引擎更容易找到我们项目)-->
<!-- 缺点: 不符合RESTFul风格-->
<url-pattern>*.html</url-pattern>
<url-pattern>*.json</url-pattern>
<!-- 为什么要另外再配置json扩展名呢?-->
<!-- 如果一个Ajax请求扩展名是html,但是实际服务器给浏览器返回的是json数据,二者就不匹配了,会出现406错误-->
<!-- 为了让Ajax请求能够顺利拿到JSON格式的响应数据,我们另外配置json扩展名-->
<!--<url-pattern>*.json</url-pattern>-->
</servlet-mapping>
10.4spring-web-mvc.xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
">
<!-- 配置自动扫描的包:扫描handler-->
<context:component-scan base-package="com.atguigu.crowd.mvc"/>
<!-- 配置视图解析器 -->
<!-- 拼接公式→前缀+逻辑视图+后缀=物理视图 -->
<!--
@RequestMapping("/xxx/xxx")
public String xxx() {
// 这个返回值就是逻辑视图
return "target";
}
物理视图是一个可以直接转发过去的地址
物理视图:"/WEB-INF/"+"target"+".jsp"
转发路径:"/WEB-INF/target.jsp"
-->
<!-- 配置SpringMVC的注解驱动-->
<mvc:annotation-driven/>
<!-- 配置视图解析器-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀:附加到逻辑视图名称前 -->
<property name="prefix" value="/WEB-INF/"/>
<!-- 后缀:附加到逻辑视图名称后 -->
<property name="suffix" value=".jsp"/>
</bean>
</beans>
10.5 页面上的 base 标签
<!-- 引入Servlet容器中相关依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<!-- JSP页面使用的依赖 -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<scope>provided</scope>
</dependency>
10.5.2 作用
将页面上路径中的${pageContext.request.contextPath}部分提取到页面开头
10.5.3 写法
<base href="http://${pageContext.request.serverName }:${pageContext.request.serverPort }${pageContext.request.contextPath }/"/>
10.5.4 需要注意的点
base 标签必须写在 head 标签内部
base 标签必须在所有“带具体路径”的标签的前面
serverName 部分 EL 表达式和 serverPort 部分 EL 表达式之间必须写“:”
serverPort 部分 EL 表达式和 contextPath 部分 EL 表达式之间绝对不能写“/”
原因:contextPath 部分 EL 表达式本身就是“/”开头
如果多写一个“/”会干扰 Cookie 的工作机制
serverPort 部分 EL 表达式后面必须写“/”
10.6 初步测试
10.6.1 编写:handler.java
@Controller
public class TestHandler {
@Autowired
private AdminService adminService;
@RequestMapping("/test/ssm.html")
public String testSsm(ModelMap modelMap){
List<Admin> adminList = adminService.getAll();
modelMap.addAttribute("adminList", adminList);
return "target";
}
}
10.6.2 编写: AdminService.java
@Service
public class AdminServiceImpl implements AdminService {
@Autowired
private AdminMapper adminMapper;
@Override
public List<Admin> getAll() {
return adminMapper.selectByExample(new AdminExample());
}
10.6.3创建目标 JSP 页面(视图)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<%-- isELIgnored="false"--%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>Success</h1>
${requestScope.adminList }
</body>
</html>
10.6.4友情提示
跟着视频学习项目开发,表面上的现象错综复杂,一会儿在页面,一会儿写jQuery,一会儿写 handler,一会儿写 Service,一会儿写 SQL,一会儿写配置文件。背后有思路作为一根红线把所有的现象都穿起来,跟着思路走,就不会迷路。思路的背后是目标。思维一定要有层次!!!
效果图: