Spring
核心概念
IoC(控制反转)
(Inversion of Control)
概念
使用对象时,由主动new产生对象转换为由外部提供对象,在此过程中对象创建控制权由程序转移到外部,此思想称为控制反转。
Spring提供了一个容器,称为Ioc容器,用来充当Ioc思想中的“外部”。
IoC容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在IoC容器中统称为Bean。
Ioc入门案例
- 管理什么? (Service与Dao)
- 如何将被管理的对象告知Ioc容器? (配置)
- 被管理的对象交给Ioc容器,如何获取到Ioc容器? (接口)
- Ioc容器得到之后,如何从容器中获取bean? (接口方法)
DI(依赖注入)
(Dependency Injection)
概念
在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入。
目标:充分解耦
-
使用IoC容器管理bean
-
在IoC容器内将有依赖关系的bean进行关系绑定
使用对象时不仅可以直接从IoC中取,并且获取到的bean已经绑定了所有的依赖关系。
DI入门案例
- 基于Ioc管理 bean
- Service中使用new的形式创建的Dao对象是否保留? (否)
- Service中需要的Dao对象如何进入到Service中? (提供方法)
- Service与Dao的关系如何描述? (配置)
AOP(面向切面编程)
(Aspect Oriented Programming)
概念
一种编程范式,指导开发者如何组织程序结构
作用:在不惊动原始设计的基础上为其进行功能增强
Spring理念:无侵入式编程
-
连接点
连接点是在应用执行过程中能够插入切面(Aspect)的一个点。这些点可以是调用方法时、甚至修改一个字段时。它是一个虚拟的概念,例如坐地铁的时候,每一个站都可以下车,那么这每一个站都是一个连接点。假如一个对象中有多个方法,那么这个每一个方法就是一个连接点。
在SpringAOP中,理解为方法的执行
-
通知
通知定义了何时,做什么。
在SpringAOP中,最终体现为方法
-
切入点
切入点是一些特使的连接点,是具体附加通知的地方。例如坐地铁的时候,具体在某个站下车,那这个站就是切入点。
在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
-
切面
切面是通知和切入点的结合,通知规定了在什么时机干什么事,切入点规定了在什么地方。如“在8点钟在西站下车“ 就是一个切面。那么时间8点,动作下车就是一个通知。西站就是一个切入点。
AOP入门案例
- 制作连接点方法
- 制作共性功能(通知)
- 定义切入点
- 绑定切入点与通知
AOP工作流程
-
Spring容器启动
-
读取所有切面配置中的切入点(也就是绑定通知的切入点)
-
初始化bean,判定bean对应类中的方法是否匹配上述切入点
- 匹配失败,直接创建对象
- 匹配成功,创建原始对象(目标对象)的代理对象
-
获取bean执行方法
- 获取bean,调用方法并执行,完成操作
- 获取的bean是代理对象,根据代理对象的运行模式运行原始方法与增强内容,完成操作
-
核心概念
-
目标对象
原始功能去掉共性功能对应的类产生的对象,这种对象是无法完成最终工作的
-
代理
目标对象无法完成工作,需要对其进行功能回填,通过原始对象的代理对象实现
-
SpringAOP本质:代理模式
-
AOP切入点表达式
-
切入点:要进行增强的方法
-
切入点表达式:对要进行增强的方法的描述方式
-
切入点表达式标准格式:
动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数) 异常名)
- 动作关键字:描述切入点执行的动作,例如
execution
表示执行到切入点 - 访问修饰符:
public、private
等,可省略 - 异常名,可省略
- 动作关键字:描述切入点执行的动作,例如
-
可以使用通配符描述切入点
-
*
:单个独立的任意符号(一个单词),可以独立出现,也可以作为前缀或者后缀的匹配符出现execution(public * service.*.UserService.select* (*)) // 匹配service包中任意包中的UserService接口中select开头的具有一个参数的方法
-
..
:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写execution(public * *..UserService.selectAll(..)) // 匹配任意包下的UserService接口中具有任意参数的selecAll方法
-
+
:专用于匹配子类类型execution(* *..*Service+.*(..)) // 匹配任意业务层接口的任意方法
-
AOP通知类型
AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终代码运行时将其加入到合理的位置
AOP通知分为五种类型
-
前置通知
@Before
-
后置通知
@After
@After("test()") public void after(JoinPoint joinPoint) { System.out.println("after"); }
就算方法出现异常,也会执行
与
finally
类似,在return指令之前执行。并且后于finally
的执行 -
环绕通知
@Around
@Around("pointcut()") public void around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("before..."); joinPoint.proceed(); System.out.println("after..."); }
-
返回后通知
方法正确执行后,才会执行
-
抛出异常后通知
多种通知共存时,前置通知与后置通知离原始方法更近,环绕通知在两侧执行。也可以说Before
和After
先与原始方法绑定,再进行Around
的增强
AOP通知获取数据
Signature signature = joinPoint.getSignature();
String className = signature.getDeclaringTypeName();
String methodName = signature.getName();
Object[] args = joinPoint.getArgs();
@AfterReturning(value = "test()", returning = "ret")
// 如果JoinPoint和其他参数同时出现,JoinPoint必须是第一个
public void afterReturning(Object ret) {
System.out.println("after returning: " + ret);
}
@AfterThrowing(value = "test()", throwing = "throwable")
public void afterThrowing(Throwable throwable) {
System.out.println("after throwing:");
throwable.printStackTrace();
}
bean配置
bean基础配置
类别 | 描述 |
---|---|
名称 | bean |
类型 | 标签 |
所属 | beans标签 |
功能 | 定义Spring容器管理的对象 |
格式 | <beans> <bean></bean> </beans> |
属性列表 | id:bean的iid,使用容器可以通过id值获取对应的bean,在一个容器中id唯一 class:bean的类型,即配置的bean的全路径类名 |
bean别名配置
类别 | 描述 |
---|---|
名称 | name |
类型 | 属性 |
所属 | bean标签 |
功能 | 定义bean的别名,可以定义多个,使用逗号(,)分号(;)空格( )分隔 |
bean的作用范围配置
类别 | 描述 |
---|---|
名称 | scope |
类型 | 属性 |
所属 | bean标签 |
功能 | 定义bean的作用范围 singleton:单例 prototype:非单例 |
bean作用范围说明
- 为什么bean默认为单例?
- 适合交给容器管理的bean
- 表现层对象
- 业务层对象
- 数据层对象
- 工具对象
- 不适合交给容器管理的bean
- 封装实体的域对象
bean实例化
-
bean本质上就是对象,使用构造方法创建bean
-
使用静态工厂实例化bean
<bean class="静态工厂全路径类名" factory-method="返回实例对象的静态方法"></bean>
-
使用实例工厂实例化bean
先创建工厂类的bean
<bean factory-bean="工厂的bean" factory-method="返回实例对象的方法"></bean>
-
使用FactoryBean实例化bean
先实现FactoryBean接口,也就是创建了一个工厂类
<bean> class="实现FactoryBean接口的类的全路径名称"</bean>
bean的生命周期
- 初始化容器
- 创建对象(分配内存)
- 执行构造方法
- 执行属性注入(set操作)
- 执行bean初始化方法
- 使用bean
- 执行业务操作
- 关闭/销毁容器
- 执行bean销毁方法
bean的销毁时机
- 容器关闭前触发bean的销毁
- 关闭容器方式
- 手工关闭
close()
- 注册关闭钩子
registerShutdownHook
- 手工关闭
依赖注入方式
-
思考:向一个类中传递数据的方式有几种?
- 普通方法(set方法)
- 构造方法
-
思考:依赖注入描述了在容器中建立bean与bean之间依赖关系的过程,如果bean运行需要的是数字或字符串呢?
- 引用类型
- 简单类型(基本数据类型与String)
-
依赖注入方式
- setter注入(简单类型、引用类型)
- 构造器注入(简单类型、引用类型)
-
依赖注入方式选择
- 强制依赖使用构造器进行
- 可选依赖使用setter注入进行
依赖自动装配
- Ioc容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配
- 自动装配方式
- 按类型(最常用)
- 按名称
- 按构造方法
依赖自动装配特征
- 用于引用类型依赖注入,不能对简单类型进行操作
- 按类型自动装配(byType)必须保障容器中想同类型的bean唯一
- 按名称自动装配(byName)必须保障容器中具有指定名称的bean
- 自动装配优先级低于setter与构造器注入,同时出现时自动装配失效
集合注入
-
Array
<property> <array> <value>值</value> <ref bean="bean的id"></ref> ... </array> </property>
-
List
<property> <list> <value>值</value> <ref bean="bean的id"></ref> ... </list> </property>
-
Set
<property> <set> <value>值</value> <ref bean="bean的id"></ref> ... </set> </property>
-
Map
<property> <map> <entry key="键" value="值"></entry> ... </map> </property>
-
Properties
<property> <props> <prop key="键">值</prop> ... </props> </property>
容器
-
创建容器
-
方式一:类路径加载配置文件
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
-
方式二:文件路径加载配置文件
ClassPathXmlApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\applicationContext.xml");
-
加载多个配置文件
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("bean1.xml", "nean2.xml");
-
-
获取bean
-
方式一:使用bean名称获取
Bookdao bookDao = (BookDao) ctx.getBean("bookDao");
-
方式二:使用bean名称获取并指定类型
Bookdao bookDao = ctx.getBean("bookDao", BookDao.class);
-
方式三:使用bean类型获取
Bookdoa bookDao = ctx.getBean(bookDao.class);
-
注解开发
定义bean
-
以下注解标注在类前,表示该类是一个bean
@Component
@Controller
@Service
@Repository
-
配置文件中加入该命名空间,告诉spring去哪些包下扫描、加载bean
<context:component-scan/>
纯注解开发
-
@Configuration
定义一个配置类
SpringConfig
,用此注解标注配置类 -
@ComponentScan
在配置类上添加此注解扫描bean所在包
@ComponentScan("包名")
或@ComponentScan({"包名1", "包名2"...})
-
AnnotationConfigApplicationContext
传入配置类的
class
来创建该容器对象
bean作用范围
@Scope("singleton")
、@Scope("prototype")
bean生命周期
- 使用
@PostConstruct、@PreDestroy
定义生命周期
依赖注入
-
自动装配
@Autowired
按类型@Qualifier
有类型冲突时,指定名称@Value
实现简单类型注入 -
加载配置文件
在配置类中使用
@PropertySource
注解加载配置文件多文件使用数组格式,不支持通配符
加载之后可使用
${变量名}
来引用
第三方bean管理
在配置类中导入自定义类
@Import(类.class) //多个使用数组形式
自定义类中:
定义一个方法,返回要管理的bean
添加@Bean表示当前方法的返回值是一个bean
依赖注入
-
简单类型
@Value
一般要加载配置文件,使用
@PropertySource
注解 -
引用类型
对返回所管理bean的方法,定义形参,Spring会根据类型自动装配(形参类型是已经管理的bean)
Spring引入MyBatis
- 增加
MyBatisConfig
配置文件 Dao
层用注解开发或编写SQL映射文件
Spring事务
事务作用:在数据层保障一系列数据库操作同成功或同失败
Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功或同失败
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
void commit(TransactionStatus var1) throws TransactionException;
void rollback(TransactionStatus var1) throws TransactionException;
}
public classs DataSourceTransactionManager {
...
}
配置
-
在Spring配置文件中启用事务的注解开发
@EnableTransactionManagement
-
定义事务管理对象的bean
@Bean public PlatformTransactionManager platformTransactionManager(DataSource dataSource) { DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); dataSourceTransactionManager.setDataSource(dataSource); return dataSourceTransactionManager; }
-
在想要添加事务管理的业务层接口(类)上添加Spring事务管理的注解
@Transactional
Spring事务角色
-
事务管理员
发起事务方,在Spring中通常代指业务层开启事务的方法
-
事务协调员
加入事务方,在Spring中通常代指数据层方法,也可以是业务层方法
Spring事务属性
@Transactional(readOnly = true, timeout = -1, rollbackFor = {Exception.class})
默认只有遇到Error
和运行时异常才会回滚,对于其他异常,考虑手动设置回滚。
- 事务传播行为:事务协调员对事务管理员所携带事务的处理态度
@Transactional(propagation = Propagation.REQUIRES_NEW)
传播属性 | 事务管理员 | 事务协调员 |
---|---|---|
REQUIRED | 开启 | 加入 |
无 | 新建 | |
REQUIRES_NEW | 开启 | 新建 |
无 | 无 | |
SUPPORTS | 开启 | 加入 |
无 | ERROR | |
NOT_SUPPORTED | 开启 | 无 |
无 | 新建 | |
MANDATORY | 开启 | 加入 |
无 | 无 | |
NEVER | 开启 | ERROR |
无 | 无 | |
NESTED | 设置savePoint,一旦回滚事务,回滚到savePoint,交由用户提交/回滚 |
SpringMVC
简介
- SpringMVC技术与Servlet技术等同,均属于web层开发技术
- SpringMVC是一种基于Java实现的基于MVC模型的轻量级web框架
配置
-
添加配置文件
@Configuration @ComponentScan("controller") public class SpringConfig { }
-
初始化Servlet容器,加载SpringMVC环境,设置SpringMVC处理的请求
public class ServletContainerInitConfig extends AbstractDispatcherServletInitializer { // 加载SpringMVC容器的配置 @Override protected WebApplicationContext createServletApplicationContext() { AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); ctx.register(SpringMVCConfig.class); return ctx; } //设置那些请求交给SpringMVC处理 @Override protected String[] getServletMappings() { return new String[]{"/"}; } // 加载Spring配置 @Override protected WebApplicationContext createRootApplicationContext() { AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); ctx.register(SpringConfig.class); ctx.registerShutdownHook(); return ctx; } }
-
创建SpringMVC控制器类
添加
@Controller
注解在方法上添加路由配置的注解
@RequestMapping
和响应体的注解@ResponseBody
@Configuration public class UserController { @RequestMapping("/save") @ResponseBody public String save() { return "save"; } }
-
工作流程
启动服务器初始化过程
- 服务器启动,执行
ServletContainerInitConfig
类,初始化web容器 - 执行
createServletApplicationContext
方法,创建WebApplicationContext
对象 - 加载
SpringMVCConfig
配置类 - 执行
@ComponentScan
加载对应的bean - 加载
Controller
类,每个@RequestMapping
对应一个具体的方法 - 执行
getServletMappings
方法,定义所有的请求都通过SpringMVC
单次请求过程
- 发送请求
/request
- web容器发现所有请求都通过SpringMVC,将请求交给SpringMVC处理
- 解析请求路径
/request
- 由
/request
匹配并执行对应的方法 - 检测到有
ResponseBody
直接将执行方法的返回值作为响应体返回给请求方
- 服务器启动,执行
请求与响应
请求映射路径
在controller
类上添加@RequestMapping("/模块名")
以防止请求路径冲突,相当于在该控制类下的每个方法的请求路径之前都加上了模块名
请求方式
-
form-data
-
post: form-data
它会将表单的数据处理为一条消息,以标签为单元,用分隔符分开。既可以上传键值对,也可以上传文件。当上传的字段是文件时,会有Content-Type来表名文件类型;content-disposition,用来说明字段的一些信息;
由于有boundary隔离,所以multipart/form-data既可以上传文件,也可以上传键值对,它采用了键值对的方式,所以可以上传多个文件。
-
post: x-www-from-urlencoded
会将表单内的数据转换为键值对
-
post: form-data需要进行额外配置
在初始化SpringMVC环境时,会尝试加载用户对
MultipartResolver
的配置,这个接口负责form-data格式数据的处理// DispatcherServlet.java public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver"; private void initMultipartResolver(ApplicationContext context) { try { this.multipartResolver = (MultipartResolver)context.getBean("multipartResolver", MultipartResolver.class); if (this.logger.isTraceEnabled()) { this.logger.trace("Detected " + this.multipartResolver); } else if (this.logger.isDebugEnabled()) { this.logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName()); } } catch (NoSuchBeanDefinitionException var3) { this.multipartResolver = null; if (this.logger.isTraceEnabled()) { this.logger.trace("No MultipartResolver 'multipartResolver' declared"); } } }
上述代码尝试获取容器中的
MultipartResolver
对象,若通过注解开发,生成bean的方法名即是该bean的名称,所以方法名必须为multipartResolver
,配置文件同理按照正常配置类的编写引入
MultipartResolver
即可MultipartResolver.yml
defaultEncoding: utf-8 maxUploadSize: 1073741824 maxInMemorySize: 10240 uploadTempDir: file:WEB-INF/uploadFile/tmp resolveLazily: true
MultipartResolverConfig.java
package config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.PropertySource; import org.springframework.core.io.FileSystemResource; import org.springframework.web.multipart.MultipartResolver; import org.springframework.web.multipart.commons.CommonsMultipartResolver; import java.io.IOException; @PropertySource("classpath:MultipartResolver.yml") public class MultipartResolverConfig { @Value("${defaultEncoding}") private String defaultEncoding; @Value("${maxUploadSize}") private long maxUploadSize; @Value("${maxInMemorySize}") private int maxInMemorySize; @Value("${uploadTempDir}") private String uploadTempDir; @Value("${resolveLazily}") private boolean resolveLazily; @Bean public MultipartResolver multipartResolver() throws IOException { CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver(); commonsMultipartResolver.setDefaultEncoding(defaultEncoding); commonsMultipartResolver.setMaxUploadSize(maxUploadSize); commonsMultipartResolver.setMaxInMemorySize(maxInMemorySize); commonsMultipartResolver.setUploadTempDir(new FileSystemResource(uploadTempDir)); commonsMultipartResolver.setResolveLazily(resolveLazily); return commonsMultipartResolver; } }
最后,
MultipartResolver
是SpringMVC的配置,在对应的配置类中加入@Import(MultipartResolverConfig.class)
若要接收传过来的文件,参数类型应为
MultipartFile
-
请求参数
-
普通参数
- 按照相同名称,自动填入形参
@RequestParam("请求参数名称") Type param
指定填入
-
引用类型参数
- 对象内属性均为基础类型,只要属性名称与请求参数对应即可
- 对象内属性含有引用类型,通过符号
.
来描述层次关系
-
数组参数
- 请求的相同名称的参数,填入同名数组
-
集合参数
- 通过
@RequestParam
将填入请求参数填入同名集合
- 通过
-
日期参数
-
在形参前加入注解,指定日期格式
@DateTimeFormat(pattern = "yyyy/MM/dd HH:mm:ss") Date date
-
-
-
-
json
-
配置
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.0</version> </dependency>
在SpringMVC配置中加入
@EnableWebMvc
@EnableWebMvc
功能之一:根据类型匹配对应的类型转换器接收参数前加入
@RequestBody
-
json数据
- json对象
- 对象内属性均为基础类型,只要属性名称与请求json的属性名称对应即可
- 对象内属性含有引用类型,按照json的
{}
格式来描述
- json普通数组
- 传递一个json数组,用相应类型列表接收即可,不要求参数名称
- json对象数组
- 传递一个json对象数组,用相应类型列表接收即可,不要求参数名称
- json对象
-
响应页面
不需要@ResponseBody
,只需要以字符串形式返回页面的名称即可
响应数据
-
@ResponseBody
将当前控制器返回值作为响应体通过
HttpMessageConverter
接口的实现类进行转换 -
响应文本数据
添加
@ResponseBody
,返回字符串即可 -
响应json数据
-
json对象、json数组、json对象数组
添加
@ResponseBody
将返回类型设置为相应类型即可
返回对象的类中,具有相应
get
方法的属性才能转换成对应的json,否则不会进行转换
-
异常处理器
package aop;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import response.Response;
@ControllerAdvice
@ResponseBody
public class ExceptionAdvice {
@ExceptionHandler(Exception.class)
public Response handleException(Exception exception) {
exception.printStackTrace();
return Response.fail();
}
}
@ControllerAdvice
是spring提供的为controller提供的通知注解
@ExceptionHandler
定义处理异常的方法及异常种类
@ResponseBody
是对响应的返回数据进行处理,否则只支持ModelAndView,Model,ModelMap,Map,View,void,Sting
类型的返回
不要忘记扫描到该类
拦截器
概念
-
拦截器
是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行
-
作用
- 在指定的方法调用前后执行预先设定的代码
- 阻止原始方法的执行
-
拦截器与过滤器区别
-
归属不同
Filter
属于Servlet
技术,Interceptor
属于SpringMVC
技术 -
拦截内容不同
Filter
对所有访问进行增强,Interceptor
仅针对SpringMVC
的访问增强
-
入门案例
-
制作拦截器功能类
package interceptor; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Component public class Interceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("pre handle..."); // 返回false则拦截请求对应方法的执行 return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("post handle..."); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("after completion..."); } }
-
配置拦截器执行位置
package config; import interceptor.Interceptor; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration @ComponentScan("interceptor") public class SpringMVCSupport implements WebMvcConfigurer { private Interceptor interceptor; public SpringMVCSupport(Interceptor interceptor) { this.interceptor = interceptor; } // 配置资源路径,放开MVC对其的拦截 @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("").addResourceLocations(""); } // 配置拦截器生效的路径 @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(interceptor).addPathPatterns("/book/*"); } }
在
SpringMVCConfig
配置类中,扫描config.support
包,以加载该类
不知道为啥extends WebMvcConfigurationSupport
实现的类通过Import
导入不能生效
也可以让SpringMVCConfig
实现WebMvcConfigurer
接口,简化配置
拦截器参数
在拦截器的方法中,request、response
可以获取原始请求,设置响应
handler
代表拦截的请求绑定的控制层方法,是一个HandlerMethod
对象
ModelAndView
封装了响应做页面跳转相关的数据
Exception
是表现层抛出的异常
拦截器链
- 多拦截器执行顺序
- 当配置多个拦截器时,形成拦截器链
- 拦截器链执行顺序按照拦截器添加顺序为准
pre1 - pre2 - controller - post2 - post1 - after2 - after1
- 当拦截器中出现对原始方法的拦截,后续的拦截器
post
均终止运行 - 只要拦截器的
pre
放行了,对应的after
就会执行,即使后面的拦截器阻止了原始方法
SSM整合
配置pom.xml
的依赖项
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.31</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
<build>
<finalName>SpringMVC-test</finalName>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>8080</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
其他配置文件
-
druid.yml
jdbc.driverClassName: com.mysql.cj.jdbc.Driver jdbc.url: jdbc:mysql://127.0.0.1:3306/test?useServerPrepStmts=true jdbc.username: root jdbc.password: 123456 # 初始化连接数量 druid.initialSize: 5 # 最大连接数 druid.maxActive: 10 # 最大等待时间(ms) druid.maxWait: 3000
-
MultipartResolver.yml
defaultEncoding: utf-8 maxUploadSize: 1073741824 maxInMemorySize: 10240 uploadTempDir: uploadFile/tmp resolveLazily: true
配置类
-
SpringConfig
package config; import org.springframework.context.annotation.*; import org.springframework.transaction.annotation.EnableTransactionManagement; @Configuration @ComponentScan({"service", "dao", "aop"}) @PropertySource("classpath:properties.yml") @Import({JdbcConfig.class, MyBatisConfig.class}) @EnableAspectJAutoProxy @EnableTransactionManagement public class SpringConfig { }
-
JdbcConfig
package config; import com.alibaba.druid.pool.DruidDataSource; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.PropertySource; import javax.sql.DataSource; @PropertySource("druid.yml") public class JdbcConfig { @Value("${jdbc.driverClassName}") private String driverClassName; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Bean public DataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(driverClassName); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } @Bean public PlatformTransactionManager platformTransactionManager(DataSource dataSource) { DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); dataSourceTransactionManager.setDataSource(dataSource); return dataSourceTransactionManager; } }
-
MyBatisConfig
package config; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.mapper.MapperScannerConfigurer; import org.springframework.context.annotation.Bean; import javax.sql.DataSource; public class MyBatisConfig { @Bean public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); // 类型别名,对指定包下的类可以直接用不区分大小写的类名代替全类名 sqlSessionFactoryBean.setTypeAliasesPackage("domain"); sqlSessionFactoryBean.setDataSource(dataSource); return sqlSessionFactoryBean; } @Bean public MapperScannerConfigurer mapperScannerConfigurer() { MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); // 包扫描加载sql映射文件 mapperScannerConfigurer.setBasePackage("dao"); return mapperScannerConfigurer; } }
-
SpringMVCConfig
package config; import controller.interceptor.Interceptor; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration @ComponentScan({"controller"}) @Import({MultipartResolverConfig.class}) @EnableWebMvc public class SpringMVCConfig implements WebMvcConfigurer { private Interceptor interceptor; public SpringMVCConfig(Interceptor interceptor) { this.interceptor = interceptor; } // 配置资源路径,放开MVC对其的拦截 @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("").addResourceLocations(""); } // 配置拦截器生效的路径 @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(interceptor).addPathPatterns("/book/*"); } }
-
ServletContainerInitConfig
package config; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer; public class ServletContainerInitConfig extends AbstractDispatcherServletInitializer { // 加载SpringMVC容器的配置 @Override protected WebApplicationContext createServletApplicationContext() { AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); ctx.register(SpringMVCConfig.class); return ctx; } //设置那些请求交给SpringMVC处理 @Override protected String[] getServletMappings() { return new String[]{"/"}; } // 加载Spring配置 @Override protected WebApplicationContext createRootApplicationContext() { AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); ctx.register(SpringConfig.class); ctx.registerShutdownHook(); return ctx; } }
-
MultipartResolverConfig
package config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.PropertySource; import org.springframework.core.io.FileSystemResource; import org.springframework.web.multipart.MultipartResolver; import org.springframework.web.multipart.commons.CommonsMultipartResolver; import java.io.IOException; @PropertySource("classpath:MultipartResolver.yml") public class MultipartResolverConfig { @Value("${defaultEncoding}") private String defaultEncoding; @Value("${maxUploadSize}") private long maxUploadSize; @Value("${maxInMemorySize}") private int maxInMemorySize; @Value("${uploadTempDir}") private String uploadTempDir; @Value("${resolveLazily}") private boolean resolveLazily; @Bean public MultipartResolver multipartResolver() throws IOException { CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver(); commonsMultipartResolver.setDefaultEncoding(defaultEncoding); commonsMultipartResolver.setMaxUploadSize(maxUploadSize); commonsMultipartResolver.setMaxInMemorySize(maxInMemorySize); commonsMultipartResolver.setUploadTempDir(new FileSystemResource(uploadTempDir)); commonsMultipartResolver.setResolveLazily(resolveLazily); return commonsMultipartResolver; } }
dao
定义接口,并通过注解或mapper映射文件实现sql语句
此层不需要在SpringConfig
中扫描或在类上添加@Repository
注解,org.apache.ibatis.binding
下的MapperProxy<T>
会自动代理该接口,并绑定为bean交给Spring容器
service
分为接口与实现类正常编写即可
对应dao
层对象通过构造方法进行依赖注入
controller
@Controller
public class UserController {
private UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@RequestMapping("/save")
@ResponseBody
public String save() {
List<User> users = userService.selectAll();
System.out.println(users);
return "save";
}
}
执行
引入ServletContainerInitConfig
之前按照如下方式获取bean并执行
获取容器,再通过容器获取bean,执行业务
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
ctx.registerShutdownHook();
引入之后,请求交给AnnotationConfigWebApplicationContext
容器,在容器内部寻找处理对应请求的bean(controller),不再需要手动获取bean。并且通过Spring的依赖注入,即可调用service、dao层的相应方法来完成响应
项目异常处理
业务异常
类型:
- 规范的用户行为产生的异常
- 不规范的用户行为产生的异常
解决方案:
- 发送对应消息给用户,提醒规范操作
系统异常
类型:
- 项目运行过程中可预计且无法避免的异常
解决方案:
- 发送固定消息给用户,告知用户
- 发送特定消息给运维,提醒维护
- 记录日志
其他异常
类型:
- 开发人员未预期到的异常
解决方案:
-
发送固定消息给用户,告知用户
-
发送特定消息给运维,提醒维护
-
记录日志
放开MVC对页面的拦截
package config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Configuration
public class SpringMVCSupport extends WebMvcConfigurationSupport {
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/目录/**").addResourceLocations("/目录/");
}
}
将此配置类加载到SpringMVCConfig
中