Spring第一讲:谈谈你对Spring的理解?从该问题着手深入解析Spring/Spring5新特性
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, 去创建代理对象,原理如下图所示
- 核心的两个类是
InvocationHandler
和Proxy
,优势是最小化依赖关系,减少依赖意味着简化开发和维护,JDK本身的支持,可能比CGLIB更加可靠
方式2:CGLIB:针对没有实现接口的类产生代理,采用的是底层的字节码增强技术,在运行期间生成当前类的子类对象,底层是依靠ASM(开源的java字节码编辑类库)操作字节码实现的,性能比JDK强
- 没有实现接口的对象, 就无法使用 JDK Proxy 去进行代理了, 这时候 Spring AOP 会
使用 Cglib 生成一个被代理对象的子类来作为代理
- 没有实现接口的对象, 就无法使用 JDK Proxy 去进行代理了, 这时候 Spring AOP 会
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 相关
- @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整合
Dao层
pojo和映射文件以及接口使用逆向工程生成(有各种工具);
SqlMapConfig.xml用于配置mybatis核心配置文件;
ApplicationContext-dao.xml整合spring在dao层的配置(数据源、会话工厂、扫描Mapper);service层
事务 ApplicationContext-trans.xml
@Service注解扫描 ApplicationContext-service.xmlcontroller层
SpringMvc.xml
注解扫描:扫描@Controller注解
注解驱动:替我们显示的配置最新版的处理器映射器(handlerMapping)和处理器适配器(handlerAdapter)
视图解析器:显示的配置是为了在controller中不用每个方法都写页面的全路径(requestMapping)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新特性相关推荐
- Spring第一讲:初步了解Spring
一.什么是Spring,什么是bean? Spring是一种框架,Spring框架主要提供了IoC容器.AOP.数据访问.Web开发.消息.测试等相关技术的支持.每一个没Spring管理的 Java对 ...
- JUC第一讲:juc并发包深入理解(P6熟练 P7精通)
并发编程并不是 Java 特有的语言特性,它是一个通用且早已成熟的领域.Java 只是根据自身情况做了实现罢了.并发编程可以总结为三个核心问题:分工.同步.互斥.分工指的是如何高效地拆解任务并分配给线 ...
- 玩转Spring—Spring5新特性之Reactive响应式编程实战
1 什么是响应式编程 一句话总结:响应式编程是一种编程范式,通用和专注于数据流和变化的,并且是异步的. 维基百科原文: In computing, reactive programming is an ...
- 【Spring】spring5新特性
JDK版本要求 Spring5是基于JDK8编写的,所以JDK8一下无法使用. 核心容器的升级 JDK7和JDK8升级变化 Tomcat要求版本在8.5以上 @NonNull注解和@Nullable注 ...
- Spring Cloud Greenwich 新特性和F版升级分享
来源:https://dwz.cn/LkwPsmut 前几天介绍了,关于Spring Cloud Greenwich版本发布的官方博客翻译:Spring Cloud Greenwich.RELEASE ...
- boot入门思想 spring_(第一讲)Spring Initializr-快速入门Spring Boot的最好选择
1讲:Spring Initializr-快速入门Spring Boot的最好选择 Spring Initializr [http://start.spring.io/]是引导你快速构建Spring ...
- Spring入门第一讲——Spring框架的快速入门
Spring的概述 什么是Spring? 我们可以从度娘上看到这样有关Spring的介绍: 说得更加详细一点,Spring是一个开源框架,Spring是于2003年兴起的一个轻量级的Java开发框架, ...
- 孙帅suns的Spring第一集总结
-----------------[B站孙帅suns的Spring源码第一集总结] 对Spring生态的认知 1.对Spring的认知 1.时至今日我们认为Spring可能就是 解决方案.技术栈.全家 ...
- 视频教程-Java进阶高手课-Spring精讲精练-Java
[ [这里是图片001] Java进阶高手课-Spring精讲精练 中国科学技术大学硕士研究生,丹麦奥尔堡大学访问学者,先后就职于eBay.蚂蚁金服.SAP等国内外一线互联网公司,在Java后端开发. ...
- Spring第一章笔记
Spring第一章笔记 第一步: 配置pom文件导入依赖 <dependencies><dependency><groupId>org.springframewor ...
最新文章
- python 绘制折线图-怎样用python绘制折线图
- 摘抄《天龙八部》诗词回目
- android tv 源代码,android_tv_metro
- es scroll 时间_游标查询 Scroll | Elasticsearch: 权威指南 | Elastic
- Map集合知识点(炸窝)
- mac sqlite可视化工具_Navicat for SQLite 12 for mac(强大数据库管理及开发工具)
- debug安装包安装在别人手机上闪退?
- 别总抱怨自己怀才不遇,告诉你将才与帅才的12个差别!
- Linux 与 Unix 系统的区别
- 携程帐号变更函(对私)
- YOLO v2论文笔记
- 去掉ubuntu下windows文件夹绿色背景
- 触屏计算机显示器CDU,触摸屏显示器是什么 触摸屏显示器怎么样【详解】
- CASIA情感数据库
- 关于计算机毕业后能从事的岗位,以及工作内容。
- PDF的Adobe Acrobat的开源替代品
- 电机转速采集装置设计
- 1978年的图灵奖获得者-Robert W. Floyd
- 新托福写作:单一观点类题型写法
- eclipse项目的删除
热门文章
- 2017年的Microsoft Imagine Cup提供的免费Azure申请及使用方法
- 交换机access接口
- 计算机语言中print是什么意思,PASCAL 语言中print是什么意思?
- 由课堂思考生活(作者:张子逸)
- Jetbrain Rider的一些相关设置
- JetBrain系列好用的插件
- mysql面试题总结_mysql面试题小结
- inflate使用方法总结
- 电脑白屏,电脑白屏是怎么回事?是系统的原因还是
- Caused by: org.activiti.engine.ActivitiException: resource ‘org/activiti/db/create/activiti.dm.creat