Spring学习记录

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">

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,一毛也是爱

打开微信或者支付宝扫一扫,即可进行扫码打赏哦