谈谈你对Spring的理解?从该问题着手深入解析Spring/Spring5新特性/SpringBoot

谈谈你对Spring的理解? 深入解析Spring框架

摘要:面试时常常被面试官问到这个问题,题目太大了,我会从Spring架构/bean初始化流程/bean生命周期/设计模式这几个方面来回应面试官。Spring框架中的核心技术:控制反转/依赖注入/面向切面编程/Spring的声明式事务/以及Spring生态相关产品的简介,这篇文章会详细说明。
说起Spring,绝对是Java开发领域的佼佼者,试问,做Java开发的有谁不知道Spring?做Java开发的又有谁没用过Spring?又有哪家公司在Java Web项目中没使用过Spring?所以,骚年,如果你选择了Java开发这条不归路,你就必须牢牢掌握Spring!
帮助解决别人的Spring问题是学些Spring的好方法。

文章目录

  • 谈谈你对Spring的理解? 深入解析Spring框架
      • 推荐资料
      • 1、为什么学习 Spring?
      • 1、什么是Spring? (在servlet3.0中引入了异步处理,在3.1中引入了非阻塞IO进一步增强性能)
      • 2、Spring容器的主要核心是:
        • 2.1、控制反转(IOC) ? 20181123
        • 2.2、依赖注入(DI是IOC的基础)的原理?
        • 2.3、面向切面编程(AOP)
        • 2.4、BeanFactory与AppliacationContext有什么区别(对象工厂)?
        • 2.5、Spring按类型自动装配注入数组、集合、Map 20210306补
        • 2.6 Spring 使用@Autowired注入与使用构造函数注入区别
      • 3、Spring中Bean的作用域和生命周期? **好问题** 还有一种提问的方法:spring bean的回收过程/applicationContext的初始化机制?
      • 4、Spring是如何获取对象的?springBean加载流程? 如何解决循环依赖问题 20181025有赞
      • 5、Spring中的设计模式?
        • 5.1、工厂设计模式
        • 5.2、单例模式(非常常用)
        • 5.3、依赖注入
        • 5.4、模板方法
        • 5.5、观察者模式
        • 5.6、Spring MVC中的适配器模式
        • 5.7、装饰者模式(类似于标准中心的facade门面)
        • 5.8、总结
      • 6、Spring/Springboot的常用注解?
        • 6.1、前置知识点
        • 6.2、最关键的注解
        • 6.3、常用的注解 - Spring Bean 相关
        • 6.4、处理常见的 HTTP 请求类型
        • 6.5、前后端传值
        • 6.6、读取配置信息
        • 6.7、参数校验
        • 6.8、全局处理 Controller 层异常
        • 6.9、事务 @Transactional
        • 6.10、json 数据处理
        • 6.11、测试相关
        • 6.12、自定义Spring注解
      • 7、Spring结构图 (6大模块)及源码分析
        • 7.1 Spring结构图 图片来源于《Spring源码深度解析》
        • 7.2 Spring的结构组成
      • 8、 请描述一下Spring的事务(事务的实现方式+事务底层原理)? 20181222补***
        • 1、Spring支持两种类型事务管理:
        • 2、配置注解事务
        • 3、编程式事务与声明式事务的区别:
        • 4、@Transactional介绍
        • 5、Spring事务的底层实现原理? 明天补充20181222
        • 6、 六种 @Transactional 注解失效场景
      • 9、Spring事务的传播属性(propagation)是怎么回事?它会影响什么? (属于事务的一种属性)
      • 10、Spring中BeanFactory和FactoryBean有什么区别?
      • 11、Spring的事件(Bean与Bean之间消息通信)
      • 12、Spring高级话题(aware/多线程/计划任务/条件注解/组合注解/Enable原理)
        • 12.1 如何使用restTemplate来提交get请求或post请求?如何调用开放平台接口
      • 13、框架spring (全家桶) springMVC(RESTful) mybatis struts2 hibernate 整合问题
      • 14、SpringMvc执行流程及工作原理 喜闻乐见的面试题
      • 15、SpringMVC常用注解都有哪些?
      • 16、如何解决get和post乱码问题?
      • 17、 参数绑定(从请求中接收参数) 重点
      • 18、Rest和Http什么关系? 大家都说Rest很轻量,你对Rest风格如何理解?
      • 18、 SpringMVC或Struts处理请求流程区别?
      • 19、SpringMVC全局异常处理/整个系统只有一个
      • 20、SpringMvc 拦截器用过吗?什么场景会用到,过滤器,拦截器,监听器有什么区别?
      • 21、SpringMVC的定制配置
      • 22、Spring其他产品(SpringBoot(简化了Spring配置文件,无需再使用springMVC,只需要引入相关的starter即可),Spring cloud,Spring security(同类产品shiro,更容易使用),Spring data(持久层通用解决方案),Spring AMQP)
        • 22.1、关注的点:
        • 22.2、Springboot的精髓(**@Conditonal是核心**)
        • 22.3、Springboot的整合技术
        • 22.4、Springboot 单元测试
        • 22.5、Springboot自定义starter
        • 22.6、Springboot与Redis缓存
        • 22.7 Springboot与消息中间件
        • 22.8 Springboot与任务 20210307补
        • 22.9、Springboot与ElasticSearch
        • 22.10 Springboot与安全
        • 22.11、Springboot与分布式
        • 22.12、Springboot与热部署
        • 22.13、Springboot与监控管理actuator
        • 22.14、Springboot与Spring比较?
        • 22.15、Springboot配置加载优先级?
      • 23、Spring cloud 可以看作是在Spring Boot基础上发展出的更加高层次的框架,它提供了构建分布式系统的通用模式,包含服务发现和服务注册、分布式配置管理、负载均衡、分布式诊断等各种子系统,可以简化微服务系统的构建
      • 24、tomcat和Spring是怎么交互的?20181025有赞
      • 25、Spring各jar包的作用? (20181104)

推荐资料

todo

1、为什么学习 Spring?

todo
Spring 源码设计精妙、结构清晰,对 Java 设计模式灵活运用,是学习Java技术的最佳实践范例。

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

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

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

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

2.1、控制反转(IOC) ? 20181123

1、什么是控制反转?

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

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

2、实例化一个java对象的三种方式:

  • 1、使用类构造器
  • 2、使用静态工厂方法
  • 3、使用实例工厂方法

3、Spring框架中IOC的原理是什么?及其实现方案?20181222

  • 控制反转 控制权由对象本身转向Spring容器,由容器根据配置文件去创建实例并创建各个实例之间的依赖关系
  • 核心1、bean工厂解耦合;在Spring中,bean工厂创建的各个实例称作bean init-method,intilizingbean接口方法afterPropertiesSet的先后顺序,即由容器动态的将某个依赖关系注入到组件之中,对象只是被动的接受依赖对象,所以是反转。
  • 2、xml解析 解析xml文件进一步降低耦合度
  • 3、反射 通过反射创建对象

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

1、什么是依赖注入?

  • Spring使用javaBean对象的set方法或者带参数的构造方法为我们在创建所需对象时,将其属性自动设置所需要的值的过程,就是依赖注入的思想
  • DI:组件之间依赖关系由容器在运行期决定(依赖注入是控制反转的基础)

2、spring的依赖注入有哪几种方式

  • 1、构造器注入 带参数的构造函数
    前置条件:对象中使用有参构造方法来注入属性

<bean id ="orders" class ="cn.com.zcy.User">
    
	<constructor-arg name = "name"  value = "电脑">constructor-arg>
	<constructor-arg name = "address" value = "apple">constructor-arg>
bean>
  • 2、Setter方法属性注入 通过JavaBean属性注入依赖关系 (或者Autwired方法)
    前置条件:对象中使用setter方法来注入属性

<bean id ="user" class ="cn.com.zcy.User">
	
	<property name ="age"  value = "27">property>
	<property name = "author" value = "qiwenjie">property>
bean>
  • 3、接口注入 需要额外接口类,不提倡

2.3、面向切面编程(AOP)

1、什么是AOP?

  • 在面向对象编程(oop)思想中,我们将事物纵向抽成一个个的对象。而在面向切面编程中,我们将各个对象某些类似的方面横向抽成一个切面,对这个切面进行一些如权限控制、事物管理,缓存处理,记录日志等公用操作处理的过程就是面向切面编程的思想

突然想到的,在标准中心项目中,记录操作类目、品牌下架日志的需求中,也可以采用该AOP方式来处理,在旗舰店的校验逻辑中,也可以采用AOP方式来实现,之前自己真的是傻乎乎的,采用的硬编码的方式。

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

2、AOP实现原理是什么?

  • JDK的动态代理(默认使用):针对实现了接口的类产生代理,运行时动态生成被调用类型的子类;

  • 谈谈你对Spring的理解?从该问题着手深入解析Spring/Spring5新特性/SpringBoot_第1张图片

  • CGLIB:针对没有实现接口的类产生代理,采用的是底层的字节码增强技术,生成当前类的子类对象

  • 谈谈你对Spring的理解?从该问题着手深入解析Spring/Spring5新特性/SpringBoot_第2张图片

AOP术语

1、连接点 joinPoint

  • 类里面的那些方法可以被增强,这些方法称为连接点

2、切入点

  • 实际被真正增强的方法,称为切入点

3、通知(增强) Advise

  • 实际增强的逻辑部分被称为通知(增强)
  • 通知有多种类型 前置通知 后置通知 环绕通知 异常通知 最终通知(finally)

4、切面

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

3、AOP解决了什么问题?

  • 1、它让我们将业务逻辑从应用服务(如事务管理)中分离出来,应用对象只关注业务逻辑,不再负责其它系统问题(如日志事务等);
  • 2、从Spring的角度看,AOP最大的用途就在于提供了事务管理的能力;
  • 3、Spring在你访问数据库之前,自动帮你开启事务,当你访问数据库结束之后,自动帮你提交/回滚事务。

4、AOP解释

  • AOP利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑,封装起来,便于减少系统的重复代码,降低模块之间的耦合度。

