Spring学习记录
知识点学习
1. Spring开发模式
工作流程:
- Spring MVC 将所有用户的请求都提交给 DispatcherServlet(前端控制器),该控制器过滤出哪些请求可以访问Servlet ,哪些不能访问;即 url-pattern 的作用,并且会加载 springmvc.xml 配置文件
- DispattcherServlet(前端控制器)查询一个或多个 HandlerMapping(处理器映射器),通过 HandlerMapping 完成 url 到 Controller 映射的组件,即将在 springmvc.xml 中配置的或者有注解的 url 与对应的处理类找到并进行存储,用Map<url,handler> 来存储(找到处理请求的 Controller )
- HandlerMapping (处理器映射器)有了映射关系,并且找到url对应的处理器,HandlerMapping (处理器映射器)就会将其处理器(Handler)返回,在返回前,会加上很多***
- DispatcherServlet(前端控制器) 拿到 Handler (处理器)后,找到 HandlerAdapter(处理器适配器),通过它来访问处理器,并执行处理器。
- 处理器会返回一个 ModelAndView 对象给 HandlerAdapter
- 通过 HandlerAdapter (处理器适配器)将 ModelAndView 对象返回给前端控制器 (DispatcherServlet)
- 前端控制器请求视图解析器 (ViewResolver) 去进行视图解析,根据逻辑视图名解析成真正的视图(jsp),其实就是将ModelAndView 对象中存放视图的名称进行查找,找到对应的页面形成视图对象
- 返回视图对象到前端控制器
- 视图渲染,就是将 ModelAndView 对象中的数据放到 request 域中,用来让页面加载数据的
2. AOP(面向切面编程)
概念
- 切面(Aspect) :官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”。
- 连接点(Joinpoint) :程序执行过程中的某一行为。
- 通知(Advice) :“切面”对于某个“连接点”所产生的动作。
- 切入点(Pointcut) :匹配连接点的断言,在AOP中通知和一个切入点表达式关联。
- 目标对象(Target Object) :被一个或者多个切面所通知的对象。
- AOP代理(AOP Proxy) 在Spring AOP中有两种代理方式,JDK动态代理和CGLIB代理
技术优势
- 将核心关注点与横切关注点完全隔离
- 与传统 OOP 相比,传统 OOP 编程是自顶向下的编写主业务逻辑,但往往需要参杂着一些与主业务逻辑无关或关系不大的逻辑,这就产生了横切性问题。AOP 能很好的隔离和管理这些与主业务逻辑关联不大的业务代码,使得代码的可读性和可维护性大大提高
- 降低模块之间的耦合度,使系统更容易扩展,更好的代码复用
技术解析
- 基于代理模式:选择过程对开发者透明.Spring内部自动选择
- JDK动态代理:只代理接口;当目标对象的实现类实现了接口的时候,Spring AOP通过其方式生成AOP代理类
- cglib动态代理:目标对象的实现类没有实现接口时,Spring AOP通过cglib动态代理方式生成AOP代理类
IOC(Inversion of Control 控制反转)
控制反转
- 反转对依赖的控制,把控制权从具体业务对象手中转交到平台或框架(IOC容器)中去
理论概括
- 通过引入IOC容器,利用依赖关系动态注入的方式,实现对象之间的解耦
- 将实现组件间关系从程序内部提到外部容器
- 容器在运行期将组件间的某种依赖关系动态注入组件中
- 降低各组件或对象之间的依赖性(相关性),各组件或对象之间更加"独立"
- 增强可维护性,非常便于进行单元测试
- 实现组件之间低耦合或无耦合
- 可复用性,可以实现把具有普遍性的常用组件独立出来,反复利用(面向对象的特征)
技术解析
- 反射(Reflection):通过类名(字符串方式)动态的生成对象,让对象在生成时才决定到底是那个对象(利用反射生成对象,效率会有降低,大概慢1~2倍)
- 工厂模式:
- IOC容器:BeanFactory(IOC容器的基本形式),ApplicationContext(IOC容器的高级表现形式)
IOC容器系列
主要容器系列:
- 实现BeanFactory接口的简单容器系列
- ApplicationContext应用上下文
XmlBeanFactory:最底层实现,只提供最基本的IoC容器的功能,读取以XML形式定义的BeanDefinition
//DefaultListableBeanFactory(基本的IoC容器)实现了ConfigurableBeanFactory接口
//而ConfigurableBeanFactory-->HierarchicalBeanFactory-->BeanFactroy
public class XmlBeanFactory extends DefaultListableBeanFactory {
//XmlBeanDefinitionReader的初始化
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
//启动从Resource中载入BeanDefinition的过程
this.reader.loadBeanDefinitions(resource);
}
}
BeanFactory: 具备双亲IoC容器的管理功能,简单IoC容器的基本功能
ApplicationContext: 具备BeanFactory IoC容器的基本功能;通过继承MessageSource,ResourceLoader,ApplicaitonEventPublisher这些接口,BeanFactory为其赋予了更高级的IoC容器特性
FactoryBean:能产生或者修饰对象生成的工厂Bean,他的实现与设计模式的工厂模式和修饰器模式类似
IoC容器的实现
IoC容器初始化
- BeanDefinition的Resource定位,BeanDefinition的载入和解析,BeanDefinition在IoC容器中的注册
- 在IoC容器中建立BeanDefinition数据映射,并无IoC容器对Bean依赖关系的注入
IoC容器的依赖注入
3. Spring Bean
生命周期
- 如果 Bean 类有实现 org.springframework.beans.factory.BeanFactoryAware 接口,工厂调用 setBeanFactory() 方法传入工厂自身
- 可以在 Bean 定义文件中使用 “init-method” 属性,执行到这个阶段时,就会执行 initBean() 方法
- 如果 Bean 类有实现 org.springframework.beans.factory.DisposableBean 接口,则执行他的 destroy() 方法
4. Spring 七大模块
1. Spring Core
- Core 封装包是框架的最基础部分,提供 IOC 和依赖注入
- 基础概念是BeanFactory ,它提供对 Factory 模式的经典实现来消除对程序性单例模式的需要,并真正地允许你从程序逻辑中分离出依赖关系和配置
2. Spring ORM
- ORM 封装包提供了常用的“对象/关系”映射 APIs 的集成层
- 其中包括 JPA 、JDO Hibernate 和 MyBatis
- 利用 ORM 封装包,可以混合使用所有 Spring 提供的特性进行“对象/关系”映射,如前边提到的简单声明性事务管理
3. Spring DAO
-
Spring 提供对 JDBC 的支持,对 JDBC 进行封装,允许 JDBC 使用 Spring 资源,并能统一管理 JDBC事务,并不对 JDBC 进行实现
- DAO (Data Access Object) 提供了JDBC 的抽象层
- DAO 可消除冗长的 JDBC 编码和解析数据库厂商特有的错误代码
- JDBC 封装包还提供了一种比编程性更好的声明性事务管理方法,不仅仅是实现了特定接口,而且对所有的 POJO (plain old Java objects)都适用
4. Spring Web
- Spring中的 Web 包提供了基础的针对Web开发的集成特性
- 例如多方文件上传
- 利用Servlet listeners进行IOC容器初始化
- 针对Web的ApplicationContext
- 当与WebWork或Struts一起使用Spring时,这个包使Spring可与其他框架结合。
5. Spring Web MVC
- Spring 中的 MVC 封装包提供了Web应用的 Model-View-Controller(MVC)实现
- Spring的MVC框架并不是仅仅提供一种传统的实现,它提供了一种清晰的分离模型,在领域模型代码和Web Form之间。并且,还可以借助Spring框架的其他特性
6. Spring Context
- 构建于 Core 封装包基础上的 Context 封装包,提供了一种框架式的对象访问方法,有点像 JNDI 注册器
- Context 封装包的特性得自于 Beans 封装包,并添加了对国际化(I18N)的支持(例如资源绑定),事件传播,资源装载的方式和 Context 的透明创建,比如说通过Servlet容器。
- 给Spring提供一个运行时环境,用以保存各个对象的状态
7. Spring AOP
Spring 的 AOP 封装包提供了符合 AOP Alliance 规范的面向切面的编程实现
让你可以定义:
- 例如方法拦截器(method-interceptors)和切点(pointcuts)
- 从逻辑上讲,减弱代码的功能耦合,从而清晰的被分离开
- 而且,利用 source-level 的元数据功能,还可以将各种行为信息合并到你的代码中
源码解读
1. 注解
1. @Resource
用法与 @Autowired 用法类似,用做依赖注入,从容器中自动获取 Bean
- 在启动 spring 的时候,首先要启动容器;
- 启动 spring 容器时,会默认寻找容器扫描范围内的可加载 bean,然后查找哪些bean上的属性和方法上有 @Resource 注解;
- 找到 @Resource 注解后,判断@Resource注解括号中的 name 属性是否为空,如果为空:看 spring 容器中的 bean 的 id 与 @Resource 要注解的那个变量属性名是否相同,如相同,匹配成功;如果不相同,看 spring 容器中 bean 的 id 对应的类型是否与 @Resource 要注解的那个变量属性对应的类型是否相等,若相等,匹配成功,若不相等,匹配失败。
- 如果 @Resource 注解括号中的 name 属性不为空,看 name 的属性值和容器中的 bean 的 id 名是否相等,如相等,则匹配成功;如不相等,则匹配失败。
@Resource 与 @Autowired
共同点
- @Resource 和 @Autowired 都可以作为注入属性的修饰,在接口仅有单一实现类时,两个注解的修饰效果相同,可以互相替换,不影响使用
不同点
- @Resource 是 Java 自己的注解,@Resource 有两个属性是比较重要的,分是 name 和 type ;Spring 将 @Resource 注解的 name 属性解析为 bean 的名字,而 type 属性则解析为 bean 的类型。所以如果使用 name 属性,则使用 byName 的自动注入策略,而使用 type 属性时则使用 byType 自动注入策略。如果既不指定 name 也不指定 type 属性,这时将通过反射机制使用 byName 自动注入策略。
- @Autowired 是 Spring 的注解,是 Spring2.5 版本引入的,@Autowired 只根据 type 进行注入,不会去匹配 name。如果涉及到 type 无法辨别注入对象时,那需要依赖 @Qualifier 或 @Primary 注解一起来修饰
有关注解 @Resource 和 @Autowired 的具体区别,在另一篇博客中通过一个springboot的demo解释
2. @RestController
该注解包含一下内容:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
String value() default "";
}
-
@ResponseBody:将返回的数据结构转换为JSON格式,@RestController 可以看作是 @Controller 和 @ResponseBody 的结合体
-
@Controller 主要用于在前后端不分离时,下面例子:
//这里其实是想要返回到 user.html 页面 public String getUser() { return "user"; }如果使用 @RestController ,则会直接把 “user” 当作字符串返回给前端;
而 @Controller 会返回到具体的页面中;
前后端分离:不需要模板渲染(Thymeleaf),直接用 @ResponseBody 将数据以 JSON 格式传给前端
前后端不分离:需要模板渲染,一般在 Controller 中都会返回到具体的页面
3. @RequestMapping
-
作用:用来请求地址映射的注解
-
范围:类(将一个特定请求或者请求模式映射到一个控制器上)和方法(指定到处理方法的映射关系)
-
属性:name,path,params,headers,condumes(5个不常用),produces,value,method(3个常用)
- value 属性:指定请求的实际地址,可以省略不写
- method 属性:指定请求类型,主要有 GET,PUT,POST,DELETE,默认为 GET
- produces 属性:指定返回内容类型
@RestController @RequestMapping(value = "/test",produces = "application/json; charset=UTF-8") public class TestController { @RequestMapping(value = "/get", method = RequestMethod.GET) //@GetMapping("/get") public String testGet(){ return "success"; } }
4. @PathVariable
- 作用:主要用来获取 URL 参数
“Spring Boot 支持 Restful 风格的 URL,GET请求携带一个 id ,作为参数接收,可使用 @PathVariable
@GetMapping("/user/{id}")
public String testPathVariable(@PathVariable Integer id) {
System.out.println(id);
return "success";
}
URL 中的占位符名称(参数)需要与方法接收参数名称一致,否则无法接收到,需要用注解的value 属性指定关系
@GetMapping("/user/{idd}")
public String testPathVariable(@PathVariable(value = "idd") Integer id) {
System.out.println(id);
return "success";
}
- 占位符的位置不固定,支持多个占位符,跟一个占位符的原理是一样的
5. @RequestParam
- 作用:也是获取请求参数的,
- @RequestParam 和 @PathVariable 的区别
- @PathValiable 是从 URL 模板中获取参数值, 即这种风格的 URL:http://localhost:8080/user/{id}
- 而 @RequestParam 是从 Request 里获取参数值,即这种风格的 URL:http://localhost:8080/user?id=1
@GetMapping("/user")
public String testRequestParam(@RequestParam Integer id) {
System.out.println(id);
return "success";
}
可以正常从控制台打印出 id 。同样地,URL 上面的参数和方法的参数需要一致,如果不一致,也需要使用 value 属性来说明,比如 URL 为:http://localhost:8080/user?idd=1。
@RequestMapping("/user")
public String testRequestParam(@RequestParam(value = "idd", required = false) Integer id) {
System.out.println("获取到的id为:" + id);
return "success";
}
除了 value 属性外,还有两个属性比较常用:
- required 属性:true 表示该参数必须要传,否则就会报 404 错误,false 表示可有可无。
- defaultValue 属性:默认值,表示请求中没有同名参数时的默认值
从 URL 中可以看出,@RequestParam 注解用于 GET 请求上时,接收拼接在 URL 中的参数。除此之外,该注解还可以用于 POST 请求,接收前端表单提交的参数,假如前端通过表单提交 username 和 password 两个参数,那我们可以使用 @RequestParam 来接收,用法和上面一样
@PostMapping("/form1")
public String testForm(@RequestParam String username, @RequestParam String password) {
System.out.println("获取到的username为:" + username);
System.out.println("获取到的password为:" + password);
return "success";
}
6. RequestBody
- 作用:用于接受前端传来的实体(JSON参数),接收参数也是对应的实体
public class User {
private String username;
private String password;
}
@PostMapping("/user")
public String testRequestBody(@RequestBody User user) {
System.out.println(user.getUsername());
Systen.out.println(user.getPassword());
return "success";
}
7. @ControllerAdvice
- 作用:用于处理系统异常
包含:
- @Component 注解:把该类作为组件交给Spring管理
- basePackages 属性:指定拦截那个包中的异常信息,不指定,则拦截项目中的所有异常
使用:
- 配合@ResponseBody 注解:处理完异常之后给调用方输出一个JSON 格式的封装数据
- 在方法上通过@ExceptionHandler 注解来指定具体的异常(例如:参数缺失异常),然后在方法中处理该异常信息,最后将结果通过统一的JSON 结构体返回给调用者
/**
* 缺少请求参数
* @param ex HttpMessageNotReadableException
* @return
*/
@Slf4j
@ExceptionHandler(MissingServletRequestParameterException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public JsonResult handleHttpMessageNotReadableException(
MissingServletRequestParameterException ex) {
log.error("缺少请求参数,{}", ex.getMessage());
return new JsonResult("400", "缺少必要的请求参数");
}
8. @Aspect
- 用来描述一个切面类,定义切面类是需要引入这个注解
- 配合@Component 注解:把该类交给 Spring 统一管理
@Pointcut:
- 用来定义一个切面(切入点),即上下文所关注的某件事情的入口
- 指定一个切面,定义需要拦截的内容
- 常用的表达式:execution(),annotation()
切入点决定了连接点关注的内容,使得可以控制通知什么时候执行
/**
* 定义一个切面,拦截 com.serendipity.templet 包和自爆下所有的方法
*/
@Pointcut("execution(* com.serendipity.templet.controller..*.*(..)")
public void pointCut() {}
execution(* com.serendipity.templet.controller .. * . * ( .. )
第一个 * 号的位置:表示返回值类型,* 表示所有类型
包名:表示需要拦截的包名,后面两个点表示当前包和当前包的所有子包
第二个 * 号的位置:表示类名,* 表示所有类
*( .. ):这个星号表示方法名,*表示所有的方法,后面括号里面表示方法的参数,两个点表示任何参数
annotation() 方式是针对某个注解来定义切面,比如我们对具有 @GetMapping 注解的方法做切面,可以如下定义切面:
//切入注解 @GetMapping 的方法
@Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
public void annotationCut(){}
@Before
- 指定的方法在切面切入目标方法之前执行
- 可以做一些 Log 处理,也可以做一些信息的统计,比如获取用户的请求 URL 以及用户的 IP 地址
@Aspect
@Component
public class LogAspectHandler {
private
@Before("pointCut()")
public void doBefore(JoinPoint joinPoint) {
log.info("====doBefore方法进入了====");
//获取签名
Signature signature = joinPoint.getSignature();
//获取切入的包名
String declaringTypeName = signature.getDeclaringTypeName();
//获取即将执行的方法名
String funcName = signature.getName();
log.info("即将执行的方法为: {},属于{}包 ", funcName, declaringTypeName);
//也可以用来记录一些信息,比如获取请求的 URL 和 IP
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//获取请求 URL
String url = request.getRequestURL().toString();
//获取请求 IP
String ip = request.getRemoteAddr();
log.info("用户请求的url为: {},ip地址为: {}", url, ip);
}
}
JoinPoint 对象
- 可以用来获取一个签名,利用签名可以获取请求的包名,方法名,包括参数(通过 joinPoint.getArgs() 获取)
@After
- 与 @Before 注解相对应,指定的方法在切面切入目标方法之后执行
- 可以做一些完成某方法之后的 Log 处理
@After("pointCut()")
public void doAfter(JoinPoint joinPoint) {
log.info("==== doAfter 方法进入了====");
Signature signature = joinPoint.getSignature();
String method = signature.getName();
log.info("方法{}已经执行完", method);
}
@AfterReturning
- 与 @After 注解有些类似,区别在于 @AfterReturning 注解可以用来捕获切入方法执行完之后的返回值,对返回值进行业务逻辑上的增强处理
/**
* 在上面定义的切面方法返回后执行该方法,可以捕获返回对象或者对返回对象进行增强
*
* @param joinPoint
* @param result
*/
@AfterReturning(pointcut = "pointCut()", returning = "result")
public void doAfterReturning(JoinPoint joinPoint, Object result) {
Signature signature = joinPoint.getSignature();
String classMethod = signature.getName();
log.info("方法{}执行完毕,返回参数为: {}", classMethod, result);
log.info("对返回参数进行业务上的增强: {}", result + "增强版");
}
@AfterThrowing
- 当被切方法执行过程中抛出异常时,会进入 @AfterThrowing 注解的方法中执行
- 可以用来做一些异常的处理逻辑
/**
* 在上面定义的切面方法执行抛出异常时,执行该方法
* @param joinPoint
* @param ex
*/
@AfterThrowing(pointcut = "pointCut()", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
Signature signature = joinPoint.getSignature();
String method = signature.getName();
//处理异常的逻辑
log.info("执行方法{}出错,异常为: {}", method, ex);
}
8. MyBatis 相关注解
@Results
- 解决实体类名称与数据库表字段名称不对应
dao层:
@Select("select * from user where id = #{id}")
@Results({
@Result(property = "username", column = "user_name"),
@Result(property = "password", column = "password")
})
User getUser(Long id);
@ResultMap
- 作用与上面相似,更加常见
- 前提是:定义了UserMapper.xml
dao层:
@Select("select * from user where id = #{id}")
@ResultMap("BaseResultMap")
User getUser(Long id);
UserMapper.xml:
<resultMap id="BaseResultMap" type="com.serendipity.templet.entity.user">


