Spring第一讲:谈谈你对Spring的理解? 深入解析Spring框架

摘要:面试时常常被面试官问到这个问题,题目太大了,我会从Spring架构/bean初始化流程/bean生命周期/设计模式这几个方面来回应面试官。Spring框架中的核心技术:控制反转/依赖注入/面向切面编程/Spring的声明式事务/以及Spring生态相关产品的简介,这篇文章会详细说明。

文章目录

  • Spring第一讲:谈谈你对Spring的理解? 深入解析Spring框架
    • 推荐资料
    • 0、为什么学习 Spring?
    • 1、什么是Spring? (在servlet3.0中引入了异步处理,在3.1中引入了非阻塞IO进一步增强性能)
      • 1.1、Spring的定义
      • 1.2、Spring是如何简化开发的?
      • 1.3、Spring编程模型
    • 2、Spring容器的主要核心是:
      • 2.1、控制反转(IOC) ? 20181123
        • 2.1.1、什么是控制反转?
        • 2.1.2、实例化一个Java对象的三种方式:
        • 2.1.3、Spring框架中IOC的原理是什么?及其实现方案?20181222
      • 2.2、依赖注入(DI是IOC的基础)的原理
        • 2.2.1、什么是依赖注入?
        • 2.2.2、Spring的依赖注入有哪几种方式
        • 2.2.3、如何手写一个DI容器
      • 2.3、面向切面编程(AOP)
        • 2.3.1、什么是AOP?
        • 2.3.2、AOP解决了什么问题?
        • 2.3.3、AOP实现原理是什么?
        • 2.3.4、AOP术语
        • 2.3.5、代码示例
        • 2.3.6、在项目中使用AOP的地方
      • 2.4、BeanFactory与AppliacationContext有什么区别(对象工厂)?
      • 2.5、Spring按类型自动装配注入数组、集合、Map 20210306补
      • 2.6、Spring 使用@Autowired注入与使用构造函数注入区别
    • 3、Spring中Bean的作用域和生命周期? **好问题** 还有一种提问的方法:spring bean的回收过程/applicationContext的初始化机制? 美团
    • 4、Spring是如何获取对象的?springBean加载流程? 如何解决循环依赖问题 20181025有赞
      • 4.1、Spring是如何获取对象的?
      • 4.2、Spring 怎么解决循环依赖的问题?
      • 4.3、Spring 能解决构造函数循环依赖吗?
      • 4.4、Spring三级缓存
    • 6、Spring/Springboot的常用注解?
      • 6.1、前置知识点
      • 6.2、最关键的注解
      • 6.3、常用的注解 - Spring Bean 相关
      • 6.4、读取配置信息
      • 6.5、参数校验
      • 6.6、全局处理 Controller 层异常
      • 6.7、事务 @Transactional
      • 6.8、json 数据处理
      • 6.9、测试相关
      • 6.10、自定义Spring注解
    • 7、Spring结构图 (6大模块)及源码分析
      • 7.1 Spring结构图 图片来源于《Spring源码深度解析》
      • 7.2 Spring的结构组成
    • 8、Spring中BeanFactory和FactoryBean有什么区别?
    • 9、Spring的事件(Bean与Bean之间消息通信)
    • 10、Spring高级话题(aware/多线程/计划任务/条件注解/组合注解/Enable原理)
      • 10.1 如何使用restTemplate来提交get请求或post请求?如何调用开放平台接口
      • 10.2、@PostConstruct 修饰的方法里用到了其他 bean 实例,会有问题吗
      • 10.3、bean 的 init-method 属性指定的方法里用到了其他 bean 实例,会有问题吗
      • 10.4、要在 Spring IoC 容器构建完毕之后执行一些逻辑,怎么实现?
      • 10.5、Spring 中的常见扩展点有哪些(5分)
    • 11、框架spring (全家桶) springMVC(RESTful) mybatis struts2 hibernate 整合问题
    • 12、 参数绑定(从请求中接收参数) 重点
    • 13、 SpringMVC或Struts处理请求流程区别?
    • 14、tomcat和Spring是怎么交互的?20181025有赞
    • 15、Spring各jar包的作用? (20181104)

推荐资料

帮助解决别人的Spring问题是学习Spring的最好方法

0、为什么学习 Spring?

  • 首先:Spring 源码设计精妙、结构清晰,对 Java 设计模式灵活运用,是学习Java技术的最佳实践范例。
  • 其次:使用面广,说起Spring,绝对是Java开发领域的佼佼者,试问,做Java开发的有谁不知道Spring?做Java开发的又有谁没用过Spring?又有哪家公司在Java Web项目中没使用过Spring?所以,如果你选择了Java开发这条不归路,你就必须牢牢掌握Spring!

1、什么是Spring? (在servlet3.0中引入了异步处理,在3.1中引入了非阻塞IO进一步增强性能)

1.1、Spring的定义

Spring是一个轻量级的开源 JavaEE 框架,可以解决企业级应用开发的复杂性而生。Spring可以使得简单的JavaBean实现以前只有EJB(Enterprise Java Beans)才能实现的功能。Spring是一个IOC和AOP容器框架(IOC:控制反转,把创建对象过程交给Spring进行管理,AOP:面向切面,不修改源代码进行功能增强)

  • Spring Framework 是最基础、最底层的一部分。它提供了最基础、最核心的 IOC 和 AOP 功能。当然,它包含的功能还不仅如此,还有其他比如事务管理(Transactions)、MVC 框架(Spring MVC)等很多功能,如下表格所示:

我司使用的SpringBoot版本 2.0.3,其依赖的Spring版本为5.0.7.RELEASE

1.2、Spring是如何简化开发的?

  • 基于POJO的轻量级和最小侵入性编程;
  • 通过依赖注入和面向接口实现松耦合;
  • 基于切面和惯例进行声明式编程;
  • 通过切面和模板减少样版式代码

1.3、Spring编程模型

  • 面向对象编程 契约接口
  • 面向切面编程
    • 动态代理
    • 字节码提升
  • 面向元编程
    • 配置元信息
    • 注解 java 5
    • 属性配置
  • 面向模块编程
    • Maven Artifacts
    • Java9 Automatic Modules
    • Spring @Enable* 注解
  • 面向函数编程
    • Lambda 较少支持
    • Reactive 异步非阻塞

2、Spring容器的主要核心是:

2.1、控制反转(IOC) ? 20181123

2.1.1、什么是控制反转?
  • 传统的java开发模式中,当需要一个对象时,我们会自己使用new或者getInstance等直接或者间接调用构造方法来创建一个对象。而在Spring开发模式中,Spring容器使用了工厂模式为我们创建了所需要的对象,不需要我们自己创建了,直接调用Spring提供的对象就可以了,这是控制反转的思想。

对象的创建对象之间的调用过程,交给Spring进行管理

2.1.2、实例化一个Java对象的三种方式:
  • 1、使用类构造器
  • 2、使用静态工厂方法
  • 3、使用实例工厂方法
2.1.3、Spring框架中IOC的原理是什么?及其实现方案?20181222
  • 控制反转 控制权由对象本身转向Spring容器,由容器根据配置文件去创建实例并创建各个实例之间的依赖关系
  • 核心
    • 1、bean工厂解耦合**;在Spring中,bean工厂创建的各个实例称作bean init-method,intilizingbean接口方法afterPropertiesSet的先后顺序,即由容器动态的将某个依赖关系注入到组件之中,对象只是被动的接受依赖对象,所以是反转。
    • 2、xml解析 解析xml文件进一步降低耦合度
    • 3、反射 通过反射创建对象

2.2、依赖注入(DI是IOC的基础)的原理

2.2.1、什么是依赖注入?
  • Spring使用javaBean对象的set方法或者带参数的构造方法为我们在创建所需对象时,将其属性自动设置所需要的值的过程,就是依赖注入的思想
  • DI:组件之间依赖关系由容器在运行期决定(依赖注入是控制反转的基础)
2.2.2、Spring的依赖注入有哪几种方式
  • 1、构造器注入 带参数的构造函数
    前置条件:对象中使用有参构造方法来注入属性
<!--id属性:唯一标识    class属性:类全路径(包括类路径)-->
<bean id ="orders" class ="cn.com.zcy.User"><!-- name 类里面的属性名称  value:向属性注入的值--><constructor-arg name = "name"  value = "电脑"></constructor-arg><constructor-arg name = "address" value = "apple"></constructor-arg>
</bean>
  • 2、Setter方法属性注入 通过JavaBean属性注入依赖关系 (或者Autwired方法)
    前置条件:对象中使用setter方法来注入属性
<!--id属性:唯一标识    class属性:类全路径(包括类路径)-->
<bean id ="user" class ="cn.com.zcy.User"><!-- name 类里面的属性名称  value:向属性注入的值--><property name ="age"  value = "27"></property><property name = "author" value = "qiwenjie"></property>
</bean>
  • 3、接口注入 需要额外接口类,不提倡
2.2.3、如何手写一个DI容器

如何手写一个DI容器

  • 1、先准备一个基本的容器对象,包含一些Map结构集合,用来方便后续过程中存储具体的对象;
  • 2、进行配置文件的读取工作或者注解的解析工作,将需要创建的bean对象都封装成BeanDefinition对象存储在容器中;
  • 3、容器将封装好的BeanDefinition对象通过反射的方式进行实例化,完成对象的实例化工作;
  • 4、进行对象的初始化操作,也就是给类中的对应属性值设值,也就是进行依赖注入,完成整个对象的创建,变成一个完整的bean对象,存储在容器的某个map结构中;
  • 5、通过容器对象来获取对象,进行对象的获取和逻辑处理工作;
  • 6、提供销毁操作,当对象不用或者容器关闭时,将无用的对象进行销毁。

2.3、面向切面编程(AOP)

2.3.1、什么是AOP?
  • 在面向对象编程(OOP)思想中,我们将事物纵向抽成一个个的对象。而在面向切面编程中,我们将各个对象某些类似的方面横向抽成一个切面,对这个切面进行一些如权限控制、事物管理,缓存处理,记录日志等公用操作处理的过程就是面向切面编程的思想。
  • 目的:减少系统重复代码, 降低模块间耦合度,提高可拓展性和可维护性

面向切面,在不修改源代码情况下进行功能增强

2.3.2、AOP解决了什么问题?
  • 1、它让我们将业务逻辑从应用服务(如事务管理)中分离出来,应用对象只关注业务逻辑,不再负责其它系统问题(如日志事务等);
  • 2、从Spring的角度看,AOP最大的用途就在于提供了事务管理的能力;
    • Spring在你访问数据库之前,自动帮你开启事务,当你访问数据库结束之后,自动帮你提交/回滚事务。