5、AOP的实现原理/以及两种实现方式(CGLIB和JDK动态代理)的区别?

  • 面向切面编程 对切入点的方法进行增强(通用日志处理/通用安全策略/通用事务框架

两种实现方式区别?

  • 1、JDK动态代理:
    其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理,其核心的两个类是InvocationHandler和Proxy,优势是最小化依赖关系,减少依赖意味着简化开发和维护,JDK本身的支持,可能比CGLIB更加可靠。

  • 2、CGLIB代理:
    实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类,CGLIB是高效的代码生成包,底层是依靠ASM(开源的java字节码编辑类库)操作字节码实现的,性能比JDK强。使用AspectJ注入式切面和@AspectJ注解驱动的切面实际上底层也是通过动态代理实现的。CGLIB的优势是有的时候调用目标可能不便实现额外接口,从某种角度看,限定调用者实现接口是有些侵入性的实践,类似CGLIB动态代理就没有这种限制,只操作我们关心的类。

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

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

编写 JDK 动态代理代码

  • (1)创建接口,定义方法
public interface UserDao {
     
 	public int add(int a,int b);
 	public String update(String id);
}
  • (2)创建接口实现类,实现方法
public class UserDaoImpl implements UserDao {
     
 	@Override
 	public int add(int a, int b) {
     
 		return a+b;
 	}
 	@Override
 	public 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;
 	}
 	//增强的逻辑
 	@Override
 	public 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.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 {
     
 
    @Autowired
    private MediaPlayer[] mediaPlayerArr;
 
    @Autowired
    private List<MediaPlayer> mediaPlayerList;
 
    @Autowired
    private Map<String, MediaPlayer> mediaPlayerMap;
 
    @Autowired
    public 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注入与使用构造函数注入区别


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

3、生命周期 其实不重要,因为这些接口我们并不会去实现
通过配置标签上的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实现了 IntializingBean接口 了,调用它的afterPropertySet方法,//同样:如果bean声明了初始化方法init-method,调用此初始化方法。 在afterPropertySet方法里面,可以给bean的变量赋初始值 – 利用这点可以执行测试用例
8. 如果bean实现了BeanPostProcessors接口 ,postProcessAfterInitialization() 方法将被调用。
9. 此时,bean已经准备就绪,可以被应用程序使用了,他们一直驻留在应用上下文中,直至该应用上下文被销毁。
10、如果 bean 实现了 DisposableBean,它将调用destroy()方法。//同样:如果bean使用destroy-method声明了销毁方法,该方法也会被被调用。

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中的共享变量,可能会出现数据错乱的问题;所以使用的时候需要特别注意。

8、多实例bean注意的事项

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

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

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的引用

5、Spring中的设计模式?

1、代理模式(proxy)

  • spring中两种代理方式,
    1、若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.proxy类代理,
    2、若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。

5.1、工厂设计模式

Spring使用工厂模式可以通过 BeanFactoryApplicationContext 创建 bean 对象。
两者对比:
BeanFactory :延迟注入(使用到某个 bean 的时候才会注入),相比于ApplicationContext 来说会占用更少的内存,程序启动速度更快。
ApplicationContext :容器启动的时候,不管你用没用到,一次性创建所有 bean 。BeanFactory 仅提供了最基本的依赖注入支持, ApplicationContext 扩展了 BeanFactory ,除了有BeanFactory的功能还有额外更多功能,所以一般开发人员使用 ApplicationContext会更多。
ApplicationContext的三个实现类:

  • ClassPathXmlApplication:把上下文文件当成类路径资源。
  • FileSystemXmlApplication:从文件系统中的 XML 文件载入上下文定义信息。
  • XmlWebApplicationContext:从Web系统中的XML文件载入上下文定义信息。
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class App {
     
    public static void main(String[] args) {
     
        ApplicationContext context = new FileSystemXmlApplicationContext(
                "C:/work/IOC Containers/springframework.applicationcontext/src/main/resources/bean-factory-config.xml");
        HelloApplicationContext obj = (HelloApplicationContext) context.getBean("helloApplicationContext");
        obj.getMsg();
    }
}

5.2、单例模式(非常常用)

在我们的系统中,有一些对象其实我们只需要一个,比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。

使用单例模式的好处:

  • 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
  • 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。

Spring 中 bean 的默认作用域就是 singleton(单例)的。除了 singleton 作用域,Spring 中 bean 还有下面几种作用域:

  • prototype : 每次请求都会创建一个新的 bean 实例。
  • request : 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
  • session : 每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效。
  • global-session: 全局session作用域,仅仅在基于portlet的web应用中才有意义,Spring5已经没有了。Portlet是能够生成语义代码(例如:HTML)片段的小型Java Web插件。它们基于portlet容器,可以像servlet一样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话

Spring 实现单例的方式:

xml : <bean id="userService" class="top.snailclimb.UserService" scope="singleton"/>
  • 注解:@Scope(value = “singleton”)

Spring 通过 ConcurrentHashMap 实现单例注册表的特殊方式实现单例模式。Spring 实现单例的核心代码如下

// 通过 ConcurrentHashMap(线程安全) 实现单例注册表
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
     
        Assert.notNull(beanName, "'beanName' must not be null");
        synchronized (this.singletonObjects) {
     
            // 检查缓存中是否存在实例  
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
     
                //...省略了很多代码
                try {
     
                    singletonObject = singletonFactory.getObject();
                }
                //...省略了很多代码
                // 如果实例对象在不存在,我们注册到单例注册表中。
                addSingleton(beanName, singletonObject);
            }
            return (singletonObject != NULL_OBJECT ? singletonObject : null);
        }
    }
    //将对象添加到单例注册表
    protected void addSingleton(String beanName, Object singletonObject) {
     
            synchronized (this.singletonObjects) {
     
                this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
            }
   }
}

5.3、依赖注入

  • 贯穿于BeanFactory/ApplacationContext(ioc的容器) 接口的核心理念。

5.4、模板方法

模板方法模式是一种行为设计模式,它定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。 模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤的实现方式

public abstract class Template {
     
    //这是我们的模板方法
    public final void TemplateMethod(){
     
        PrimitiveOperation1();  
        PrimitiveOperation2();
        PrimitiveOperation3();
    }
    protected void  PrimitiveOperation1(){
     
        //当前类实现
    }

    //被子类实现的方法
    protected abstract void PrimitiveOperation2();
    protected abstract void PrimitiveOperation3();

}
public class TemplateImpl extends Template {
     
    @Override
    public void PrimitiveOperation2() {
     
        //当前类实现
    }
    @Override
    public void PrimitiveOperation3() {
     
        //当前类实现
    }
}

Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。一般情况下,我们都是使用继承的方式来实现模板模式,但是 Spring 并没有使用这种方式,而是使用Callback 模式与模板方法模式配合,既达到了代码复用的效果,同时增加了灵活性。

5.5、观察者模式

观察者模式是一种对象行为型模式。它表示的是一种对象与对象之间具有依赖关系,当一个对象发生改变的时候,这个对象所依赖的对象也会做出反应。Spring 事件驱动模型就是观察者模式很经典的一个应用。Spring 事件驱动模型非常有用,在很多场景都可以解耦我们的代码。比如我们每次添加商品的时候都需要重新更新商品索引,这个时候就可以利用观察者模式来解决这个问题。

Spring 事件驱动模型中的三种角色
事件角色

  • ApplicationEvent (org.springframework.context包下)充当事件的角色,这是一个抽象类,它继承了java.util.EventObject并实现了 java.io.Serializable接口。

Spring 中默认存在以下事件,他们都是对 ApplicationContextEvent 的实现(继承自ApplicationContextEvent):

  • ContextStartedEvent:ApplicationContext 启动后触发的事件;
  • ContextStoppedEvent:ApplicationContext 停止后触发的事件;
  • ContextRefreshedEvent:ApplicationContext 初始化或刷新完成后触发的事件;
  • ContextClosedEvent:ApplicationContext 关闭后触发的事件。
    谈谈你对Spring的理解?从该问题着手深入解析Spring/Spring5新特性/SpringBoot_第3张图片
    事件监听者角色
    ApplicationListener 充当了事件监听者角色,它是一个接口,里面只定义了一个 onApplicationEvent()方法来处理ApplicationEvent。ApplicationListener接口类源码如下,可以看出接口定义看出接口中的事件只要实现了 ApplicationEvent就可以了。所以,在 Spring中我们只要实现 ApplicationListener 接口的 onApplicationEvent() 方法即可完成监听事件
package org.springframework.context;
import java.util.EventListener;
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
     
    void onApplicationEvent(E var1);
}

事件发布者角色
ApplicationEventPublisher 充当了事件的发布者,它也是一个接口。

@FunctionalInterface
public interface ApplicationEventPublisher {
     
    default void publishEvent(ApplicationEvent event) {
     
        this.publishEvent((Object)event);
    }
    void publishEvent(Object var1);
}

ApplicationEventPublisher 接口的publishEvent()这个方法在AbstractApplicationContext类中被实现,阅读这个方法的实现,你会发现实际上事件真正是通过ApplicationEventMulticaster来广播出去的。

Spring 的事件流程总结

  • 定义一个事件: 实现一个继承自 ApplicationEvent,并且写相应的构造函数;
  • 定义一个事件监听者:实现 ApplicationListener 接口,重写 onApplicationEvent() 方法;
  • 使用事件发布者发布消息: 可以通过 ApplicationEventPublisher 的 publishEvent() 方法发布消息。
// 定义一个事件,继承自ApplicationEvent并且写相应的构造函数
public class DemoEvent extends ApplicationEvent{
     
    private static final long serialVersionUID = 1L;
    private String message;
    public DemoEvent(Object source,String message){
     
        super(source);
        this.message = message;
    }
    public String getMessage() {
     
         return message;
    }

// 定义一个事件监听者,实现ApplicationListener接口,重写 onApplicationEvent() 方法;
@Component
public class DemoListener implements ApplicationListener<DemoEvent>{
     

    //使用onApplicationEvent接收消息
    @Override
    public void onApplicationEvent(DemoEvent event) {
     
        String msg = event.getMessage();
        System.out.println("接收到的信息是:"+msg);
    }
}
// 发布事件,可以通过ApplicationEventPublisher  的 publishEvent() 方法发布消息。
@Component
public class DemoPublisher {
     
    @Autowired
    ApplicationContext applicationContext;

    public void publish(String message){
     
        //发布事件
        applicationContext.publishEvent(new DemoEvent(this, message));
    }
}

当调用 DemoPublisher 的 publish() 方法的时候,比如 demoPublisher.publish(“你好”) ,控制台就会打印出:接收到的信息是:你好.

5.6、Spring MVC中的适配器模式

在Spring MVC中,DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由HandlerAdapter 适配器处理。HandlerAdapter 作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller 作为需要适配的类

为什么要在 Spring MVC 中使用适配器模式?

  • Spring MVC 中的 Controller 种类众多,不同类型的 Controller 通过不同的方法来对请求进行处理。如果不利用适配器模式的话,DispatcherServlet 直接获取对应类型的 Controller,需要的自行来判断,像下面这段代码一样:
if(mappedHandler.getHandler() instanceof MultiActionController){
       
   ((MultiActionController)mappedHandler.getHandler()).xxx  
}else if(mappedHandler.getHandler() instanceof XXX){
       
    ...  
}else if(...){
       
   ...  
}  

假如我们再增加一个 Controller类型就要在上面代码中再加入一行 判断语句,这种形式就使得程序难以维护,也违反了设计模式中的开闭原则 – 对扩展开放,对修改关闭

5.7、装饰者模式(类似于标准中心的facade门面)

装饰者模式可以动态地给对象添加一些额外的属性或行为。相比于使用继承,装饰者模式更加灵活。简单点儿说就是当我们需要修改原有的功能,但我们又不愿直接去修改原有的代码时,设计一个Decorator套在原有代码外面。其实在 JDK 中就有很多地方用到了装饰者模式,比如 InputStream家族,InputStream 类下有 FileInputStream (读取文件)、BufferedInputStream (增加缓存,使读取文件速度大大提升)等子类都在不修改InputStream 代码的情况下扩展了它的功能。

装饰者模式示意图
谈谈你对Spring的理解?从该问题着手深入解析Spring/Spring5新特性/SpringBoot_第4张图片
Spring 中配置 DataSource 的时候,DataSource 可能是不同的数据库和数据源。我们能否根据客户的需求在少修改原有类的代码下动态切换不同的数据源?这个时候就要用到装饰者模式。Spring 中用到的包装器模式在类名上含有 Wrapper或者 Decorator。这些类基本上都是动态地给一个对象添加一些额外的职责。

5.8、总结

Spring 框架中用到了哪些设计模式?