2.3.3、AOP实现原理是什么?
  • 方式1:JDK的动态代理(默认使用):针对实现了接口的类产生代理,运行时动态生成被调用类型的子类;

    • 如果要代理的对象, 实现了某个接口, 那么 Spring AOP 会使用 JDK Proxy, 去创建代理对象,原理如下图所示
    • 核心的两个类是InvocationHandlerProxy,优势是最小化依赖关系,减少依赖意味着简化开发和维护,JDK本身的支持,可能比CGLIB更加可靠
  • 方式2:CGLIB:针对没有实现接口的类产生代理,采用的是底层的字节码增强技术,在运行期间生成当前类的子类对象,底层是依靠ASM(开源的java字节码编辑类库)操作字节码实现的,性能比JDK强

    • 没有实现接口的对象, 就无法使用 JDK Proxy 去进行代理了, 这时候 Spring AOP 会
      使用 Cglib 生成一个被代理对象的子类来作为代理
2.3.4、AOP术语

1、连接点 joinPoint

  • 类里面的那些方法可以被增强,这些方法称为连接点
  • 比如方法的调用、异常的抛出等

2、切入点 PointCut

  • 实际被真正增强的方法,称为切入点
  • 是JoinPoint的集合,是程序中需要注入Advice的位置的集合,指明Advice要在什么样的条件下才能被触发,在程序中主要体现为书写切入点表达式
//Pointcut表示式
@Pointcut("execution(* com.savage.aop.MessageSender.*(..))")
  • 由下列方式来定义或者通过 &&、 ||、 !、 的方式进行组合:

    • execution:用于匹配方法执行的连接点;
    • within:用于匹配指定类型内的方法执行;
    • this:用于匹配当前AOP代理对象类型的执行方法;
      • 注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;
    • target:用于匹配当前目标对象类型的执行方法;
      • 注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;
    • args:用于匹配当前执行的方法传入的参数为指定类型的执行方法;
    • @within:用于匹配所以持有指定注解类型内的方法;
    • @target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解
    • @args:用于匹配当前执行的方法传入的参数持有指定注解的执行;
    • @annotation:用于匹配当前执行方法持有指定注解的方法;
  • 格式
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
  • 其中后面跟着“?”的是可选项括号中各个pattern分别表示:

    • 修饰符匹配(modifier-pattern?)
    • 返回值匹配(ret-type-pattern): 可以为*表示任何返回值, 全路径的类名等
    • 类路径匹配(declaring-type-pattern?)
    • 方法名匹配(name-pattern):可以指定方法名 或者 代表所有, set 代表以set开头的所有方法
    • 参数匹配((param-pattern)):可以指定具体的参数类型,多个参数间用“,”隔开,各个参数也可以用"" 来表示匹配任意类型的参数,"…"表示零个或多个任意参数。
      • 如(String)表示匹配一个String参数的方法;(,String) 表示匹配有两个参数的方法,第一个参数可以是任意类型,而第二个参数是String类型。
    • 异常类型匹配(throws-pattern?)
  • eg.
    • 任意公共方法的执行:execution(public * *(…))
    • 任何一个以“set”开始的方法的执行:execution(* set*(…))
    • AccountService 接口的任意方法的执行:execution(* com.xyz.service.AccountService.*(…))
    • 定义在service包里的任意方法的执行: execution(* com.xyz.service…(…))
    • 定义在service包和所有子包里的任意类的任意方法的执行:execution(* com.xyz.service….(…))
      • 第一个表示匹配任意的方法返回值, …(两个点)表示零个或多个,第一个…表示service包及其子包,第二个表示所有类, 第三个*表示所有方法,第二个…表示方法的任意参数个数
    • 定义在pointcutexp包和所有子包里的JoinPointObjP2类的任意方法的执行:execution(* com.test.spring.aop.pointcutexp…JoinPointObjP2.*(…))")
    • pointcutexp包里的任意类: within(com.test.spring.aop.pointcutexp.*)
    • pointcutexp包和所有子包里的任意类:within(com.test.spring.aop.pointcutexp…*)
    • 实现了Intf接口的所有类,如果Intf不是接口,限定Intf单个类this(com.test.spring.aop.pointcutexp.Intf)
    • 当一个实现了接口的类被AOP的时候,用getBean方法必须cast为接口类型,不能为该类的类型
    • 带有@Transactional标注的所有类的任意方法:
      • @within(org.springframework.transaction.annotation.Transactional)
      • @target(org.springframework.transaction.annotation.Transactional)
    • 带有@Transactional标注的任意方法:@annotation(org.springframework.transaction.annotation.Transactional)
      • @within和@target针对类的注解,@annotation是针对方法的注解
    • 参数带有@Transactional标注的方法:@args(org.springframework.transaction.annotation.Transactional)
    • 参数为String类型(运行是决定)的方法: args(String)

3、通知(增强) Advise

  • 实际增强的逻辑部分被称为通知(增强) ,AOP在特定的切入点上执行的增强处理
  • 通知有多种类型 前置通知 后置通知 环绕通知 异常通知 最终通知(finally)
    • @Before:标识一个前置增强方法,相当于BeforeAdvice的功能
    • @After:final增强,不管是抛出异常或者正常退出都会执行
    • @AfterReturning:后置增强,似于AfterReturningAdvice, 方法正常退出时执行
    • @AfterThrowing:异常抛出增强,相当于ThrowsAdvice
    • @Around:环绕增强,相当于MethodInterceptor

4、切面 Aspect

  • 是动作
  • 把通知应用到切入点过程

5、Advisor(增强):

  • 是PointCut和Advice的综合体,完整描述了一个 advice 将会在 pointcut 所定义的位置被触发
2.3.5、代码示例

①JDK动态代理:1)使用 JDK 动态代理,使用 Proxy 类里面的方法创建代理对象
调用 newProxyInstance 方法,方法有三个参数:

/*
* 第一参数,类加载器
*​ 第二参数,增强方法所在的类,这个类实现的接口,支持多个接口
*​ 第三参数,实现这个接口 InvocationHandler,创建代理对象,写增强的部分
*/
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)

编写 JDK 动态代理代码Demo

  • (1)创建接口,定义方法
public interface UserDao {public int add(int a,int b);public String update(String id);
}
  • (2)创建接口实现类,实现方法
public class UserDaoImpl implements UserDao {@Overridepublic int add(int a, int b) {return a+b;}@Overridepublic String update(String id) {return id;}
}
  • (3)使用 Proxy 类创建接口代理对象
public class JDKProxy {public static void main(String[] args) {//创建接口实现类代理对象Class[] interfaces = {UserDao.class};UserDaoImpl userDao = new UserDaoImpl(); /** 第一参数,类加载器 第二参数,增强方法所在的类,这个类实现的接口,(支持多个接口)第三参数,实现这个接口 InvocationHandler,创建代理对象,写增强的部分  */UserDao dao =(UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces,new UserDaoProxy(userDao));int result = dao.add(1, 2);System.out.println("result:"+result);}
}//创建代理对象代码
class UserDaoProxy implements InvocationHandler {//1 把创建的是谁的代理对象,把谁传递过来//有参数构造传递private Object obj;public UserDaoProxy(Object obj) {this.obj = obj;}//增强的逻辑@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//方法之前System.out.println("方法之前执行...." + method.getName()+" :传递的参数..."+ Arrays.toString(args));//被增强的方法执行Object res = method.invoke(obj, args);//方法之后System.out.println("方法之后执行...."+obj);return res;}
}
2.3.6、在项目中使用AOP的地方

1、在标准中心项目中,记录各业务线操作日志的需求中,采用了AOP方式来处理;

  • 见这个项目 项目实战第一讲:如何优雅地记录操作日志

2、在SPI提供的SDK中,提供了注解,底层通过切面在完成创建bean的操作

  • 见这个项目 项目实战第十六讲:使用开闭原则实现商品价格规则引擎

3、缓存dubbo调用结果的AfterImage,底层使用了切面

  • 见这个项目

4、清理各业务线缓存,使用的切面

  • 项目实战第二讲:高并发下如何保障缓存和数据库的一致性

5、阿里DRDS分布式事务注解,使用了切面,

  • 见这个项目

2.4、BeanFactory与AppliacationContext有什么区别(对象工厂)?

  • 在Spring中,所有管理的对象都是JavaBean对象,而BeanFactory和ApplicationContext就是Spring框架的两个IOC容器,现在一般使用ApplicationContext,其不单包含了BeanFactory的作用,同时还进行更多的扩展。

1、beanFactory

  • 基础类型的IOC容器,提供完整的IOC服务支持,如果没有特殊指定,默认采用延迟加载初始化策略。相对来说,容器启动初期速度较快,所需资源有限;
  • Spring内部使用的接口,不提供给开发人员进行使用
  • 加载配置文件时不会创建对象,在获取对象(使用)才会创建对象

2、AplicationContext

  • 是在beanFactory的基础上构建,是相对比较高级的容器实现,除了BeanFactory的所有支持外,AplicationContext还提供了时间发布,国际化支持等功能,applicationContext管理的对象,在容器启动后默认初始化并且绑定完成。
  • 加载配置文件时就会把配置文件对象进行创建

2.5、Spring按类型自动装配注入数组、集合、Map 20210306补

Spring按类型自动装配注入数组、集合、Map时,是把应用上下文中对应类型的bean装配进集合,而不是直接查找一个对应类型的集合然后注入。以下面这段代码为例

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;@Component("cdPlayers")
public class CDPlayers {@Autowiredprivate MediaPlayer[] mediaPlayerArr;@Autowiredprivate List<MediaPlayer> mediaPlayerList;@Autowiredprivate Map<String, MediaPlayer> mediaPlayerMap;@Autowiredpublic void printInfo() {System.out.println(Arrays.toString(mediaPlayerArr));System.out.println(mediaPlayerList);System.out.println(mediaPlayerMap);}
}

Spring会这样做:

  • 1、查找应用上下文里可赋给MediaPlayer类型的bean放进mediaPlayerArr数组,
  • 2、查找可赋给MediaPlayer类型的bean放进mediaPlayerList,
  • 3、查找可赋给MediaPlayer类型的bean put进mediaPlayerMap,key为bean的name。

这个过程的源码在DefaultListableBeanFactory的doResolveDependency方法中,具体如下

@Nullable
private Object resolveMultipleBeans(DependencyDescriptor descriptor, @Nullable String beanName,@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) {Class<?> type = descriptor.getDependencyType();if (type.isArray()) {Class<?> componentType = type.getComponentType();ResolvableType resolvableType = descriptor.getResolvableType();Class<?> resolvedArrayType = resolvableType.resolve();if (resolvedArrayType != null && resolvedArrayType != type) {type = resolvedArrayType;componentType = resolvableType.getComponentType().resolve();}if (componentType == null) {return null;}Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType,new MultiElementDescriptor(descriptor));if (matchingBeans.isEmpty()) {return null;}if (autowiredBeanNames != null) {autowiredBeanNames.addAll(matchingBeans.keySet());}TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());Object result = converter.convertIfNecessary(matchingBeans.values(), type);if (getDependencyComparator() != null && result instanceof Object[]) {Arrays.sort((Object[]) result, adaptDependencyComparator(matchingBeans));}return result;}else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {Class<?> elementType = descriptor.getResolvableType().asCollection().resolveGeneric();if (elementType == null) {return null;}Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType,new MultiElementDescriptor(descriptor));if (matchingBeans.isEmpty()) {return null;}if (autowiredBeanNames != null) {autowiredBeanNames.addAll(matchingBeans.keySet());}TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());Object result = converter.convertIfNecessary(matchingBeans.values(), type);if (getDependencyComparator() != null && result instanceof List) {((List<?>) result).sort(adaptDependencyComparator(matchingBeans));}return result;}else if (Map.class == type) {ResolvableType mapType = descriptor.getResolvableType().asMap();Class<?> keyType = mapType.resolveGeneric(0);if (String.class != keyType) {return null;}Class<?> valueType = mapType.resolveGeneric(1);if (valueType == null) {return null;}Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType,new MultiElementDescriptor(descriptor));if (matchingBeans.isEmpty()) {return null;}if (autowiredBeanNames != null) {autowiredBeanNames.addAll(matchingBeans.keySet());}return matchingBeans;}else {return null;}}

我们可以看到在resolveMultipleBeans方法中:

  • 如果是数组,则获取数组元素类型,查找匹配该类型的所有bean,返回一个这些bean的数组;
  • 如果该类可赋给Collection,并且是一个接口,则获取集合元素类型,查找匹配该类型的所有bean,返回一个这些bean的集合;
  • 如果该类型是Map(注意是type == Map.class),且key是String类型,则获取Map的value的类型,查找匹配该类型的所有bean,这是一个key为bean name、value为bean实例的一个Map,返回这个Map
  • 其他情况则是我们所熟知的按类型自动装配过程。

2.6、Spring 使用@Autowired注入与使用构造函数注入区别

  • 初始化时机不同,Spring先执行构造函数,然后执行@AutoWired注入的对象
  • 在日常编码中,可以认为没有区别

3、Spring中Bean的作用域和生命周期? 好问题 还有一种提问的方法:spring bean的回收过程/applicationContext的初始化机制? 美团

1、作用域:得分类讨论,bean常用的scope有两种:singleton(单例)和prototype(多例

  • 1、多例时:访问一个bean,就会开启一个线程,当访问结束,线程会关闭,bean的生命周期结束,即每次调用getBean()时,相当于执行new XxxBean();
  • 2、单例时:线程在访问之后,产生单一实例,对于多线程的查询,会产生存取共享资源引发的安全问题。不会被关闭;
  • 3、无状态的Bean适合用不变模式,技术就是单例模式,这样可以共享实例,提高性能。有状态的Bean,多线程环境下不安全,那么适合用Prototype原型模式。Prototype: 每次对bean的请求都会创建一个新的bean实例;
    • 有无状态指的是什么?有状态是指有数据存储的功能。无状态是指不会存储数据
  • 4、默认情况下,从Spring bean工厂所取得的实例为singleton(scope属性为singleton),容器只存在一个共享的bean实例
  • tips:具体案例可以看这篇博客,目前还没有看懂:https://blog.csdn.net/bingjing12345/article/details/9794945

如何选择?

  • 对有状态的bean(有状态可以简单理解为具有数据存储功能)应该使用prototype作用域,而对无状态的bean用该使用singleton作用域;
  • 如果是web容器,支持另外三种作用域(生产环境没使用过):
    • 1、request 为每个http请求创建单独的bean实例;
    • 2、session 作用域是session范围;
    • 3、global session,用于portlet容器,提供全局性的http session

2、生命周期:从对象创建到对象销毁的过程

  • bean定义:在配置文件里面用<bean id=“”, class =“”>来进行定义
  • bean初始化:有两种方式初始化:
    1.在配置文件中通过指定init-method属性来完成
    2.实现 org.springframwork.beans.factory.InitializingBean接口
  • bean调用:有三种方式可以得到bean实例,并进行调用
  • bean销毁:销毁有两种方式
    1.使用配置文件指定的destroy-method属性
    2.实现 org.springframwork.bean.factory.DisposeableBean接口

3、生命周期 其实不重要,因为这些接口我们并不会去实现

  • 通过配置< bean>标签上的init-method作为bean的初始化的时候执行的方法,destroy-method作为bean销毁时执行的方法,销毁方法想要执行,需要是单例创建的bean,而且在工厂关闭的时候,bean才会被销毁。

具体而言 10个步骤 重点

1、实例化bean对象: Spring容器从XML文件中读取Bean的定义,并实例化Bean。 (默认单例模式)
2、设置bean属性: Spring 将值和Bean的引用注入到对应的属性中。 (Spring对bean进行依赖注入)
下面是通过各种Aware接口声明依赖关系,注入bean对容器基础设施层面的依赖
3、如果Bean实现了BeanNameAware接口,Spring传递bean的ID到setBeanName()方法。
4、 如果Bean实现了BeanFactoryAware接口,Spring传递 beanfactory 给setBeanFactory方法。
5、如果bean实现了ApplicationContextAware接口,Spring将调用 setApplicationContext() 方法,将bean所在的应用上下文的引用传入进来;
Aware接口的作用:bean可以实现各种不同Aware的子接口,为容器以 callback 形式注入依赖对象提供了统一入口
6、如果bean实现了BeanPostProcessors接口,Spring会在postProcesserBeforeInitialization()方法内调用它们(前置初始化方法)。
7、如果bean实现了 InitializingBean接口 了,调用它的 afterPropertySet方法,//同样:如果bean声明了初始化方法init-method,调用此初始化方法。 在afterPropertySet方法里面,可以给bean的变量赋初始值 – 利用这点可以执行测试用例
8、 如果bean实现了BeanPostProcessors接口 ,postProcessAfterInitialization() 方法将被调用。
9、此时,bean已经准备就绪,可以被应用程序使用了,他们一直驻留在应用上下文中,直至该应用上下文被销毁。
10、如果 bean 实现了 DisposableBean,它将调用destroy()方法。//同样:如果bean使用destroy-method声明了销毁方法,该方法也会被被调用。

实现ApplicationContextAware 的 Demo

@Component
public class ApplicationContextUtil implements ApplicationContextAware {private static ApplicationContext context;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {context = applicationContext;}public static ApplicationContext getApplicationContext() {return context;}public static void clear() {context = null;}/*** 获取Bean* @param requiredType* @param <T>* @return*/public static <T> T getBean(Class<T> requiredType) {return (T)getApplicationContext().getBean(requiredType);}/*** @param name* @param <T>* @return*/public static <T> T getBean(String name) {return (T)getApplicationContext().getBean(name);}public static <T> T getBean(String name, Class<T> clazz) {return getApplicationContext().getBean(name, clazz);}
}

实现 InitializingBean接口的 Demo

@Data
@Component
public class LicecseConfig implements InitializingBean {@Value("${license.category.vaccine}")Long vaccineCategoryId;@Value("${license.audit-org.vaccine}")Long vaccineAuditOrgId;/*** 服务配置中心配置的  一级类目ID  和  审核机构ID  给枚举类赋值* @throws Exception*/@Overridepublic void afterPropertiesSet() throws Exception {LicenseCategoryEnum.VACCINE.setCategoryId(vaccineCategoryId);LicenseAuditOrgEnum.VACCINE.setOrgId(vaccineAuditOrgId);}
}

实现 DisposableBean接口的 demo

@Configuration
@SuppressWarnings("all")
public class EventBusConfiguration implements InitializingBean, DisposableBean, ApplicationContextAware {private ApplicationContext applicationContext;private final Map<String, EventAdapter> beans = Maps.newHashMap();@Overridepublic void afterPropertiesSet() throws Exception {Map<String, EventAdapter> initBeans = applicationContext.getBeansOfType(EventAdapter.class);if (!CollectionUtils.isEmpty(initBeans)) {initBeans.values().stream().forEach(EventBusSupport::register);beans.putAll(initBeans);}}@Overridepublic void destroy() throws Exception {if (!CollectionUtils.isEmpty(beans)) {beans.values().stream().forEach(EventBusSupport::unregister);}}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}
}

4、注意事项:

  • 有两个重要的 bean 生命周期方法,第一个是 setup(),它是在容器加载 bean 的时候被调用。第二个方法是 teardown() 它是在容器卸载类的时候被调用

5、把bean交给spring管理跟直接new的方式有什么好处?

  • 1、因为把对象生成放在了xml中定义,所以当我们需要换一个实现子类将非常容易,直接修改xml即可
  • 2、不需要了解实现类的创建方法。普通的new的方式需要了解实现类的构造

6、面试常问?

  • 1、编写完配置文件以后就可以直接使用bean了,Spring是如何做到的?

    • 首先扫描配置文件,将bean放入IOC容器中,当使用时直接从IOC中取就可以了
  • 2、配置文件扫描spring是如何做的?
    • 使用专门处理XML的库 是什么库?org.xml.sax
  • 3、什么是xml,使用xml的优缺点,xml的解析器有哪几种,分别有什么区别?
    • xml是一种可扩展性标记语言,支持自定义标签(使用前必须预定义)使用DTD和XML Schema标准化XML结构,优点是用于配置文件,格式统一,符合标准;用于在互不兼容的系统间交互数据,共享数据方便; 缺点是 xml文件格式复杂,数据传输占流量,服务端和客户端解析xml文件占用大量资源且不易维护。Xml常用解析器有2种,分别是:DOM和SAX; 主要区别在于它们解析xml文档的方式不同。使用DOM解析,XML文档以DOM 树形结构加载入内存,而SAX采用的是事件模型。(不是重点)

7、单实例bean注意的事项

  • 单例bean是整个应用共享的,所以需要考虑到线程安全问题,之前在玩springmvc的时候,springmvc中controller默认是单例的,有些开发者在controller中创建了一些变量,那么这些变量实际上就变成共享的了,controller可能会被很多线程同时访问,这些线程并发去修改controller中的共享变量,可能会出现数据错乱的问题;所以使用的时候需要特别注意。
  • 如果在bean中声明了任何有状态的实例变量或者类变量,推荐使用 ThreadLocal 把变量变成线程私有,如果bean的实例变量或类变量需要在多个线程之间共享,那么就只能使用 Synchronized,lock,cas等这些实现线程同步的方法 了。

8、多实例bean注意的事项

  • 多例bean每次获取的时候都会重新创建,如果这个bean比较复杂,创建时间比较长,会影响系统的性能,这个地方需要注意。

4、Spring是如何获取对象的?springBean加载流程? 如何解决循环依赖问题 20181025有赞

4.1、Spring是如何获取对象的?

1、单例时

  • 默认情况下,会在启动容器时实例化,也可以指定bean结点的lazy-init=“true”来延时初始化bean,此时,只有第一次获取bean时才会初始化bean