  • 工厂设计模式 : Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
  • 代理设计模式 : Spring AOP 功能的实现。
  • 单例设计模式 : Spring 中的 Bean 默认都是单例的。
  • 模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
  • 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
  • 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
  • 适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。

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 {
     
   @Autowired
   private 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 {
     
    @Bean
    public TransferService transferService() {
     
        return new TransferServiceImpl();
    }
}

6.4、处理常见的 HTTP 请求类型

5 种常见的请求类型:

  • GET :请求从服务器获取特定资源。举个例子:GET /users(获取所有学生)
  • POST :在服务器上创建一个新的资源。举个例子:POST /users(创建学生)
  • PUT :更新服务器上的资源(客户端提供更新后的整个资源)。举个例子:PUT /users/12(更新编号为 12 的学生)
  • DELETE :从服务器删除特定的资源。举个例子:DELETE /users/12(删除编号为 12 的学生)
  • PATCH :更新服务器上的资源(客户端提供更改的属性,可以看做作是部分更新),使用的比较少。

GET 请求

  • @GetMapping(“users”) 等价于@RequestMapping(value="/users",method=RequestMethod.GET)
@GetMapping("/users")
public ResponseEntity<List<User>> getAllUsers() {
     
 	return userRepository.findAll();
}

POST 请求

  • @PostMapping(“users”) 等价于@RequestMapping(value="/users",method=RequestMethod.POST)
@PostMapping("/users")
public ResponseEntity<User> createUser(@Valid @RequestBody UserCreateRequest userCreateRequest) {
     
 	return userRespository.save(user);
}

PUT 请求

  • @PutMapping("/users/{userId}") 等价于@RequestMapping(value="/users/{userId}",method=RequestMethod.PUT)
@PutMapping("/users/{userId}")
public ResponseEntity<User> updateUser(@PathVariable(value = "userId") Long userId, @Valid @RequestBody UserUpdateRequest userUpdateRequest) {
     
  ......
}

DELETE 请求

  • @DeleteMapping("/users/{userId}")等价于@RequestMapping(value="/users/{userId}",method=RequestMethod.DELETE)
@DeleteMapping("/users/{userId}")
public ResponseEntity deleteUser(@PathVariable(value = "userId") Long userId){
     
  ......
}

PATCH 请求

  • 一般实际项目中,我们都是 PUT 不够用了之后才用 PATCH 请求去更新数据。
 @PatchMapping("/profile")
 public ResponseEntity updateStudent(@RequestBody StudentUpdateRequest studentUpdateRequest) {
     
       studentRepository.updateDetail(studentUpdateRequest);
       return ResponseEntity.ok().build();
}
  • @Qualifier:根据属性名称进行注入 该注解和@Autowired搭配使用,用于消除特定bean自动装配的歧义。
  • @Resource:可以根据类型注入,可以根据名称注入。(不推荐)
  • @Value:注入普通类型属性。示例:
@Value(value="qiwenjie")
private String name;

6.5、前后端传值

掌握前后端传值的正确姿势,是做需求的第一步

  1. @PathVariable 和 @RequestParam
  • @PathVariable用于获取路径参数,@RequestParam用于获取查询参数。

举个简单的例子:

@GetMapping("/klasses/{klassId}/teachers")
public List<Teacher> getKlassRelatedTeachers(
         @PathVariable("klassId") Long klassId,
         @RequestParam(value = "type", required = false) String type ) {
     
...
}
//如果我们请求的 url 是:/klasses/{123456}/teachers?type=web
//那么我们服务获取到的数据就是:klassId=123456,type=web

2、@RequestBody

  • 用于读取 Request 请求(可能是 POST,PUT,DELETE,GET 请求)的 body 部分并且Content-Type 为 application/json 格式的数据接收到数据之后会自动将数据绑定到 Java 对象上去。系统会使用HttpMessageConverter或者自定义的HttpMessageConverter将请求的 body 中的 json 字符串转换为 java 对象。
    我们有一个注册的接口:
@PostMapping("/sign-up")
public ResponseEntity signUp(@RequestBody @Valid UserRegisterRequest userRegisterRequest) {
     
  	userService.save(userRegisterRequest);
  	return ResponseEntity.ok().build();
}

UserRegisterRequest对象:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserRegisterRequest {
     
    @NotBlank
    private String userName;
    @NotBlank
    private String password;
    @NotBlank
    private String fullName;
}

我们发送 post 请求到这个接口,并且 body 携带 JSON 数据:

{
     "userName":"coder","fullName":"shuangkou","password":"123456"}

这样我们的后端就可以直接把 json 格式的数据映射到我们的 UserRegisterRequest 类上
谈谈你对Spring的理解?从该问题着手深入解析Spring/Spring5新特性/SpringBoot_第5张图片

6.6、读取配置信息

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

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

hangzhou2021: 2021快过年了
wade:
  name: qiwenjie
  iphone: 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 {
     
    @NotEmpty
    private String location;
    private List<Book> books;

    @Setter
    @Getter
    @ToString
    static 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.7、参数校验

即使在前端对数据进行校验的情况下,我们还是要对传入后端的数据再进行一遍校验,避免用户绕过浏览器直接通过一些 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 生成):
    谈谈你对Spring的理解?从该问题着手深入解析Spring/Spring5新特性/SpringBoot_第6张图片
  • 注意的是: 所有的注解,推荐使用 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> {
     
    @Override
    public 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)
    @ResponseBody
    public 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> {
     

    @Override
    public boolean isValid(String phoneField, ConstraintValidatorContext context)		   {
     
        if (phoneField == null) {
     
            // can be null
            returntrue;
        }
        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.8、全局处理 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.9、事务 @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.10、json 数据处理

1、过滤 json 数据

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

public class User {
     
    private String userName;
    private String fullName;
    private String password;
   //生成json时将userRoles属性过滤
    @JsonIgnore
    private 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 {
     
    @JsonUnwrapped
    private Location location;
    @JsonUnwrapped
    private PersonInfo personInfo;

  @Getter
  @Setter
  @ToString
  public static class Location {
     
     private String provinceName;
     private String countyName;
  }
  @Getter
  @Setter
  @ToString
  public static class PersonInfo {
     
    private String userName;
    private String fullName;
  }
}

未扁平化之前:

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

使用@JsonUnwrapped 扁平对象之后:

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

6.11、测试相关

  • @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.12、自定义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中注册
2、如果某个类的头上带有特定的注解@Component @Repository @Service @Controller,就会将这个对象作为bean注册进Spring容器。(功能是一样的,只是用在不同层中)
3、在使用spring管理的bean时,无需在对调用的对象进行new的过程,只需使用@Autowired将需要的bean注入本类即可。


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

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

谈谈你对Spring的理解?从该问题着手深入解析Spring/Spring5新特性/SpringBoot_第7张图片
图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的事务(事务的实现方式+事务底层原理)? 20181222补***

1、Spring支持两种类型事务管理:

1、基于 @Transactional 的声明式事务管理:(默认)

  • 只需要用注解和XML配置来管理事务。Spring 2.x 还引入了基于注解的体式格式,具体触及@Transactional注解。@Transactional 可以注解在接口、接口方法、类和类方法上。当作用于类上时,该类的一切 public 方法将都具有该类型的事务属性。
    ----DataSource //jdbc配置(要连接的数据库)
    -----dataSource----SessionFactory //hibernate配置

spring事务配置

-----TransactionManager----datasourcetransactionmanager // jdbc配置
----hibernatetransactionmanager // hibernate配置

代理机制----bean和代理 --每个bean有一个代理                     
                        --所有bean共享一个代理基类
        ----使用拦截器
        ----使用tx标签配置的拦截器
        ----全注解配置

基于TransactionInterceptor的声明式事务管理:
两个次要的属性:
transactionManager,用来指定一个事务治理器,并将具体事务相关的操作请托给它;Properties 类型的transactionAttributes 属性,该属性的每一个键值对中,键指定的是方法名,方法名可以行使通配符,而值就是表现呼应方法的所运用的事务属性。

2、spring事务配置示例(使用tx标签配置的拦截器)(已过时)
步骤:
1、配置事务管理器


 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
     <property name="sessionFactory" ref="sessionFactory">
 <bean/>

2、配置注解事务


 
 <tx:annotation-driven transaction-manager="transactionManager"/>
  
 <aop:config> 
     <aop:pointcut id="interceptorPointCuts" 
             expression="execution(*com.bluesky.spring.dao.*.*(..))"/>
     <aop:advisor advice-ref="txAdvice" 
             pointcut-ref="interceptorPointCuts"/>
 aop:config>

execution 语法结构
execution([权限修饰符][返回类型][类全路径]方法名称)
execution(com.bluesky.spring.dao..*(…)) 所有方法进行增强

3、在业务层上添加一个注解: @Transactional(可以添加一些参数)

@Transactional 
 public class AccountServiceImpl implements AccountService{
     }

3、编程式事务与声明式事务的区别:

1)编程式事务:是指在代码中手动的管理事务的提交、回滚等操作,代码侵入性比较强,如下示例:

try {
     
    //TODO something
     transactionManager.commit(status);
} catch (Exception e) {
     
    transactionManager.rollback(status);
    thrownew InvoiceApplyException("异常失败");
}

2)声明式事务:基于AOP面向切面的,它将具体业务与事务处理部分解耦,代码侵入性很低,所以在实际开发中声明式事务用的比较多。声明式事务也有两种实现方式,一是基于TX和AOP的xml配置文件方式,二种就是基于@Transactional 注解

@Transactional
@GetMapping("/test")
public String test() {
     
    int insert = cityInfoDictMapper.insert(cityInfoDict);
}

4、@Transactional介绍

1、@Transactional注解可以作用于哪些地方?
@Transactional 可以作用在接口、类、类方法。

  • 作用于类:当把@Transactional 注解放在类上时,表示所有该类的public方法都配置相同的事务属性信息。
  • 作用于方法:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息。
  • 作用于接口:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效
@Transactional
@RestController
@RequestMapping
publicclass MybatisPlusController {
     
    @Autowired
    private CityInfoDictMapper cityInfoDictMapper;
    @Transactional(rollbackFor = Exception.class)
    @GetMapping("/test")
    public String test() throws Exception {
     
        CityInfoDict cityInfoDict = new CityInfoDict();
        cityInfoDict.setParentCityId(2);
        cityInfoDict.setCityName("2");
        cityInfoDict.setCityLevel("2");
        cityInfoDict.setCityCode("2");
        int insert = cityInfoDictMapper.insert(cityInfoDict);
        return insert + "";
    }
}

2、@Transactional注解有哪些属性?

  • propagation属性
    propagation 代表事务的传播行为默认值为 Propagation.REQUIRED,其他的属性信息如下:

  • Propagation.REQUIRED:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。( 也就是说如果A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法的事务合并为一个事务

  • Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。

  • Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。

  • Propagation.REQUIRES_NEW:重新创建一个新的事务,如果当前存在事务,暂停当前的事务。( 当类A中的 a 方法用默认Propagation.REQUIRED模式,类B中的 b方法加上采用 Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 b方法操作数据库,然而 a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停 a方法的事务 )

  • Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务。

  • Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常。

  • Propagation.NESTED :和 Propagation.REQUIRED 效果一样。

  • isolation 属性
    isolation:事务的隔离级别,默认值为 Isolation.DEFAULT。

  • TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.

  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读.

  • TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生

  • TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。(什么是脏读、不可重复读、幻读,可以看mysql

  • TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别

timeout 属性

  • timeout:事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。

readOnly 属性

  • readOnly:指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。

rollbackFor 属性

  • rollbackFor:用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。

noRollbackFor属性

  • noRollbackFor:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。

5、Spring事务的底层实现原理? 明天补充20181222

使用aop实现的。aop在进行解析的时候,最终生成一个Advisor对象,这个Advisor对象中封装了切面织入所需要的所有信息,其中就包括Aspect:他是跨不同java类层面的横切性逻辑,实现形式上,可以是XML文件中配置的普通类,也可在类代码中用“@Aspect”注解声明,运行时spring框架创建Advisor来指代他:(源码中对应BeanFactoryTransactionAttributeSourceAdvisor)

Advisor最重要的两个部分:PointCut和Advice属性。

  • 切入的时机pointCut:判断目标bean是否需要织入当前事务逻辑;(为了使切点复用,利用@PointCut专门定义拦截规则)代码中对应TransactionAttributeSourcePointcut
  • 切入的动作Advice:封装了需要织入的切面逻辑 ;代码中对应TransactionInterceptor

Join Point:它是Aspect可以切入的特定点,在Spring里面只有方法可以作为Join Point(是可利用的机会,具体由pointcut指定)
Advice:它定义了切面中能够采取的动作。如果去看Spring源码,就会发现Advice、Join Point并没有定义在Spring自己的命名空间里,这是因为他们是源自AOP联盟,可以看作是Java工程师在AOP层面沟通的通用规范

  • 通常将拦截器类型的Advice叫作Around,在代码中可以使用“@Around”来标记,或者在配置中使用“ < aop:around>”

分别解释这三个类:

  • BeanFactoryTransactionAttributeSourceAdvisor:封装了实现事务所需的所有属性,包括pointCut,advice,transactionManager以及一些其他在Transactional注解中声明的属性;
  • TransactionAttributeSourcePointcut:判断目标bean是否需要织入当前事务逻辑,依据就是当前方法或类声明上有没有使用@Transactional注解;
  • TransactionInterceptor : 封装了需要织入的切面逻辑 ,Spring事务是借助数据库事务来实现对目标方法的环绕的。

总结:Spring支持AspectJ的注解式切面编程
1、使用@Aspect声明是一个切面;
2、使用@After、@Before、@Around定义建言(advice),可直接将拦截规则(切点)作为参数;
3、其中@After、@Before、@Around参数的拦截规则为切点(PointCut),为了使切点复用,可使用@PointCut专门定义拦截规则;
4、其中符合条件的每一个被拦截处为连接点(JointPoint)

示例:
1、添加Spring AOP支持及AsectJ依赖

  • aop/aspectjrt/aspectjweaver

2、编写拦截规则的注解(编写注解)

 @Target(ElementType.METHOD)
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 public @interface Action{
     
     String name();//注解是一种元数据,即解释数据的数据
 }

3、编写使用注解的被拦截类(使用注解)

@Service
 public class DemoAnnotationService{
     
     @Action(name="拦截式拦截的add操作")
     public void add(){
     }
 }

4、编写切面

@Aspect      //切面
@Component   //让此切面加入IOC容器
public class LogAspect{
     
    @PointCut("@annotation(com.wisely.aop.Action)")
    public void annotationPointCut(){
     };
    //后置处理逻辑,在切入点方法执行后执行
    @After("anntationPointCut")
    public void after(JointPoint jointPoint){
     
        MethodSignature signature=(MethodSignature)jointPoint.getSignature();
        Method method =getSignature.getMethod();
        Action action = method.getAnnotation(Action.class);
        sout("Q"+action.name());
    }
}

5、配置类

  • 使用@EnableAspectJAutoProxy注解开启Spring对AspectJ的支持

6、运行


6、 六种 @Transactional 注解失效场景

使用@Transactional注解时需要注意许多的细节,不然你会发现@Transactional总是莫名其妙的就失效了

  • 1、@Transactional 应用在非 public 修饰的方法上
    如果Transactional注解应用在非public 修饰的方法上,Transactional将会失效
    谈谈你对Spring的理解?从该问题着手深入解析Spring/Spring5新特性/SpringBoot_第8张图片
    之所以会失效是因为在Spring AOP 代理时,如上图所示 TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute 方法,获取Transactional 注解的事务配置信息。
protected TransactionAttribute computeTransactionAttribute(Method method,
    Class<?> targetClass) {
     
        // Don't allow no-public methods as required.
        if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
     
        return null;
}
  • 此方法会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。
    注意:protected、private 修饰的方法上使用 @Transactional 注解,虽然事务无效,但不会有任何报错,这是我们很容犯错的一点

  • 2、@Transactional 注解属性 propagation 设置错误
    这种失效是由于配置错误,若是错误的配置以下三种 propagation,事务将不会发生回滚。

    • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
    • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
    • TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  • 3、@Transactional 注解属性 rollbackFor 设置错误
    rollbackFor 可以指定能够触发事务回滚的异常类型Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性。
    谈谈你对Spring的理解?从该问题着手深入解析Spring/Spring5新特性/SpringBoot_第9张图片

// 希望自定义的异常可以进行回滚
@Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class
  • 若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。Spring 源码如下:
private int getDepth(Class<?> exceptionClass, int depth) {
     
    if (exceptionClass.getName().contains(this.exceptionName)) {
     
          // Found it!
          return depth;
	}
    // If we've gone as far as we can go and haven't found it...
    if (exceptionClass == Throwable.class) {
     
        return -1;
	}
	return getDepth(exceptionClass.getSuperclass(), depth + 1);
}
  • 4、同一个类中方法调用,导致@Transactional失效(开发中遇到过这种场景
    开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方

那为啥会出现这种情况?其实这还是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理

//@Transactional
    @GetMapping("/test")
    private Integer A() throws Exception {
     
        CityInfoDict cityInfoDict = new CityInfoDict();
        cityInfoDict.setCityName("2");
        /**
         * B 插入字段为 3的数据
         */
        this.insertB();
        /**
         * A 插入字段为 2的数据
         */
        int insert = cityInfoDictMapper.insert(cityInfoDict);

        return insert;
    }

    @Transactional()
    public Integer insertB() throws Exception {
     
        CityInfoDict cityInfoDict = new CityInfoDict();
        cityInfoDict.setCityName("3");
        cityInfoDict.setParentCityId(3);

        return cityInfoDictMapper.insert(cityInfoDict);
    }

  • 5、异常被你的 catch“吃了”导致@Transactional失效
    这种情况是最常见的一种 @Transactional 注解失效场景
@Transactional
private Integer A() throws Exception {
     
    int insert = 0;
    try {
     
        CityInfoDict cityInfoDict = new CityInfoDict();
        cityInfoDict.setCityName("2");
        cityInfoDict.setParentCityId(2);
        /**
         * A 插入字段为 2的数据
         */
        insert = cityInfoDictMapper.insert(cityInfoDict);
        /**
         * B 插入字段为 3的数据
         */
        b.insertB();
    } catch (Exception e) {
     
        e.printStackTrace();
    }
}

如果B方法内部抛了异常,而A方法此时try catch了B方法的异常,那这个事务还能正常回滚吗?
答案:不能!
会抛出异常:

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
  • 因为当ServiceB中抛出了一个异常以后,ServiceB标识当前事务需要rollback。但是ServiceA中由于你手动的捕获这个异常并进行处理,ServiceA认为当前事务应该正常commit。此时就出现了前后不一致,也就是因为这样,抛出了前面的UnexpectedRollbackException异常。

  • spring的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行commit or rollback,事务是否执行取决于是否抛出runtime异常。如果抛出runtime exception 并在你的业务方法中没有catch到的话,事务会回滚。

  • 在业务方法中一般不需要catch异常,如果非要catch一定要抛出throw new RuntimeException(),或者注解中指定抛异常类型@Transactional(rollbackFor=Exception.class),否则会导致事务失效,数据commit造成数据不一致,所以有些时候 try catch反倒会画蛇添足。

  • 6、数据库引擎不支持事务
    这种情况出现的概率并不高,事务能否生效数据库引擎是否支持事务是关键。常用的MySQL数据库默认使用支持事务的innodb引擎。一旦数据库引擎切换成不支持事务的myisam,那事务就从根本上失效了。


9、Spring事务的传播属性(propagation)是怎么回事?它会影响什么? (属于事务的一种属性)

Spring 对事务控制的支持统一在 TransactionDefinition 类中描述,该类有以下几个重要的接口方法:

  • int getPropagationBehavior():事务的传播行为:就是多个事务方法相互调用时,事务如何在这些方法间传播
    REQUIRED 支持当前事务,不存在就新建一个 默认(默认的隔离级别与各个数据库一致)
    required new 如果有当前事务,挂起当前事务,创建一个新的事务
    nested 如果有当前事务,嵌套事务执行
    假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这3个服务类的3个方法通过Spring的事务传播机制都工作在同一个事务中
    int getIsolationLevel():事务的隔离级别
    int getTimeout():事务的过期时间
    boolean isReadOnly():事务的读写特性。

10、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 文件读取配置元数据并用它去创建一个完全配置的系统或应用。


11、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{
     
     @Autowired
     ApplicationContext applicationContext;  //用于发布事件
     applicationContext.publishEvent(new DemoEvent(this,msg));
     
 }

(4)配置类

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

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

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

12、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

12.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;
    @Autowired
    protected TestRestTemplate restTemplate;
    protected AuthRequestInterceptor authRequestInterceptor;

    public ControllerTestBase() {
     
        log.info("ControllerTestBase created");
    }
    @PostConstruct
    public 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();
    }
    @Bean
    public 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 {
     

    @Autowired
    private xxxClient xxxrClient;
    @Test
    public 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()));
    }

    @Test
    public 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;
}




13、框架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监听


14、SpringMvc执行流程及工作原理 喜闻乐见的面试题

1、springMvc是什么?(市场占有率40%,web开发当之无愧的霸主)

  • 一个表现层框架,就是从请求中接收传入的参数,然后将处理后的结果数据返回给页面展示

2、springMvc执行流程(已经滚瓜乱熟了)

  • a. 用户向服务器发送请求,请求被springMVC前端控制器DispatchServlet捕获;
  • b. DispatcherServle对请求URL进行解析,得到请求资源标识符(URL),然后根据该URL调用HandlerMapping将请求映射到处理器HandlerExcutionChain;
  • c. DispatchServlet根据获得Handler选择一个合适的HandlerAdapter适配器处理;
  • d. Handler对数据处理完成以后将返回一个ModelAndView对象给DispatchServlet;
  • e. Handler返回的 ModelAndView只是一个逻辑视图并不是一个正式的视图,DispatcherSevlet 通过ViewResolver视图解析器将逻辑视图转化为真正的视图View;
  • h. DispatcherServle通过model解析出ModelAndView中的参数进行解析最终展现出完整的view并返回给客户端;

SpringMVC启动流程?
待补充
启动流程和运行流程有何区别?20181222
待补充


15、SpringMVC常用注解都有哪些?

1、 @RequestMapping 用来处理请求地址映射的注解,可用于类或方法上
注解在方法上的@RequestMapping路径会继承注解在类上的路径

  • 示例: @RequestMapping("/anno") 映射此类的访问路径是**/anno**
  • @RequestMapping(produces=“application/json;charset=UTF-8”) 返回值是json对象,字符集是UTF-8
  • @RequestMapping(value="/pathvar/{str};produces=“application/json;charset=UTF-8”)
    method1(@PathVariable String str,HttpServletRequest request) //接受路径参数,并在方法参数前结合@PathVariable使用,访问路径是 /anno/pathvar/xx
  • @RequestMapping(value="/requestParam;produces=“application/json;charset=UTF-8”)
    method2(Long id,HttpServletRequest request) //访问路径是**/anno/requestParam?id=1**
  • @RequestMapping(value="/obj;produces=“application/json;charset=UTF-8”)
    method2(DemoObj obj,HttpServletRequest request) //解释参数到对象 访问路径是 /anno/obj?id=1&name=xx
  • @RequestMapping(value={"/name1","/name2"};produces=“application/json;charset=UTF-8”) //不同路径到相同的方法

2、@RequestBody 注解实现接收 http 请求的 json 数据,将 json 数据转换为 java 对象

  • @ResponseBody 注解实现将 controller 方法返回对象转化为 json 响应给客户 可以放置在方法上方或返回值类型之前
  • 返回值放在response体内

3、@Controller 它标记的类就是一个控制层对象

  • Dispatcher Servlet会自动扫描注解了此注解的类,并将Web请求映射到注解了@RequestMapping的方法上

4、@Resource 和@Autowired 都是做 bean 的注入时使用

  • 相同点:两者都可以写在字段和 setter 方法上。两者如果都写在字段上,那么就不需要再写 setter 方法
  • 不同点:@Autowired 为 Spring 提供的注解,需要导入包(.annotation.Autowired)
  • @Autowired 注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许 null值,可以设置它的 required 属性为 false。
    如果我们想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用
  • @Resource 不是Spring的注解,默认按照 ByName 自动注入,需要导入包 javax.annotation.Resource。 @Resource有两个重要的属性:name 和 type,而 Spring 将@Resource 注解的 name 属性解析为 bean 的名字,而 type 属性则解析为 bean 的类型。所以,如果使用 name 属性,则使用 byName 的自动注入策略
  • 推荐使用@Autowired注解,因为按照类型装配依赖对象不会有任何问题出现。

5、@PathVariable 用于将请求 URL 中的模板变量映射到功能处理方法的参数上,即取出uri模板中的变量作为参数。

  • 如/news/001,可接受001作为参数,此注解放置在参数前

6、@requestParam
@requestParam 主要用于在 SpringMVC 后台控制层获取参数,类似一种是 request.getParameter(“name”),他有三个常用参数 defaultValue,required,value

7、@Component 相当于通用的注解,当不知道一些类归到哪个层时使用,但是不建议

8、@RestController (组合注解@Controller和@ResponseBody)

  • 注意:1、jackson对象和json做转换时一定需要此空参构造

如何开启注解处理器和适配器?

  • 我们在项目中一般会在 springMvc.xml 中通过开启 < mvc:annotation-driven>来实现注解处理器和适配器的开启。
  • 使用@EnableWebMvc注解会开启一些默认的配置,如viewResolver或MessageConverter

16、如何解决get和post乱码问题?

1、解决post请求乱码:

  • 我们可以在web.xml里边配置一个CharacterEncodingFilter过滤器。设置为utf-8
    在web.xml文件中filter的位置加上如下内容:
 <filter>
     <filter-name>encodingFilterfilter-name>
     <filter-class>
         org.springframework.web.filter.CharacterEncodingFilter
     filter-class>
     <init-param>
         <param-name>encodingparam-name>
         <param-value>UTF-8param-value>
     init-param>
     <init-param>
         <param-name>forceEncodingparam-name>
         <param-value>trueparam-value>
     init-param>
 filter>
 <filter-mapping>
     <filter-name>encodingFilterfilter-name>
     <url-pattern>*url-pattern>
 filter-mapping>    

2、解决get请求的乱码:对于get请求中文参数出现乱码解决方法有两个:

  1. 修改tomcat配置文件添加编码与工程编码一致。
  2. 另外一种方法对参数进行重新编码 String userName = New String(Request.getParameter(“userName”).getBytes(“ISO8859-1”), “utf-8”);
  3. win平台默认是gbk编码,mac/Linux是utf-8,同一开发平台为utf-8,可以避免乱码问题.

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

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中将自定义转换器配置到注解驱动上

18、Rest和Http什么关系? 大家都说Rest很轻量,你对Rest风格如何理解?

1、Rest是一种web服务实现方式,Http接口按照Rest风格设计就是 restful http

  • rest风格,一种更为简洁良好的设计风格,可以遵守,也可不必
  • rest这个词,是由fielding的博士论文中提出的,翻译成中文是表现层状态转换。表现层指的是资源(图片、歌曲、文本等)的表现层,可以使用url来对其进行访问。
  • restful就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格,是对http协议的诠释。

要求url中没有动词,只有名词,没有参数
例如:@RequestMapping(value="/viewItems/{id}")

  • {xxx}是占位符,在方法中使用@pathVariable可以获取{xxx}中的变量
  • restful风格其中的一个思想是:通过http请求对应的post、get、put、delete方法,来完成对应的curd操作。

2、REST架构的主要原则:

  1. 网络上的所有事物都可以被抽象化为资源(根据请求头信息,返回xml或json)
  2. 每个资源有唯一的资源标识符
  3. 同一资源具有多种表现形式
  4. 对资源的各种操作不会改变资源标识符
  5. 所有的操作都是无状态

3、资源操作
http://example.com/users/
GET: 获取一个资源
POST:创建一个新的资源
PUT:修改一个资源的状态
DELETE:删除一个资源

之前的操作 RESTful的用法 幂等 安全
http://127.0.0.1/user/query/1 GET查询 http://127.0.0.1/user/1 GET
http://127.0.0.1/user/save POST增加 http://127.0.0.1/user POST
http://127.0.0.1/user/update POST修改 http://127.0.0.1/user PUT
http://127.0.0.1/user/delete GET/POST删 http://127.0.0.1/user DELETE

1、如何理解 RESTful API 的幂等性?

  • 对于同一REST接口的多次访问,得到的资源状态是相同的

2、如何保证接口的幂等性?****
有些接口可天然实现幂等性,比如查询接口:增加、更新、删除都要保证幂等性。那么如何来保证幂等性呢?

  • 1、全局唯一ID 在执行操作前先根据这个全局唯一ID是否存在,来判断这个操作是否已经执行。如果存在则表示该方法已经执行;否则,把全局ID,存储到存储系统弄中,比如数据库,redis等
  • 2、去重表 //适用于有唯一标识场景 如订单ID
  • 3、多版本控制 //适合在更新的场景中,在更新的接口中增加一个版本号,来做幂等
  • 4、状态机控制 //适合在有状态机流转的情况 在设计状态字段时,使用int类型,并且通过值类型的大小来做幂等
    比如订单的创建为0,付款成功为100。付款失败为99

4、最佳实践

  • url组成 网络协议http/https 服务器地址 接口名称 ?参数列表
  • url定义限定 不要使用过大写字母 使用中线-代替下划线_ 参数列表应该被encode过

5、springMVC实现RESTful服务

  • springmvc原生态的支持rest风格的架构
  • 涉及到的注解:@requestMapping 请求的url
    @pathVariable 参数
    @responseBody 响应的json数据

6、发送请求工具

  • 1、advanced REST client
    是chrome浏览器下的一个插件,通过它能够发送http,https,websocket请求,这样就不必通过写一个jsp页面来实现请求,节省时间

  • 2、HttpClient工具类
    模拟了浏览器的行为,如果我们需要在后端向某一地址提交数据获取结果,就可以使用HttpClient

  • 3、我们公司常使用的工具
    restlet插件 postman插件 用于模拟浏览器的请求

7、更新资源时,需要加过滤器,解决无法提交表单的问题

@responsebody(method=requestMethod.put)
public responseBodyEntity<void> updateUser(User user){
     
    try{
     
        integer count = this.userservice.updateUser(user);
        if(count.intValue() == 1){
     
            //响应 204
            return responseEntity.status(HttpStatus.NO_CONTENT).build();
        }catch(exception e){
     e.printStackTrace}
    //新增失败  500
    return responseEntity.status(HttpStatus.INTERNAL_SERVERR_ERROR).build();
    }
}

默认情况下,put请求时无法提交表单数据的,需要在web.xml中添加过滤器解决

<filter>
    <filter-name>httpmethodfilter
    <filter-class>org.springframework.web.filter.httpputformContentFilter
filter>

删除资源时,将post请求转化为DELETE或者是put要用-method指定真正的请求方法


18、 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


19、SpringMVC全局异常处理/整个系统只有一个

使用方法:

  • 1)需要实现一个接口 HandlerExceptionResolver TODO 补充demo
  • 2)需要在springMvc中配置。
    处理逻辑:捕获整个系统中发生的异常。
  • 1、异常写入日志文件 ✅
  • 2、及时通知开发人员。发邮件、短信。✅
  • 3、展示一个错误页面,例如:您的网络故障,请重试。

20、SpringMvc 拦截器用过吗?什么场景会用到,过滤器,拦截器,监听器有什么区别?

1、拦截器interceptor:

  • 是指通过统一拦截,从浏览器发往服务器的请求来完成功能的增强
  • 在面向切面编程中应用
  • 使用场景:解决请求的共性问题(乱码问题,权限验证问题

2、过滤器:filter

  • 实现了javax.servlet.Filter接口
  • 主要的用途是过滤字符编码、做一些业务逻辑判断
  • 原理:在web.xml文件配置好要拦截的客户端请求,它都会帮你拦截到请求,此时你就可以对请求或响应(Request、Response)统一设置编码,简化操作
  • 生命周期:随web应用启动而启动的,只初始化一次,以后就可以拦截相关请求,只有当web应用停止或重新部署的时候才销毁

3、监视器:listener

  • 实现了javax.servlet.ServletContextListener 接口
  • 主要作用是:做一些初始化的内容添加工作、设置一些基本的内容、比如一些参数或者是一些固定的对象等
  • 生命周期:它也是随web应用的启动而启动,只初始化一次,随web应用的停止而销毁

4、区别:

  • 1.拦截器是基于java反射机制的,而过滤器是基于函数回调的;
  • 2.过滤器依赖于servlet容器,而拦截器不依赖于servlet容器;
  • 3.拦截器只能对Action请求起作用,而过滤器则可以对几乎所有请求起作用;
  • 4.拦截器可以访问Action上下文、值栈里的对象,而过滤器不能;
  • 5.在Action的生命周期中,拦截器可以多次调用,而过滤器只能在容器初始化时被调用一次

21、SpringMVC的定制配置

我们定义一个配置类MyMvcConfig,继承WebMVCConfigurerAdapter,并在此类中使用@EnableWebMvc注解,开启对springMVC的配置支持

1、静态资源映射 在配置里重写addResourceHandlers方法来实现

@override 
public void addResourceHandlers(ResourceHandlerRegistry registry){
     
 	registry.addResourceHandler("/asserts/**").addResourceLocation("classpath:/asserts/");//前者是对外暴露的访问路径  后者是文件放置的目录
}

2、拦截器设置Interceptor

  • 实现对每一个请求处理前后进行相关的业务处理,类似servlet中的Filter
  • 先让普通的Bean实现HandlerInterceptor接口或是继承HandlerInterceptorAdapter类来实现自定义拦截器,然后重写WebMvcConfigurerAdapter(配置类)的addInterceptors方法来注册自定义的拦截器

3、@ControllerAdvice 将对控制器的全局配置放置在同一个位置

  • 注解了@ControllerAdvice的类的方法可使用@ExceptionHandler @InitBinder @ModelAttribute注解到方法上

(1)定制ControllerAdvice
@ExceptionHandler(value=xx.class) 用于全局处理控制器里的异常,更人性化的将异常输出给用户
例如:

@ExceptionHandler(value=Exception.class)   //value为拦截所有的异常
public ModelAndView exception(Exception exception, webRequest  request){
     
     ModelAndView modelAndView =new ModelAndView("error");
     modelAndView.addObject("errorMessage",exception.getMessage());
     return modelAndView;
}
  • @InitBinder 用于设置WebDataBinder,自动绑定前台请求参数到Model中
    例如:
@InitBinder
public void initBinder(WebDataBinder webDataBinder){
     
    webDataBinder.setDisallowedFields("id");
}
  • @ModelAttribute 让全局的@RequestMapping都能拿在此处设置的键值对
    例如:
@ModelAttribute
public void addAttributes(Model model){
     
    model.addAttributes("msg","额外信息");
}

(2)那么,在使用控制器时:

@Controller 
public class AdviceController{
     
    @RequestMapping("/advice")
    public String getSomething(@ModelAttribute("msg") String msg,DemoObj obj){
     
        throw new IllegalArgumentException("参数有误/"+"来自@ModelAttribute:"+msg);
    }
}

(3)异常展示页面 在src/main/resources/views下,新建error.jsp
${errorMessage} //运行时发现 id被过滤掉了,且获得了@ModelAttribute的msg信息

4、其他配置

  • 快捷的ViewController //配置页面转向 registry.addViewController("/index").setViewName("/index");//前面是路径,后面是页面
  • 路径匹配参数设置 使用configurePathMatch方法不可忽视"."后面的参数 Configurer。setUseSuffixPatternMatch(false);

5、文件上传配置(必备)springMvc通过配置MultipartResolver来上传文件,在spring的控制器中,通过MultipartFile file来接受文件

  • (1)添加文件上传依赖 commons-fileupload/commons-io
  • (2)上传页面,在src/main/resources/views下新建upload.jsp
<div class="upload">
    <form action="upload" enctype="multipart/form-data" method="post">
        <input type="file" name="file"/>br>
        <input type="submit" value="上传"/>
    form>
div>
  • (3)添加转向到upload页面的ViewController
public void addViewController(ViewControllerRegistry registry){
     
    registry.addViewController("/index").setViewName("/index");
    registry.addViewController("/toUpload").setViewName("/upload");
}
  • (4)MultipartResolver配置(配置类中)
@Bean
public MultipartResolver multipartResolver(){
     
    CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
    multipartResolver.setMaxUploadSize(1000000);
    return multipartResolver;
}
  • (5)控制器
@Controller 
public class UploadController{
     
    @RequestMapping(value="/upload",method="RequestMethod.POST")
    public @ResponseBody String upload(MultipartFile file){
     
        try{
     
            FileUtils.writeByteArrayToFile(new File("e:/upload/"+file.getOriginalFileName()),file.getBytes());//快速写文件到磁盘
            return "ok";
        }catch(IOException e){
     
            e.prrintStackTrace();
            return "wrong";
        }
    }
}

6、自定义HttpMessageConvertor 用于处理request和response里的数据


22、Spring其他产品(SpringBoot(简化了Spring配置文件,无需再使用springMVC,只需要引入相关的starter即可),Spring cloud,Spring security(同类产品shiro,更容易使用),Spring data(持久层通用解决方案),Spring AMQP)

22.1、关注的点:

1、spring boot是什么

  • 简化了Spring配置文件,使用默认开发配置来实现快速开发,无需再使用springMVC,只需要引入相关的starter即可

2、Springboot的特点?

  • 约定优于配置:SpringBoot是基于约定的,所以很多配置都有默认值,但如果想使用自己的配置替换默认配置的话,就可以使用application.properties或者application.yml (application.yaml)进行配置(yaml文件更加结构化,比json占用字符少)
  • 通过整合通用实践,更加自动、智能的依赖管理等,Spring Boot提供了各种典型应用领域的快速开发基础,所以它是以应用为中心的框架集合(如:内嵌servlet容器)
  • 通过起步依赖和自动配置简化开发;可以对Spring的配置进行简化

3、SpringBoot注解大全:

  • @ComponentScan指定了扫描指定基本包下的类;
  • @SpringbootConfiguration标注该类是Spring的一个配置类
  • @EnableAutoConfiguration是SpringBoot自动配置功能开启的注解

22.2、Springboot的精髓(@Conditonal是核心

Springboot原理:几个重要的事件回调机制,配置在META_INF/spring.factories ApplicationContextInitializer/SprigApplicationRunListener

Springboot启动会加载大量的自动配置类;我们看需要的功能有没有Springboot默认写好的自动配置类;再来看这个自动配置类中到底配置了哪些组件(只要我们要用的组件有,我们就不需要再来配置);给容器中自动配置类添加组件时,会从properties类中获取某些属性,我们就可以在配置文件中指定这些属性的值。

运作原理:从@SpringbootApplication注解开始,这是一个组合注解,核心功能时@EnableAutoConfiguration注解提供的;
@EnableAutoConfiguration关键的@Import导入的类有EnableAutoConfigurationImportSelector使用springFactoriesloader.loadFactoryNames方法扫描具有META-INF/spring.factories文件的jar包,里面声明了哪些自动配置。

核心注解:打开任意一个AutoConfiguration文件,一般都有下面的条件注解,例如autoconfigure.condition包下条件注解
@ConditionalOnBean:容器中有指定的Bean的条件下 @ConditionalOnClass:当类路径下有指定的类的条件下 @ConditionalOnExpression 基于SpEL表达式作为判断条件
@ConditionalOnJava:基于JVM版本作为判断条件 @ConditionalONMissingBean:容器中没有指定Bean的情况下 @ConditionalOnWebApplication:当前项目是web项目的条件下 等
这些注解组合了@Conditional元注解,只是使用了不同的条件。

核心功能:

  • 1、独立运行的Spring项目,springboot可以以jar包形式独立运行,运行项目只需通过java -jar xx.jar来运行
  • 2、内嵌Servlet容器,可以内嵌Tomcat/jetty,我们无需以war包形式部署项目
  • 3、提供starter简化Maven配置:springboot提供一系列的starter pom来简化Maven的依赖加载,例如导入springboot-starter-web时,自动会导入所有相关依赖包
  • 4、自动配置Spring:Springboot会根据类路径中的jar包、类,为jar包里的类自动配置Bean,这样会极大减少我们需要的配置,也可以自定义自动配置
  • 5、准生产的应用监控:springboot提供基于http/ssh/telnet对运行时的项目进行监控
  • 6、无代码生成和xml配置:通过spring4.x提供的条件注解,使用java配置+注解配置组合,不再需要任何xml配置即可实现Spring所有配置

java配置:java Config提供了配置spring ioc容器的纯java方法,有助于避免使用xml配置,优点在于

  • 1、面向对象的配置,用户能充分利用java中的面向对象功能,一个配置类可以继承另一个,重写它的@Bean方法
  • 2、较少或消除xml配置
  • 3、类型安全和重构友好:javaConfig提供了一种类型安全的方法来配置Spring容器,由于java5对泛型的支持,现在可以按类型而不是按名称检索bean,不需要任何强制转换或基于字符串的查找

22.3、Springboot的整合技术

日志管理(slf4j日志抽象层+logback日志实现层)

  • 整合Mybatis时,可以在application.properties中配置mybatis.mapper-locations指定要加载的映射文件
  • 整合Junit时,需要在测试类上添加@RunWith(SpringRunner.class)
  • 整合SpringData JPA时,需要添加SpringData JPA的起步依赖spring-boot-starter-data-jpa
  • 整合JPA可以实现分页和排序,将可分页的org.springframework.data.domain.Pageable传递给存储库方法

22.4、Springboot 单元测试

1、首先引入maven文件

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-testartifactId>
    <scope>testscope>
dependency>

SpringBoot提供了spring-boot-start-test启动器,该启动器提供了常见的单元测试库:

  • JUnit: 一个Java语言的单元测试框架
  • Spring Test & Spring Boot Test:为Spring Boot应用提供集成测试和工具支持
  • AssertJ:支持流式断言的Java测试框架
  • Hamcrest:一个匹配器库
  • Mockito:一个java mock框架
  • JSONassert:一个针对JSON的断言库
  • JsonPath:JSON XPath库

2、常用注解

  • @RunWith(SpringRunner.class)
    JUnit运行使用Spring的测试支持。SpringRunner是SpringJUnit4ClassRunner的新名字

  • @SpringBootTest
    该注解为SpringApplication创建上下文并支持Spring Boot特性,其webEnvironment提供如下配置:Mock-加载WebApplicationContext并提供Mock Servlet环境,嵌入的Servlet容器不会被启动。RANDOM_PORT-加载一个EmbeddedWebApplicationContext并提供一个真实的servlet环境。嵌入的Servlet容器将被启动并在一个随机端口上监听。DEFINED_PORT-加载一个EmbeddedWebApplicationContext并提供一个真实的servlet环境。嵌入的Servlet容器将被启动并在一个默认的端口上监听(application.properties配置端口或者默认端口8080)。NONE- 使用SpringApplication加载一个ApplicationContext,但是不提供任何的servlet环境。

  • @MockBean
    在你的ApplicationContext里为一个bean定义一个Mockito mock。

  • @SpyBean
    定制化Mock某些方法。使用@SpyBean除了被打过桩的函数,其它的函数都将真实返回。

  • @WebMvcTest
    该注解被限制为一个单一的controller,需要利用@MockBean去Mock合作者(如service)。

3、测试Controller

  • 在测试Controller时需要进行隔离测试,这个时候需要Mock Service层的服务
@RunWith(SpringRunner.class)
@WebMvcTest(ScoreController.class)
public class ScoreControllerTestNew {
     
    @Autowired
    private MockMvc mockMvc;
    @MockBean
    private ICalculateService calculateService;
    @MockBean
    private IModelMonitorService modelMonitorService;
    @MockBean
    private IScoreConfigService scoreConfigService;
    @MockBean
    private IModelProductService modelProductService;
    @Before   初始化工作
    public void setUp(){
     
    }
    @Test
    public void testScore() throws Exception {
     
        given(this.modelProductService.get(anyLong())).willReturn(null);
        String jsonStr = "{\"data\":{\"debit_account_balance_code\":40,\"credit_consume_count\":1,\"debit_start_age\":1,\"debit_consume_sum_code\":2,\"age\":38},\"modelProductId\":5}";
        RequestBuilder requestBuilder = null;
        requestBuilder = post("/scoreApi/score").contentType(MediaType.APPLICATION_JSON).content(jsonStr);
        this.mockMvc.perform(requestBuilder).andExpect(status().isOk()).andExpect(MockMvcResultMatchers.content().string("{}"));
    }
}

4、测试Service

  • 测试Service和测试Controller类似,同样采用隔离法
@RunWith(SpringRunner.class)
@SpringBootTest
public class ServiceTest {
     
    @MockBean
    private ModelMonitorMapper modelMonitorMapper;
    @Autowired
    private IModelMonitorService modelServiceServiceImpl;
    @Test
    public void testModelServiceServiceImpl(){
     
        given(modelMonitorMapper.insert(anyObject())).willReturn(0);
        int n =  modelServiceServiceImpl.insert(new ModelMonitor());
        assertThat(n).isEqualTo(0);
    }
}

5、测试Dao

  • 测试的时候为了防止引入脏数据使用注解@Transactional和@Rollback在测试完成后进行回滚。
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class ScoreControllerTestNew {
     
    @Autowired
    private ModelMonitorMapper modelMonitorMapper;
    @Test
    @Rollback
    public void testDao() throws Exception {
     
        ModelMonitor modelMonitor = new ModelMonitor();
        modelMonitor.setModelProductId(Long.parseLong("5"));
        modelMonitor.setLogit(21.144779999999997);
        modelMonitor.setDerivedVariables("{\"debit_account_balance_code\":1.0,\"credit_consume_count\":1.0,\"debit_start_age\":1.0,\"debit_consume_sum_code\":1.0,\"age\":1.0}");
        modelMonitor.setScore("300");
        modelMonitor.setSrcData("{\"data\":{\"debit_account_balance_code\":40,\"credit_consume_count\":1,\"debit_start_age\":1,\"debit_consume_sum_code\":2,\"age\":38},\"modelProductId\":5}");
        int n = modelMonitorMapper.insert(modelMonitor);
        assertThat(n).as("检查数据是否成功插入").isEqualTo(0);
    }
}

22.5、Springboot自定义starter

开发技巧:

  • 1、模板引擎页面修改后要实时生效则禁用模板引擎的缓存,页面修改完成Ctrl+f9,重新编译

  • 2、F5重新提交的问题:拦截器进行登录检查

  • 3、jdk1.8.0_131/maven3.3.9/idea2017.2.2/springboot1.5.19 springboot不支持jsp,默认使用模板引擎(freemarker/thymeleaf),得注意版本间适配的问题,以免发生错误

    Springboot Rest风格的注解:原来@RequestMapping;现在:@PostMapping增加/@GetMapping查询/@PutMapping修改/@DeleteMapping删除;spring4.2之后:使用@RestController代替@ResponseBody+@Controller
    可以启动springMVC的自动配置@EnableWebMvc,对于浏览器返回modelAndview,对于客户端,返回json数据

springboot配置嵌入式servlet容器(tomcat/jetty长连接/Undertow不支持jsp):嵌入式的tomcat只能执行jar项目,由IOC容器带动嵌入式tomcat,若是使用外置tomcat,则可以运行war项目,此时,pom.xml文件server-tomcat的scope设置为provided
Docker容器技术,用于管理框架的自动配置,不需要重复配置
Springboot整合jdbc/mybatis/spribgdataJPA:jdbc的连接池tomcat.jdbc.pool.DataSource ,数据源使用Druid连接池更常见(可以查看SQL监控,web应用统计,jdbc执行数时间);mybatis配置ConfigurationCustomizer
开启驼峰命名法,使用MapperScan注解批量扫描Mapper接口

22.6、Springboot与Redis缓存

Springboot自身的缓存在ConcurrentMap<>中,在实际开发中我们使用缓存中间件
几个重要的概念

  • Cache:缓存接口,定义缓存操作,实现类有redisCache、EhCacheCache、ConcurrentMapCache等
  • CacheManager:缓存管理器,管理各种缓存组件(cache)
  • @Cacheable:主要针对方法配置,能够根据方法的请求对其结果进行缓存
  • @CacheEvict:清空缓存
  • @CachePut:保证方法被调用,又希望结果被缓存
  • @EnableCaching:开启基于注解的缓存
  • keyGenerator:缓存数据时key生成策略
  • serialize:缓存数据时value序列化策略

@Cacheable/@CachePut的区别:前者先调缓存,后者先更新方法,在把结果存在缓存中,两者可以结合使用(@caching),注意:要保证取缓存的key和存缓存的key是一致的
几个属性:

  • cacheNames/value:指定缓存组件的名字;
  • key:缓存数据使用的key,可以用它来指定,默认是使用方法参数的值 1-方法的返回值编写SpEL, #id 参数id的值
  • keyGenerater:key的生成器,可以自己指定key的生成器的组件id
  • key/keyGenerator:二选一使用
  • cacheManager:指定符合条件的情况下才缓存; condition=“#id>0”
  • unless:否定缓存,当unless指定的条件为true,方法的返回值就不会被缓存,可以获取到结果进行判断
  • sync:是否使用异步模式

redis数据类型 去看数据库部分
springboot redis保存数据:1、以jdk底层序列化方式 默认 2、json格式的序列化器,底层是jackson2的jar包

22.7 Springboot与消息中间件

kafka/activeMQ/RabbitMQ 以rabbitMQ为例,端口号5672(客户端访问)/15672(访问管理页面) 引入的依赖项springboot-starter-activemq
MQ的作用:异步处理/应用解耦/流量削峰(秒杀)
JMS:java消息服务 ActiveMQ(队列类型有queue/topic)AMQP:高级消息队列协议 rabbitMQ(交换器类型有direct/fanout/topic)

  • 我们公司整合的是阿里云提供的RocketMQ

22.8 Springboot与任务 20210307补

异步任务 @Async注解 开启@EnableAsync
定时任务 @EnableScheduling @Scheduled
邮件任务 pop3 smtp
同类产品 quartz

接入ElasticJob
1、引入配置

<dependency>
	<groupId>cn.gov.zcy.bootgroupId>
	<artifactId>spring-boot-starter-elasticjobartifactId>
	<version>3.0.2-RELEASEversion>
dependency>

2、配置中心配置项

  • spring-boot-starter-elasticjob 支持多个定时任务的配置,如上所示,显而易见elasticjobs 是一个数组。
  • namespace 请使用默认值,不需要动
  • jobName 给你的定时任务请个英文名吧
  • cron cron表达式,不必详说
  • shardingCount 分片数量
  • jobType ,job类型,0- 简单任务,1-数据流任务(熟悉tbshecuPle就不陌生了),2-脚本任务
  • itemParams, 任务项,自定义项
  • classFullName,自定义的类,完全限定名
  • streamingProcess 使用默认值,数据流任务使用
  • scriptCmdLine 脚本任务使用
  • dataSource 很简单不必详说
  • needLog: false 这一项一定要设为false,实际中发现开启了数据库日志功能后,对数据库造成的压力很大,而且记录的数据基本没有什么价值 以上的配置中如:zk、数据源请使用项目中通用的配置即可
elasticjobs:
-
zk: ${zookeeper.cluster}
namespace: zcy-elastic-job-${zcy.env.label}
jobName: test-case-1 
cron: 0/2 * * * * ? 
shardingCount: 4
jobType: 0
itemParams: 0=Beijing,1=Shanghai,2=Guangzhou  
classFullName: com.MySimpleJob
streamingProcess: false
needLog: false
overwrite: true
scriptCmdLine:

3、 代码编写

  • 如果是简单任务,请实现 com.dangdang.ddframe.job.api.simple.SimpleJob 接口
  • 如果使用数据流任务,请实现 com.dangdang.ddframe.job.api.dataflow.DataflowJob 接口
  • 如果是脚本任务,请实现 com.dangdang.ddframe.job.api.script.ScriptJob

4、问题排查

4.1、遇到错误:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘cn.gov.zcy.elasticjob.config.ElasticJobConfig’: Invocation of init method failed; nested exception is java.lang.NoClassDefFoundError: org/apache/curator/connection/ConnectionHandlingPolicy

解决办法:

  • 检查直接依赖、间接依赖,将curator-framework、curator-client、curator-recipes 三个jar版本都改为 2.10.0 即可

4.2、在job任务接口实现类中,如果需要获取spring context,可以使用工具类

  • SpringContext.getBean(“xxxxx”)

4.3、启动报错:

Caused by: java.lang.NoSuchMethodError: com.google.gson.stream.JsonWriter.jsonValue(Ljava/lang/String;)Lcom/google/gson/stream/

解决方法:

  • 将gson 升级到2. 6.1

4.4、问题反馈

  • 如果本地启动时,没有找到apollo中配置的ElasticJob配置项,程序启动会报错

5、ElasticJob 原理分析

22.9、Springboot与ElasticSearch

elasticsearch初始会使用2G的堆空间,可通过docker run -e ES_java-_OPTS="-Xms 256m -Xmx 256m" -d -p 9200:9200 -p 9300:9300 --name ES01 镜像名 来启动
版本适配的问题:可以升级springboot版本1.5.19(不推荐),可以降ES的版本(使用2.4.6版本)

22.10 Springboot与安全

shiro或security 注解@EnableWebSecurity 引入依赖:spring-boot-starter-security ,配置类必须扩展WebSecurityConfigurerAdapter并覆盖其方法。
1、Spring security与apache shiro的对比?

22.11、Springboot与分布式

在分布式系统中国,国内常用zookeeper+dubbo组合(我们公司的分布式框架组合),而springboot推荐使用全栈的Spring(springboot+springcloud)

22.12、Springboot与热部署

在开发中修改了一个java文件后想看到效果不得不重启应用,希望能在不重启应用的情况下,程序可以自动部署(该模块在生产环境中被禁用)
Spring Loaded/JRebel/springboot Devtools

22.13、Springboot与监控管理actuator

作用:帮助我们访问生产环境中正在运行的应用程序的当前状态,监视器模块公开了一组可直接作为HTTP URL访问的REST端点来检查状态 .beans 容器中所有bean的信息;autconfig所有自动配置信息 auditevents审计信息 configprops 所有配置属性 dump线程状态信息 env当前环境信息 health应用健康状态; info当前应用信息 metrics应用的各项指标 mappings应用@RequestMapping映射路径 shutdown关闭当前应用 trace追踪信息

22.14、Springboot与Spring比较?

1、依赖注入 Spring:构造器注入xml文件 springboot:@Bean(value)+java配置
2、包扫描 spring:< context:component-scan> springboot:@ComponentScan(value)

22.15、Springboot配置加载优先级?

1. 加载位置与顺序
SpringBoot启动会扫描以下位置的application.properties/yml文件作为spring boot的默认配置文件:

file:./config/    file: 指当前项目根目录
file:./
classpath:/config/    classpath: 指当前项目的resources目录
classpath:/

以上是按照优先级从高到低的顺序,所有位置的文件都会被加载,高优先级配置内容会覆盖低优先级配置的内容,并形成互补配置;当然,我们也可以通过spring.config.location来改变默认配置
谈谈你对Spring的理解?从该问题着手深入解析Spring/Spring5新特性/SpringBoot_第10张图片

  • 注意:maven打包是不能把src外面的两个application.properties文件打到jar包里的

23、Spring cloud 可以看作是在Spring Boot基础上发展出的更加高层次的框架,它提供了构建分布式系统的通用模式,包含服务发现和服务注册、分布式配置管理、负载均衡、分布式诊断等各种子系统,可以简化微服务系统的构建

建议:整体上把握前沿框架的应用范围和内部设计,了解主要组件和具体用途
细节待补充 20210110


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

xxxxx


25、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,java,aop,springboot)