<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" lazy-init="true"/>

spring读取xml文件时,会创建对象,创建对象时先调用构造器,然后调用init-method属性值中所指定的方法,对象在被销毁的时候,会调用destroy-method属性值中所指定的方法。

2、多例时prototype

  • 在第一次请求该bean时才初始化,请求每一个prototype的bean时,Spring容器都会调用其构造器创建这个对象,然后调用init-method属性值中所指定的方法。当容器关闭时,destroy方法不会被调用。Spring不会对bean的整个生命周期负责,清除prototype作用域的对象并释放任何prototype bean所持有的昂贵资源,都是客户端代码的职责(让spring容器释放prototype作用域bean资源的可行方法是:通过使用bean的后置处理器,该处理器持有要被清除的bean的引用

4.2、Spring 怎么解决循环依赖的问题?

  • Spring 是通过提前暴露 bean 的引用来解决的,具体如下。

  • Spring 首先使用构造函数创建一个 “不完整” 的 bean 实例(之所以说不完整,是因为此时该 bean 实例还未初始化),并且提前曝光该 bean 实例的 ObjectFactory(提前曝光就是将 ObjectFactory 放到 singletonFactories 缓存).

  • 通过 ObjectFactory 我们可以拿到该 bean 实例的引用,如果出现循环引用,我们可以通过缓存中的 ObjectFactory 来拿到 bean 实例,从而避免出现循环引用导致的死循环。

  • 举个例子:A 依赖了 B,B 也依赖了 A,那么依赖注入过程如下。

    • 检查 A 是否在缓存中,发现不存在,进行实例化
    • 通过构造函数创建 bean A,并通过 ObjectFactory 提前曝光 bean A
    • A 走到属性填充阶段,发现依赖了 B,所以开始实例化 B。
    • 首先检查 B 是否在缓存中,发现不存在,进行实例化
    • 通过构造函数创建 bean B,并通过 ObjectFactory 曝光创建的 bean B
    • B 走到属性填充阶段,发现依赖了 A,所以开始实例化 A。
    • 检查 A 是否在缓存中,发现存在,拿到 A 对应的 ObjectFactory 来获得 bean A,并返回。
    • B 继续接下来的流程,直至创建完毕,然后返回 A 的创建流程,A 同样继续接下来的流程,直至创建完毕。
  • 这边通过缓存中的 ObjectFactory 拿到的 bean 实例,虽然拿到的是 “不完整” 的 bean 实例,但是由于是单例,所以后续初始化完成后,该 bean 实例的引用地址并不会变,所以最终我们看到的还是完整 bean 实例。

4.3、Spring 能解决构造函数循环依赖吗?

答案是不行的,对于使用构造函数注入产生的循环依赖,Spring 会直接抛异常

为什么无法解决构造函数循环依赖?

  • 上面解决逻辑的第一句话:“首先使用构造函数创建一个 “不完整” 的 bean 实例”,从这句话可以看出,构造函数循环依赖是无法解决的,因为当构造函数出现循环依赖,我们连 “不完整” 的 bean 实例都构建不出来。

4.4、Spring三级缓存

Spring 的三级缓存其实就是解决循环依赖时所用到的三个缓存。

  • singletonObjects:正常情况下的 bean 被创建完毕后会被放到该缓存,key:beanName,value:bean 实例。

  • singletonFactories:上面说的提前曝光的 ObjectFactory 就会被放到该缓存中,key:beanName,value:ObjectFactory。

  • earlySingletonObjects:该缓存用于存放 ObjectFactory 返回的 bean,也就是说对于一个 bean,ObjectFactory 只会被用一次,之后就通过 earlySingletonObjects 来获取,key:beanName,早期 bean 实例


6、Spring/Springboot的常用注解?

6.1、前置知识点

Spring在2.5版本以后开始支持注解的方式来配置依赖注入。可以用注解的方式来代替xml中bean的描述。
xml注入的方式示例:autowire属性常用两个值:byName根据属性名称注入,注入值bean的id值和类属性名称一样 byType根据属性类型注入(基本是不会使用该方法的)

<bean id="emp" class="cn.csdn.service.Emp" autowire= "byName"/>

注解注入将会被容器在XML注入之前被处理,所以后者会覆盖掉前者对于同一个属性的处理结果* (切记)注解装配在 Spring 中默认是关闭的。所以需要在 Spring 的核心配置文件中配置一下才能使用基于注解的装配模式。配置方式如下:

<context:annotation-config />
//配置完成后,就可以用注解的方式在 Spring 中向属性、方法和构造方法中自动装配变量

什么是注解?

  • 注解是代码特殊标记,格式:@注解名称(属性名称=属性值,属性名称=属性值)
  • 使用注解,注解作用在类上面,方法上面,属性上面
  • 使用注解目的:简化xml配置

开启注解扫描
1、如果扫描多个包,多个包使用逗号隔开
2、扫描包上层目录

<context:component-scan base-package= "cn.com.zcy.service"></context:component-scan>

6.2、最关键的注解

最关键的注解:

  • @SpringBootApplication
  • 这个注解是 Spring Boot 项目的基石,创建 SpringBoot 项目之后会默认在主类加上
@SpringBootApplication
public class SpringSecurityApplication {public static void main(java.lang.String[] args) {SpringApplication.run(SpringSecurityApplication.class, args);}
}

@SpringBootApplication //该注解是一个组合注解,组合了

  • @Configuration 允许在 Spring 上下文中注册额外的 bean 或导入其他配置类
  • @EnableAutoConfiguration 根据类路径中的jar包依赖为当前项目进行自动配置
  • @ComponentScan 扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描该类所在的包下所有的类

我们可以使用@ComponentScan注解来指定Spring扫描哪些包,可以使用excludeFilters()指定扫描时排除哪些组件,也可以使用includeFilters()指定扫描时只包含哪些组件。当使用includeFilters()指定只包含哪些组件时,需要禁用默认的过滤规则

package org.springframework.boot.autoconfigure;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {......
}package org.springframework.boot;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {}

6.3、常用的注解 - Spring Bean 相关

  1. @Autowired
    自动导入对象到类中,被注入进的类同样要被 Spring 容器管理。比如:Service 类注入到 Controller 类中。
@Service
public class UserService {......
}@RestController
@RequestMapping("/users")
public class UserController {@Autowiredprivate UserService userService;......
}

2、@Component,@Repository,@Service, @Controller

  • 我们一般使用 @Autowired 注解让 Spring 容器帮我们自动装配 bean。要想把类标识成可用于 @Autowired 注解自动装配的 bean 的类,可以采用以下注解实现:

  • @Component:通用的注解,可标注任意类为 Spring 组件。如果一个 Bean 不知道属于哪个层,可以使用@Component 注解标注。

  • @Repository : 对应持久层即 Dao 层,主要用于数据库相关操作。

  • @Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。

  • @Controller : 对应 Spring MVC 控制层,主要用于接受用户请求并调用 Service 层返回数据给前端页面。

3、@RestController

  • @RestController注解是@Controller和@ResponseBody的合集,表示这是个控制器 bean,并且是将函数的返回值直接填入 HTTP 响应体中,是 REST 风格的控制器。
  • 单独使用 @Controller 不加 @ResponseBody的话一般使用在要返回一个视图的情况,这种情况属于比较传统的 Spring MVC 的应用,对应于前后端不分离的情况。@Controller +@ResponseBody 返回 JSON 或 XML 形式数据
    关于@RestController 和 @Controller的对比,请看这篇文章:@RestController vs @Controller

4、@Scope
声明 Spring Bean 的作用域,使用方法:

@Bean
@Scope("singleton")
public Person personSingleton() {return new Person();
}

四种常见的 Spring Bean 的作用域:

  • singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。
  • prototype : 每次请求都会创建一个新的 bean 实例。
  • request : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。
  • session : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。

5、@Configuration
一般用来声明配置类,可以使用 @Component注解替代,不过使用@Configuration注解声明配置类更加语义化。

@Configuration
public class AppConfig {@Beanpublic TransferService transferService() {return new TransferServiceImpl();}
}

6.4、读取配置信息

  • 很多时候我们需要将一些常用的配置信息比如阿里云 oss、发送短信、微信认证的相关配置信息等等放到配置文件中。(我们公司使用的是apollo配置)
  • 下面我们来看一下 Spring 为我们提供了哪些方式帮助我们从配置文件中读取这些配置信息。

我们的数据源application.yml内容如下::

hangzhou2021: 2021快过年了
wade:name: qiwenjieiphone: 15868860041
library:location: 浙江杭州books:- name: java编程思想description: xxx。- name: 并发编程实战description: xxxx。- name: 深入理解Java虚拟机description: xxx

1、@value(常用)

  • 使用 @Value(“${property}”) 读取比较简单的配置信息:
@Value("${hangzhou2021}")
String hangzhou2021;

2、@ConfigurationProperties(常用)

  • 通过@ConfigurationProperties读取配置信息并与 bean 绑定。
  • 你可以像使用普通的 Spring bean 一样,将其注入到类中使用。
@Component
@ConfigurationProperties(prefix = "library")
class LibraryProperties {@NotEmptyprivate String location;private List<Book> books;@Setter@Getter@ToStringstatic class Book {String name;String description;}省略getter/setter......
}

3、PropertySource(不常用)

  • @PropertySource读取指定 properties 文件
@Component
@PropertySource("classpath:website.properties")class WebSite {@Value("${url}")private String url;省略getter/setter......
}

6.5、参数校验

即使在前端对数据进行校验的情况下,我们还是要对传入后端的数据再进行一遍校验,避免用户绕过浏览器直接通过一些 HTTP 工具直接向后端请求一些违法数据

  • JSR(Java Specification Requests) 是一套 JavaBean 参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注解加在我们 JavaBean 的属性上面,这样就可以在需要校验的时候进行校验了,非常方便!
  • 校验的时候我们实际用的是 Hibernate Validator 框架。Hibernate Validator 是 Hibernate 团队最初的数据校验框架,目前最新版的 Hibernate Validator 6.x 是 Bean Validation 2.0(JSR 380)的参考实现。
  • SpringBoot 项目的 spring-boot-starter-web 依赖中已经有 hibernate-validator 包,不需要引用相关依赖。如下图所示(通过 idea 插件—Maven Helper 生成):
  • 注意的是: 所有的注解,推荐使用 JSR 注解,即 javax.validation.constraints,而不是org.hibernate.validator.constraints
  • Spring参数校验的方法

1、一些常用的字段验证的注解

  • @NotEmpty 被注释的字符串的不能为 null 也不能为空
  • @NotBlank 被注释的字符串非 null,并且必须包含一个非空白字符
  • @Null 被注释的元素必须为 null
  • @NotNull 被注释的元素必须不为 null
  • @AssertTrue 被注释的元素必须为 true
  • @AssertFalse 被注释的元素必须为 false
  • @Pattern(regex=,flag=)被注释的元素必须符合指定的正则表达式
  • @Email 被注释的元素必须是 Email 格式。
  • @Min(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
  • @Max(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
  • @DecimalMin(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
  • @DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
  • @Size(max=, min=)被注释的元素的大小必须在指定的范围内
  • @Digits (integer, fraction)被注释的元素必须是一个数字,其值必须在可接受的范围内
  • @Past被注释的元素必须是一个过去的日期
  • @Future 被注释的元素必须是一个将来的日期

2、验证请求体(RequestBody)

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {@NotNull(message = "classId 不能为空")private String classId;@Size(max = 33)@NotNull(message = "name 不能为空")private String name;@Pattern(regexp = "((^Man$|^Woman$|^UGM$))", message = "sex 值不在可选范围")@NotNull(message = "sex 不能为空")private String sex;@Email(message = "email 格式不正确")@NotNull(message = "email 不能为空")private String email;
}
  • 我们在需要验证的参数上加上了@Valid注解,如果验证失败,它将抛出MethodArgumentNotValidException
@RestController
@RequestMapping("/api")
public class PersonController {@PostMapping("/person")public ResponseEntity<Person> getPerson(@RequestBody @Valid Person person) {return ResponseEntity.ok().body(person);}
}

3、验证请求参数(Path Variables 和 Request Parameters)

  • 一定一定不要忘记在类上加上 Validated 注解了,这个参数可以告诉 Spring 去校验方法参数。
@RestController
@RequestMapping("/api")
@Validated
public class PersonController {@GetMapping("/person/{id}")public ResponseEntity<Integer> getPersonByID(@Valid @PathVariable("id") @Max(value = 5,message = "超过 id 的范围了") Integer id) {return ResponseEntity.ok().body(id);}
}
  • Spring参数校验

自定义 Validator(非常实用)
案例一:校验特定字段的值是否在可选范围
比如我们现在多了这样一个需求:Person类多了一个 region 字段,region 字段只能是China、China-Taiwan、China-HongKong这三个中的一个。
第一步你需要创建一个注解:

@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = RegionValidator.class)
@Documented
public@interface Region {String message() default "Region 值不在可选范围内";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};
}

重点在于:@Constraint(validatedBy = RegionValidator.class) RegionValidator这个类实际校验的方法。

第二步你需要实现 ConstraintValidator接口,并重写isValid 方法

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;publicclass RegionValidator implements ConstraintValidator<Region, String> {@Overridepublic boolean isValid(String value, ConstraintValidatorContext context) {HashSet<Object> regions = new HashSet<>();regions.add("China");regions.add("China-Taiwan");regions.add("China-HongKong");return regions.contains(value);}
}

这个类RegionValidator要实现接口

public interface ConstraintValidator<A extends Annotation, T> {  //A为自定义注解ValidNum,T为校验数据的类型;void initialize(A var1);   //初始化方法,boolean isValid(T var1, ConstraintValidatorContext var2); //验证的逻辑,返回false则验证不通过
}

现在你就可以使用这个注解:

@Region
private String region;

在Controller层接收参数并验证

import com.alibaba.fastjson.JSON;
import com.river.jsr.entity.Region;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.*;import javax.validation.Valid;
import java.util.Iterator;
import java.util.List;/*** Created by qiwenjie on 2021/06/30.*/
@RestController
public class RegionController {@RequestMapping(value = "jsr", method = RequestMethod.POST)@ResponseBodypublic String jsrValid(@Valid Region region, BindingResult result) {List<ObjectError> allErrors = result.getAllErrors();if (allErrors != null && !allErrors.isEmpty()) {Iterator<ObjectError> errorIterator = allErrors.iterator();while (errorIterator.hasNext()) {ObjectError error = errorIterator.next();return error.getDefaultMessage();}}return Region.toString();}
}

BindingResult 该对象为接收错误信息的对象,倘若没有,则校验不通过时直接抛出异常,通过这个对象可以拿到异常信息。

拓展点: 进一步我们可以写成一个aop,前置通知去做这一步的校验,切入点打在要加入校验的方法或所有有该对象接收的方法上。

案例二:校验电话号码
校验我们的电话号码是否合法,这个可以通过正则表达式来做

import javax.validation.Constraint;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;@Documented   //用于指定被修饰的注解将被javadoc工具提取成文档
@Constraint(validatedBy = PhoneNumberValidator.class)
@Target({FIELD, PARAMETER})  //用于指定被修饰的注解的适用范围,即被修饰的注解可以用来修饰哪些程序元素
@Retention(RUNTIME)    //用来描述被修饰的注解的生命周期
public@interface PhoneNumber {String message() default "Invalid phone number";Class[] groups() default {};Class[] payload() default {};
}
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber,String> {@Overridepublic boolean isValid(String phoneField, ConstraintValidatorContext context)         {if (phoneField == null) {// can be nullreturntrue;}return phoneField.matches("^1(3[0-9]|4[57]|5[0-35-9]|8[0-9]|70)\\d{8}$") && phoneField.length() > 8 && phoneField.length() < 14;}
}

搞定,我们现在就可以使用这个注解了。

@PhoneNumber(message = "phoneNumber 格式不正确")
@NotNull(message = "phoneNumber 不能为空")
private String phoneNumber;

6.6、全局处理 Controller 层异常

相关注解:
@ControllerAdvice :注解定义全局异常处理类
@ExceptionHandler :注解声明异常处理方法
如何使用呢?拿我们在第 5 节参数校验这块来举例子。如果方法参数不对的话就会抛出MethodArgumentNotValidException,我们来处理这个异常。

@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {/*** 请求参数异常处理*/@ExceptionHandler(MethodArgumentNotValidException.class)public ResponseEntity<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) {......}
}

更多关于 Spring Boot 异常处理的内容,看这两篇文章:

  • SpringBoot 处理异常的几种常见姿势
  • 使用枚举简单封装一个优雅的 Spring Boot 全局异常处理!

明天继续补充

  • 今日done

6.7、事务 @Transactional

在要开启事务的方法上使用@Transactional注解即可

@Transactional(rollbackFor = Exception.class)
public void save() {......
}

我们知道 Exception 分为运行时异常 RuntimeException非运行时异常。在@Transactional注解中如果不配置rollbackFor属性,那么事物只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事物在遇到非运行时异常时也回滚。
@Transactional 注解一般用在可以作用在类或者方法上。

  • 作用于类:当把@Transactional 注解放在类上时,表示所有该类的public 方法都配置相同的事务属性信息。
  • 作用于方法:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息。
    更多关于关于 Spring 事务的内容请查看:
    一口气说出 6 种 @Transactional 注解失效场景

6.8、json 数据处理

1、过滤 json 数据

  • @JsonIgnoreProperties 作用在类上用于过滤掉特定字段不返回或者不解析。
//生成json时将userRoles属性过滤
@JsonIgnoreProperties({"userRoles"})
public class User {private String userName;private String fullName;private String password;@JsonIgnoreprivate List<UserRole> userRoles = new ArrayList<>();
}
  • @JsonIgnore一般用于类的属性上,作用和上面的@JsonIgnoreProperties 一样
  • 作用是“在实体类向前台返回数据时用来忽略不想传递给前台的属性或接口”

public class User {private String userName;private String fullName;private String password;//生成json时将userRoles属性过滤@JsonIgnoreprivate List<UserRole> userRoles = new ArrayList<>();
}
  • 注意事项:
    例如:User实体中有一个字段password,当我们用User实体作为输出类给前端返回用户信息的时候,并不希望将password值也一并返回。这个时候就 可以在password属性上加上注解JsonIgnore 或者,可以在User类上加上注解@JsonIgnoreProperties(value = “{password}”)
    但是,要注意的是,当前端以json格式向后台传password的值,且后台是以实体User接收时,这时候@JsonIgnore会忽略,即不接收password字段的值。若想避免此类情况,建议使用form表单的形式提交参数,而非json格式

2、格式化 json 数据

  • @JsonFormat一般用来格式化 json 数据。:
@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone="GMT")
private Date date;

3、扁平化对象

@Getter
@Setter
@ToString
public class Account {@JsonUnwrappedprivate Location location;@JsonUnwrappedprivate PersonInfo personInfo;@Getter@Setter@ToStringpublic static class Location {private String provinceName;private String countyName;}@Getter@Setter@ToStringpublic static class PersonInfo {private String userName;private String fullName;}
}

未扁平化之前:

{"location": {"provinceName":"湖北","countyName":"武汉"},"personInfo": {"userName": "coder1234","fullName": "shaungkou"}
}

使用@JsonUnwrapped 扁平对象之后:

@Getter
@Setter
@ToString
public class Account {@JsonUnwrappedprivate Location location;@JsonUnwrappedprivate PersonInfo personInfo;......
}
{"provinceName":"湖北","countyName":"武汉","userName": "coder1234","fullName": "shaungkou"
}

6.9、测试相关

  • @ActiveProfiles一般作用于测试类上, 用于声明生效的 Spring 配置文件。
@SpringBootTest(webEnvironment = RANDOM_PORT)
@ActiveProfiles("test")
@Slf4j
public abstract class TestBase {......
}
  • @Test声明一个方法为测试方法
  • @Transactional 被声明的测试方法的数据会回滚,避免污染测试数据。
  • @WithMockUser Spring Security 提供的,用来模拟一个真实用户,并且可以赋予权限。
@Test
@Transactional
@WithMockUser(username = "user-id-18163138155", authorities = "ROLE_TEACHER")
void should_import_student_success() throws Exception {......
}

完全注解开发

  • 使用SpringBoot来完成

6.10、自定义Spring注解

1、自定义注解的场景
登陆、权限拦截、日志处理JUnit,以及各种Java框架,如Spring,Hibernate

2、原理:
Java自定义注解通过运行时反射获取注解,以获取注解修饰的“类、方法、属性”的相关解释。
例如:我们要获取某个方法的调用日志,可以通过AOP(动态代理机制)给方法添加切面,通过反射来获取方法包含的注解,如果包含日志注解,就进行日志记录

3、通过容器ApplicationContext拿到所有标注了自定义注解的类
例如:@RPCService(“aaaimpl”)

1、定义一个java文件 自定义注解RpcService

@Target({ElementType.TYPE})     //表示该注解用于什么地方 可能的 ElemenetType 参数包括:CONSTRUCTOR构造器 FIELD域 LOCAL_VARIABLE局部变量声明 METHOD方法声明 PACKAGE包声明 PARAMETER参数声明 TYPE类、接口或enum声明
@Retention(RetentionPolicy.RUNTIME) //表示在什么级别保存该注解信息    VM将在运行期也保留注释,因此可以通过反射机制读取注解的信息   可选参数包括:SOURCE注解将被编译器丢弃   CLASS注解在class文件中可用,但会被VM丢弃 RUNTIME注解在运行期也保留注释
@Component       //让Spring扫描
public @interface RpcService{String value();//可以拿到注解里面的参数
}

2、将直接类加到需要使用的类上,我们可以通过获取注解,来得到这个类

@RpcService("HelloService")
public class HelloServiceImpl implements HelloService {public String hello(String name) {return "Hello! " + name;}
}

3、类实现的接口

public interface HelloService {String hello(String name);
}

4、通过ApplicationContext获取所有标记这个注解的类

public void setApplicationContext(ApplicationContext ctx) throws BeansException {Map<String, Object> serviceBeanMap = ctx.getBeansWithAnnotation(RpcService.class);for (Object serviceBean : serviceBeanMap.values()) {try {Method method = serviceBean.getClass().getMethod("hello", new Class[]{String.class});Object invoke = method.invoke(serviceBean, "bbb");System.out.println(invoke);}catch (Exception e) {e.printStackTrace();} }
}

5、结合spring实现junit测试
注解如何使用?
1、在Spring中,用注解来向容器注册Bean。需要在applicationContext.xml中注册<context:component-scan base-package=“package1”/>
2、如果某个类的头上带有特定的注解@Component @Repository @Service @Controller,就会将这个对象作为bean注册进Spring容器。(功能是一样的,只是用在不同层中)
3、在使用spring管理的bean时,无需在对调用的对象进行new的过程,只需使用@Autowired将需要的bean注入本类即可。


7、Spring结构图 (6大模块)及源码分析

7.1 Spring结构图 图片来源于《Spring源码深度解析》


图7.1 Spring整体架构图

(1)核心容器Core Container:包括Core、Beans、Context、Expression Language模块

  • Core模块:封装了框架依赖的最底层部分,包括资源访问、类型转换及一些常用工具类;
  • Beans模块:提供了框架的基础部分,包括反转控制和依赖注入。其中Bean Factory是容器核心,本质是“工厂设计模式”的实现,而且无需编程实现“单例设计模式”,单例完全由容器控制,而且提倡面向接口编程,而非面向实现编程;
  • Context模块:以Core和Beans为基础,集成Beans模块功能并添加资源绑定、数据验证、国际化、Java EE支持、容器生命周期、事件传播等;核心接口是ApplicationContext;
  • EL模块:提供强大的表达式语言支持,支持访问和修改属性值,方法调用,支持访问及修改数组、容器和索引器,命名变量,支持算数和逻辑运算,支持从Spring 容器获取Bean。

(2)AOP、Aspects模块:

  • AOP模块:Spring AOP模块提供了符合 AOP 联盟规范的面向切面的编程(aspect-oriented programming)实现,提供比如日志记录、权限控制、性能统计等通用功能和业务逻辑分离的技术,并且能动态的把这些功能添加到需要的代码中;这样各专其职,降低业务逻辑和通用功能的耦合;
  • Aspects模块:提供了对AspectJ的集成,这是一个功能强大且成熟的面向切面编程(AOP)框架;
  • 数据访问/集成模块:该模块包括了JDBC、ORM、OXM、JMS和事务管理;
  • 事务模块:该模块用于 Spring 管理事务,只要是 Spring 管理对象都能得到 Spring 管理事务的好处,无需在代码中进行事务控制了,而且支持编程和声明性的事务管理;
  • JDBC模块:提供了一个JBDC的样例模板,使用这些模板能消除传统冗长的JDBC编码还有必须的事务控制,而且能享受到Spring管理事务的好处;
  • ORM模块:提供与流行的“对象-关系”映射框架的无缝集成,包括Hibernate、JPA、MyBatis等。而且可以使用Spring事务管理,无需额外控制事务。

(3)数据集成与访问
Spring的jdbc和dao模块抽象化了这些样板代码,使得数据库连接变得简单

  • JDBC 模块提供了删除冗余的 JDBC 相关编码的 JDBC 抽象层;
  • ORM 模块为流行的对象关系映射 API,包括 JPA,JDO,Hibernate 和 iBatis,提供了集成层;
  • 包含了JMS,他会使用消息以异步的方式与其他应用集成;

(4)web与远程调用

  • 提供了基础的面向Web的集成特性。例如,多文件上传、使用servlet listeners初始化IoC容器以及一个面向Web的应用上下文

(5)instrumentation

  • 它为tomcat提供了一个织入代理,能够为tomcat传递类文件。

(6)test

  • 提供了一系列的mock对象实现。

7.2 Spring的结构组成


8、Spring中BeanFactory和FactoryBean有什么区别?

Spring核心工厂是BeanFactory (一种容器,提供了基本的DI支持

  • BeanFactory采取延迟加载,第一次getBean时才会初始化Bean(不推荐使用),ApplicationContext是会在加载配置文件时初始化Bean。
  • Bean 工厂是工厂模式的一个实现,提供了控制反转功能,用来把应用的配置和依赖从真正的应用代码中分离。(在配置文件定义bean类型可以和返回类型不一样)

常用的BeanFactory 实现:
有DefaultListableBeanFactory 、 XmlBeanFactory 、 ApplicationContext等。
XMLBeanFactory,最常用的就是org.springframework.beans.factory.xml.XmlBeanFactory,它根据XML文件中的定义加载beans。该容器从XML 文件读取配置元数据并用它去创建一个完全配置的系统或应用。


9、Spring的事件(Bean与Bean之间消息通信)

我们希望一个一个Bean监听当前Bean所发送的事件,流程:
1、自定义事件,继承ApplicationEvent;
2、定义事件监听器,实现ApplicationListener;
3、使用IOC容器发布事件

(1)自定义事件

public classDemoEvent extends ApplicationEvent{private static final long serialVersionUID =1L;private String msg;构造函数/getset方法
}

(2)事件监听器

@Component
public class DemoListener implements ApplicationListener<DemoEvent>{  //<>指定监听的事件类型public void onApplicationEvent(DemoEvent event){  //对消息进行接收处理String msg =event.getMsg();sout(msg);}
}

(3)事件发布类

 @Component public class DemoPublisher{@AutowiredApplicationContext applicationContext;  //用于发布事件applicationContext.publishEvent(new DemoEvent(this,msg));}

(4)配置类

@Configuration
@ComponentScan("包名")
public class EventConfig{}

(5)运行 实例化IOC容器,

  • getBean(),调用事件发布类

10、Spring高级话题(aware/多线程/计划任务/条件注解/组合注解/Enable原理)

1、Spring Aware 将Bean和Spring框架耦合
spring aware目的是为了让Bean获得Spring容器的服务,因为applicationContext接口集成了MessageSource接口、ApplicationPublisher接口和ResourceLoader接口,所以Bean继承ApplicationContextAware可以获得Spring容器的所有服务,但原则上我们需要什么就实现什么接口

2、多线程(@EnableAsync 通过任务执行器TaskExecutor来实现多线程和并发编程)
使用ThreadPoolTaskExecutor实现一个基于线程池的TaskExecutor,实际开发中任务一般是异步的,我们在配置类中通过@EnableAsync开启对异步任务的支持,并通过在实际执行的Bean方法中使用

@Async注解来声明其是一个异步任务。

  • 配置类中实现AsyncConfigurer接口并重写getAsyncExecutor方法,并返回一个threadpoolTaskExecutor,这样我们就获得一个基于线程池TaskExecutor

3、计划任务(通过@Scheduled支持多种类型的计划任务,包含cron/fixDelay/fixRate)

通过@Scheduled声明方法是计划任务,使用fixedRate属性每隔固定时间执行
使用cron属性可按照指定时间执行(Unix下) 开启计划任务@EnableScheduling

4、条件注解@Conditional

  • 比Profile更优秀 根据特定条件来控制Bean的创建行为(在springboot中大量应用到了条件注解)
  • 以不同的操作系统作为条件,通过实现Condition接口,并重写其matches方法构造判断条件,若在Windows系统下运行程序,则输出列表命令为dir,若在Linux操作系统下运行程序,输出列表命令为ls。

5、组合注解 使用一个注解表示两个注解 例如@Configuration和@ComponentScan被其他注解代替

6、@Enable* 注解工作原理
已有的@Enable*

  • @EnableAspectJAutoProxy注解开启Spring对AspectJ的支持;
  • @EnableAsync开启对异步任务的支持;
  • @EnableScheduling开启计划任务的支持;
  • @EnableWebMvc开启WebMVC的配置支持;
  • @EnableConfigurationProperties开启对@ConfigurationProperties注解配置Bean的支持
  • @EnableJpaRepositories开启对SpringData JPA Repository的支持
  • @EnableTransactionManagement开启注解式事务的支持
  • @EnableCaching开启注解式的缓存支持)

原理:所有的注解都有一个@import注解,用于导入配置类,自动开启的实现是导入了一些自动配置的Bean,导入方式分为以下三种:

  • 1、直接导入配置类 @EnableScheduling
  • 2、依赖条件选择配置类 @EnableAsync
  • 3、动态注册Bean @EnableAspectJAutoProxy

10.1 如何使用restTemplate来提交get请求或post请求?如何调用开放平台接口

get请求:

package cn.gov.zcy.zlb.user.workbench.controller.support;/*** controller单元测试基类,提供基本的环境变量设置.公共方法实现*/
@Slf4j
@RunWith(SpringRunner.class)
@TestPropertySource(properties = {"env=XXX", "apollo.cluster=xxx"})
@ComponentScan(basePackages = "cn.gov.xxx",excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {cn.gov.zcy.zlb.user.workbench.xxxApplication.class})})
@Import(cn.gov.zcy.zlb.web.auth.config.SpringInitConfig.class)
@EnableApolloConfig(value = {"xxx", "dev.xxx"}, order = Ordered.HIGHEST_PRECEDENCE)
@EnableAutoConfiguration
@SpringBootTest(classes = ControllerTestBase.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class ControllerTestBase {static String env;static String apolloCluster;static String port;static String basePath;static {env = System.getProperty("env");apolloCluster = System.getProperty("apollo.cluster");port = System.getProperty("server.port");if (env == null) {env = "DEV";System.setProperty("env", env);}if (apolloCluster == null) {apolloCluster = "zlb";System.setProperty("apollo.cluster", apolloCluster);}if (port == null) {port = "18080";System.setProperty("server.port", port);}basePath = String.format("http://localhost:%s/", port);}//测试用户名和密码@Value("${xxx.userName:admin}")String userName;@Value("${xxx.password:test123456}")String password;@Autowiredprotected TestRestTemplate restTemplate;protected AuthRequestInterceptor authRequestInterceptor;public ControllerTestBase() {log.info("ControllerTestBase created");}@PostConstructpublic void init() {//认证拦截器Supplier<String> authorizationHeaderSupplier = () -> buildAuthorizationHeader();authRequestInterceptor = new AuthRequestInterceptor(authorizationHeaderSupplier);   restTemplate.getRestTemplate().setInterceptors(Arrays.asList(authRequestInterceptor));}/*** 设置当成测试的登录用户名和密码* @param userName* @param password*/protected void setCurrentLoginInsetCurrentLoginInfofo(String userName, String password) {this.userName = userName;this.password = password;}/*** 构建完整的测试URL* @param path* @return*/protected String buildTestUrl(String path) {if (path.startsWith("/")) {return basePath + path.substring(1);}return basePath + path;}/*** 发送响应结果为JSONObject的GET请求* @param path* @param params* @return*/public JSONObject sendGetRequestOfJson(String path, Map<String, String> params) {String body = sendGetRequestOfString(path, params);Assert.assertTrue(body.startsWith("{") && body.endsWith("}"));return JSONObject.parseObject(body);}public JSONObject sendPostRequestOfJson(String path, Map<String, String> params) {LinkedMultiValueMap<String, Object> postParams = new LinkedMultiValueMap<>();for (Map.Entry<String, String> entry : params.entrySet()) {postParams.put(entry.getKey(), Collections.singletonList(entry.getValue()));}String body = sendPostRequestOfString(path, postParams);Assert.assertTrue(body.startsWith("{") && body.endsWith("}"));return JSONObject.parseObject(body);}/*** 发送响应结果为String的GET请求* @param path* @param params* @return*/protected String sendGetRequestOfString(String path, Map<String, String> params) {String testUrl = buildTestUrl(path);Map<String, String> urlVariables = new HashMap<>();if (params != null && params.size() > 0) {urlVariables.putAll(params);}ResponseEntity<String> response = this.restTemplate.getForEntity(testUrl, String.class, urlVariables);log.info("url:{}, response: {}", testUrl, JSON.toJSONString(response));assertHttpStateOk(response);String body = response.getBody();Assert.assertTrue(StringUtils.hasText(body));return body;}/*** 发送响应结果为String的Post请求* ContentType为APPLICATION_FORM_URLENCODED* @param path  路径* @param param* @return*/protected String sendPostRequestOfString(String path, MultiValueMap<String, Object> param) {String testUrl = buildTestUrl(path);HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);HttpEntity postBody = new HttpEntity<>(param, headers);ResponseEntity<String> response = this.restTemplate.postForEntity(testUrl, postBody, String.class);log.info("url:{}, response: {}", testUrl, JSON.toJSONString(response));assertHttpStateOk(response);String body = response.getBody();Assert.assertTrue(StringUtils.hasText(body));return body;}protected void assertHttpStateOk(ResponseEntity response) {Assert.assertEquals(HttpStatus.OK, response.getStatusCode());}private String buildAuthorizationHeader() {authRequestInterceptor.setEnableAuthorizationHeader(false);String userName = this.userName;String pwd = this.password;String url = getOAuthTokenApiUrl();try {//请求HttpHeaders headers = new HttpHeaders();headers.set("Content-Type", "application/x-www-form-urlencoded");headers.set("Authorization", "Basic emN5YWRtaW46dks2b2xSNUl6b2NlQ1A4dQ==");MultiValueMap<String, String> body = new LinkedMultiValueMap<>();body.add("username", userName);body.add("password", pwd);body.add("authentication_type", "password");HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(body, headers);ResponseEntity<JSONObject> response = restTemplate.exchange(url, HttpMethod.POST, httpEntity, JSONObject.class);log.info("url:{},res:{}", url, response);assertHttpStateOk(response);return response.getHeaders().get("Set-Cookie").get(0);} finally {authRequestInterceptor.setEnableAuthorizationHeader(true);}}private String getOAuthTokenApiUrl() {return "http://www.zlb.cai-inc.com/api/login";}
}

post请求

如何调用开放平台接口:

// 配置文件
@Configuration
public class xxxInitConfig {public static final String CONFIG_PREFIX = "xxx.openapi.client";@Bean@ConfigurationProperties(prefix = CONFIG_PREFIX)public xxxClient.Config config() {return new xxxClient.Config();}@Beanpublic xxxrClient xxxClient(@Autowired xxxClient.Config config) {return new xxxClient(config);}
}/*** 调用第三方接口单元测试* @date 2021/7/9*/
@Slf4j
@TestPropertySource(properties = {"env=xxx", "apollo.cluster=xxx"})
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MaycurClientTest.class)
//@TestPropertySource(locations = {"classpath:xxx.properties"})
@Import(xxxInitConfig.class)
@EnableAutoConfiguration(exclude = {com.ctrip.framework.apollo.spring.boot.ApolloAutoConfiguration.class})
public class xxxClientTest {@Autowiredprivate xxxClient xxxrClient;@Testpublic void getAuthLoginTokenTest() {Response<xxxClient.Token> result = xxxClient.getAuthLoginToken();Response<xxxClient.Token> result2 = xxxClient.getAuthLoginToken();log.info("result2:{}", JSON.toJSONString(result2, SerializerFeature.PrettyFormat));Assert.assertTrue(result2.isSuccess() && result2.getResult().equals(result.getResult()));}@Testpublic void getxxxDocument() {Response<List<Resp>> result = xxxClient.getDocuments(operatorId);log.info("result:{}", JSON.toJSONString(result, SerializerFeature.PrettyFormat));Assert.assertTrue(result.isSuccess());}
}private Config config;
private Token currToken;
private OkHttpClient httClient;public xxxClient(Config config) {this.config = config;this.httClient = new OkHttpClient();
}/*** 获取登录认证token* @return*/
public Response<Token> getAuthLoginToken() {Token token = getCachedAuthLoginTokenToken();if (token != null) {return Response.ok(token);}return refreshAuthLoginToken();
}private Token getCachedAuthLoginTokenToken() {//默认缓存20分钟,第三方系统为30分钟int tokenCacheTs = config.getTokenCacheSec() == null ? 1200 : config.getTokenCacheSec();if (currToken != null && (currToken.getCreatAtMilleSec() / 1000 + tokenCacheTs) > (System.currentTimeMillis() / 1000)) {return currToken;}return null;}private synchronized Response<Token> refreshAuthLoginToken() {//双重检测Token token = getCachedAuthLoginTokenToken();if (token != null) {return Response.ok(token);}Long timestamp = System.currentTimeMillis();String signaturePlainText = config.getAppSecret() + ":" + config.getAppCode() + ":" + timestamp;String signatureText = DigestUtils.sha256Hex(signaturePlainText);String params = new JSONObject().fluentPut("appCode", config.getAppCode()).fluentPut("secret", signatureText).fluentPut("timestamp", timestamp.toString()).toJSONString();RequestBody body = RequestBody.create(APPLICATION_JSON, params);Request request = new Request.Builder().url(config.getAuthLoginApiPath()).post(body).addHeader("content-type", APPLICATION_JSON.toString()).build();Response<Response> apiResponse = invokeXxxApi(request, xxxAuthLoginApiResponse.class);if (apiResponse.isSuccess()) {this.currToken = new Token(apiResponse.getResult().getData().getEntCode(), apiResponse.getResult().getData().getTokenId(), System.currentTimeMillis());return Response.ok(this.currToken);}return Response.fail(apiResponse.getCode(), apiResponse.getMessage());}// 最底层执行的逻辑
private <T extends Response> Response<T> invokeXxxApi(Request request, Class<T> clz) {String body = null;try {okhttp3.Response response = httClient.newCall(request).execute();try {body = response.body().string();} catch (IOException ex) {log.error("读取响应失败", ex);} finally {response.close();}if (!response.isSuccessful()) {log.error("调用API失败,url:{},code:{},message:{},response:{}", request.url(), response.code(), response.message(), body);if (body != null && body.startsWith("{") && body.endsWith("}") && body.indexOf("\"code\"") > 0) {MaycurApiResponse apiResponse = JSON.parseObject(body, MaycurApiResponse.class);return Response.fail(apiResponse.getCode(), apiResponse.getMessage());} else {return Response.fail(Integer.toString(response.code()), response.message());}}if (log.isDebugEnabled()) {log.debug("调用每刻API,url:{},body:{}", request.url(), body);}} catch (IOException e) {log.error("调用每刻API失败,url:{}", request.url(), e);return Response.fail("500", e.getMessage());}T result = JSON.parseObject(body, clz);if (result.isOk()) {return Response.ok(result);}Response ret = Response.fail(result.getCode(), result.getMessage());ret.setResult(result);return ret;
}

10.2、@PostConstruct 修饰的方法里用到了其他 bean 实例,会有问题吗

该题可以拆解成下面3个问题:

  • 1、@PostConstruct 修饰的方法被调用的时间
  • 2、bean 实例依赖的其他 bean 被注入的时间,也可理解为属性的依赖注入时间
  • 3、步骤2的时间是否早于步骤1:如果是,则没有问题,如果不是,则有问题

解析:

  • 1、PostConstruct 注解被封装在 CommonAnnotationBeanPostProcessor中,具体触发时间是在 postProcessBeforeInitialization 方法,从 doCreateBean 维度看,则是在 initializeBean 方法里,属于初始化 bean 阶段。
  • 2、属性的依赖注入是在 populateBean 方法里,属于属性填充阶段。
  • 3、属性填充阶段位于初始化之前,所以本题答案为没有问题。

10.3、bean 的 init-method 属性指定的方法里用到了其他 bean 实例,会有问题吗

该题同上面这题类似,只是将 @PostConstruct 换成了 init-method 属性。

答案是不会有问题。同上面一样,init-method 属性指定的方法也是在 initializeBean 方法里被触发,属于初始化 bean 阶段。

10.4、要在 Spring IoC 容器构建完毕之后执行一些逻辑,怎么实现?

1、比较常见的方法是使用事件监听器,实现 ApplicationListener 接口,监听 ContextRefreshedEvent 事件。

2、还有一种比较少见的方法是实现 SmartLifecycle 接口,并且 isAutoStartup 方法返回 true,则会在 finishRefresh() 方法中被触发。

两种方式都是在 finishRefresh 中被触发,SmartLifecycle在ApplicationListener之前。

10.5、Spring 中的常见扩展点有哪些(5分)

1、ApplicationContextInitializer

  • initialize 方法,在 Spring 容器刷新前触发,也就是 refresh 方法前被触发。

2、BeanFactoryPostProcessor

  • postProcessBeanFactory 方法,在加载完 Bean 定义之后,创建 Bean 实例之前被触发,通常使用该扩展点来加载一些自己的 bean 定义。

3、BeanPostProcessor

  • postProcessBeforeInitialization 方法,执行 bean 的初始化方法前被触发;
  • postProcessAfterInitialization 方法,执行 bean 的初始化方法后被触发。

4、@PostConstruct

  • 该注解被封装在 CommonAnnotationBeanPostProcessor 中,具体触发时间是在 postProcessBeforeInitialization 方法。

5、InitializingBean

  • afterPropertiesSet 方法,在 bean 的属性填充之后,初始化方法(init-method)之前被触发,该方法的作用基本等同于 init-method,主要用于执行初始化相关操作。

6、ApplicationListener,事件监听器

  • onApplicationEvent 方法,根据事件类型触发时间不同,通常使用的 ContextRefreshedEvent 触发时间为上下文刷新完毕,通常用于 IoC 容器构建结束后处理一些自定义逻辑。

7、@PreDestroy

该注解被封装在 DestructionAwareBeanPostProcessor 中,具体触发时间是在 postProcessBeforeDestruction 方法,也就是在销毁对象之前触发。

8、DisposableBean

  • destroy 方法,在 bean 的销毁阶段被触发,该方法的作用基本等同于

  • destroy-method,主用用于执行销毁相关操作。


11、框架spring (全家桶) springMVC(RESTful) mybatis struts2 hibernate 整合问题

1、SSM整合

  1. Dao层
    pojo和映射文件以及接口使用逆向工程生成(有各种工具);
    SqlMapConfig.xml用于配置mybatis核心配置文件;
    ApplicationContext-dao.xml整合spring在dao层的配置(数据源、会话工厂、扫描Mapper);

  2. service层
    事务 ApplicationContext-trans.xml
    @Service注解扫描 ApplicationContext-service.xml

  3. controller层
    SpringMvc.xml
    注解扫描:扫描@Controller注解
    注解驱动:替我们显示的配置最新版的处理器映射器(handlerMapping)和处理器适配器(handlerAdapter)
    视图解析器:显示的配置是为了在controller中不用每个方法都写页面的全路径(requestMapping)

  4. web.xml
    springMvc前端控制器配置
    spring监听



12、 参数绑定(从请求中接收参数) 重点

1、默认类型:

  • 在controller方法中可以有也可以没有,看自己需求随意添加. httpservletRqeust, httpServletResponse, httpSession, Model(ModelMap其实就是Mode的一个子类,一般用的不多)

2、基本类型:string, double, float, integer, long. boolean

3、pojo类型:页面上input框的name属性值必须要等于pojo的属性名称

4、vo类型(view object 用于表现层):页面上input框的name属性值必须要等于vo中的属性.属性.属性…

5、自定义转换器converter:

  • 作用:由于springMvc无法将string自动转换成date,所以需要自己手动编写类型转换器,需要编写一个类实现Converter接口
  • 在springMvc.xml中配置自定义转换器
  • 在springMvc.xml中将自定义转换器配置到注解驱动上

13、 SpringMVC或Struts处理请求流程区别?

SpringMVC通过ModelAndView,把结果集拿到以后,从配置文件里获取对应的bean,类反射

区别:

  • springMvc是方法级别的拦截,一个方法对应一个request上下文,而方法同时又跟一个url对应,参数的传递是直接注入到方法中的,是该方法独有的
  • struts2是类级别的拦截,一个类对应一个request上下文,struts是在接受参数的时候,可以用属性来接受参数,这就说明参数是让多个方法共享的,这也就无法用注解或其他方式标识其所属方法了。
  • 1、SpringMvc的入口是一个servlet即前端控制器,而Struts2入口是一个filter过滤器
  • 2、SpringMvc是基于方法开发(一个url对应一个方法),请求参数传递到方法的形参,可以设计为单例或多例(建议多例),struts2是基于类开发,传递参数是通过类的属性,只能设计为多例;
  • 3、Struts2 采用值栈存储请求和响应的数据,通过OGNL存取数据,Springmvc通过参数解析器是将request请求内容解析,并给方法形参赋值,将数据和视图封装成ModelAndView对象, 最后又将ModelAndView中的模型数据通过reques域传输到页面。Jsp视图解析器默认使用JSTL表达式

备注:如今的开发基本不使用struts2了,基本都是sprigboot这一条脚手架,里面集成了springMvc。 2021010


14、tomcat和Spring是怎么交互的?20181025有赞

xxxxx


15、Spring各jar包的作用? (20181104)

除了spring.jar文件,Spring还包括有其它13个独立的jar包
1、spring-core.jar (核心)

  • 这个jar文件包含spring框架基本的核心工具类,spring其他组件都要用到这个包里面的类

2、spring-beans.jar (核心)

  • 这个jar文件是所有应用都要用到的,它包含访问配置文件、创建和管理bean以及进行ioc/di操作相关的所有类

3、spring-aop.jar

  • 这个jar文件包含在应用中使用Spring的AOP特性时所需的类,使用声明式事务管理时,需要导入

4、spring-context.jar (核心)

  • 这个jar文件为Spring核心提供了大量扩展。可以找到使用Spring ApplicationContext(容器)特性时所需的全部类,JDNI所需的全部类

5、spring-dao.jar

  • 这个jar文件包含Spring DAO、Spring Transaction进行数据访问的所有类。为了使用声明型事务支持,还需在自己的应用里包含spring-aop.jar

6、spring-hibernate.jar

  • 这个jar文件包含Spring对Hibernate 2及Hibernate 3进行封装的所有类

7、spring-jdbc.jar

  • 这个jar文件包含对Spring对JDBC数据访问进行封装的所有类

8、spring-orm.jar

  • 这个jar文件包含Spring对DAO特性集进行了扩展,使其支持 iBATIS、JDO、OJB、TopLink 与spring-dao结合使用

9、spring-remoting.jar

  • 这个jar文件包含支持EJB、JMS、远程调用Remoting(RMI、Hessian、Burlap、Http Invoker、JAX-RPC)方面的类

10、spring-support.jar

  • 这个jar文件包含支持缓存Cache,JCA、JMX、邮件服务方面的类

11、spring-web.jar

  • 这个jar文件包含Web应用开发时,用到Spring框架时所需的核心类,包括自动载入WebApplicationContext特性的类、Struts与JSF集成类、文件上传的支持类、Filter类和大量工具辅助类

12、spring-webmvc.jar

  • 这个jar文件包含Spring MVC框架相关的所有类。包含国际化、标签、Theme、视图展现的FreeMarker、JasperReports、Tiles、Velocity、XSLT相关类

13、spring-mock.jar

  • 这个jar文件包含Spring一整套mock类来辅助应用的测试。
    一般来说,加载全部的spring.jar文件

五色令人目盲;五音令人耳聋;五味令人口爽;驰骋畋猎,令人心发狂;难得之货,令人行妨;是以圣人为腹不为目,故去彼取此。 --《道德经》

Spring第一讲:谈谈你对Spring的理解?从该问题着手深入解析Spring/Spring5新特性相关推荐

  1. Spring第一讲:初步了解Spring

    一.什么是Spring,什么是bean? Spring是一种框架,Spring框架主要提供了IoC容器.AOP.数据访问.Web开发.消息.测试等相关技术的支持.每一个没Spring管理的 Java对 ...

  2. JUC第一讲:juc并发包深入理解(P6熟练 P7精通)

    并发编程并不是 Java 特有的语言特性,它是一个通用且早已成熟的领域.Java 只是根据自身情况做了实现罢了.并发编程可以总结为三个核心问题:分工.同步.互斥.分工指的是如何高效地拆解任务并分配给线 ...

  3. 玩转Spring—Spring5新特性之Reactive响应式编程实战

    1 什么是响应式编程 一句话总结:响应式编程是一种编程范式,通用和专注于数据流和变化的,并且是异步的. 维基百科原文: In computing, reactive programming is an ...

  4. 【Spring】spring5新特性

    JDK版本要求 Spring5是基于JDK8编写的,所以JDK8一下无法使用. 核心容器的升级 JDK7和JDK8升级变化 Tomcat要求版本在8.5以上 @NonNull注解和@Nullable注 ...

  5. Spring Cloud Greenwich 新特性和F版升级分享

    来源:https://dwz.cn/LkwPsmut 前几天介绍了,关于Spring Cloud Greenwich版本发布的官方博客翻译:Spring Cloud Greenwich.RELEASE ...

  6. boot入门思想 spring_(第一讲)Spring Initializr-快速入门Spring Boot的最好选择

    1讲:Spring Initializr-快速入门Spring Boot的最好选择 Spring Initializr [http://start.spring.io/]是引导你快速构建Spring ...

  7. Spring入门第一讲——Spring框架的快速入门

    Spring的概述 什么是Spring? 我们可以从度娘上看到这样有关Spring的介绍: 说得更加详细一点,Spring是一个开源框架,Spring是于2003年兴起的一个轻量级的Java开发框架, ...

  8. 孙帅suns的Spring第一集总结

    -----------------[B站孙帅suns的Spring源码第一集总结] 对Spring生态的认知 1.对Spring的认知 1.时至今日我们认为Spring可能就是 解决方案.技术栈.全家 ...

  9. 视频教程-Java进阶高手课-Spring精讲精练-Java

    [ [这里是图片001] Java进阶高手课-Spring精讲精练 中国科学技术大学硕士研究生,丹麦奥尔堡大学访问学者,先后就职于eBay.蚂蚁金服.SAP等国内外一线互联网公司,在Java后端开发. ...

  10. Spring第一章笔记

    Spring第一章笔记 第一步: 配置pom文件导入依赖 <dependencies><dependency><groupId>org.springframewor ...

最新文章

  1. python 绘制折线图-怎样用python绘制折线图
  2. 摘抄《天龙八部》诗词回目
  3. android tv 源代码,android_tv_metro
  4. es scroll 时间_游标查询 Scroll | Elasticsearch: 权威指南 | Elastic
  5. Map集合知识点(炸窝)
  6. mac sqlite可视化工具_Navicat for SQLite 12 for mac(强大数据库管理及开发工具)
  7. debug安装包安装在别人手机上闪退?
  8. 别总抱怨自己怀才不遇,告诉你将才与帅才的12个差别!
  9. Linux 与 Unix 系统的区别
  10. 携程帐号变更函(对私)
  11. YOLO v2论文笔记
  12. 去掉ubuntu下windows文件夹绿色背景
  13. 触屏计算机显示器CDU,触摸屏显示器是什么 触摸屏显示器怎么样【详解】
  14. CASIA情感数据库
  15. 关于计算机毕业后能从事的岗位,以及工作内容。
  16. PDF的Adobe Acrobat的开源替代品
  17. 电机转速采集装置设计
  18. 1978年的图灵奖获得者-Robert W. Floyd
  19. 新托福写作:单一观点类题型写法
  20. eclipse项目的删除

热门文章

  1. 2017年的Microsoft Imagine Cup提供的免费Azure申请及使用方法
  2. 交换机access接口
  3. 计算机语言中print是什么意思,PASCAL 语言中print是什么意思?
  4. 由课堂思考生活(作者:张子逸)
  5. Jetbrain Rider的一些相关设置
  6. JetBrain系列好用的插件
  7. mysql面试题总结_mysql面试题小结
  8. inflate使用方法总结
  9. 电脑白屏,电脑白屏是怎么回事?是系统的原因还是
  10. Caused by: org.activiti.engine.ActivitiException: resource ‘org/activiti/db/create/activiti.dm.creat