摘要:面试时常常被面试官问到这个问题,题目太大了,我会从Spring架构/bean初始化流程/bean生命周期/设计模式这几个方面来回应面试官。Spring框架中的核心技术:控制反转/依赖注入/面向切面编程/Spring的声明式事务/以及Spring生态相关产品的简介,这篇文章会详细说明。
说起Spring,绝对是Java开发领域的佼佼者,试问,做Java开发的有谁不知道Spring?做Java开发的又有谁没用过Spring?又有哪家公司在Java Web项目中没使用过Spring?所以,骚年,如果你选择了Java开发这条不归路,你就必须牢牢掌握Spring!
帮助解决别人的Spring问题是学些Spring的好方法。
todo
todo
Spring 源码设计精妙、结构清晰,对 Java 设计模式灵活运用,是学习Java技术的最佳实践范例。
Spring是一个轻量级的开源JavaEE框架,可以解决企业级应用开发的复杂性而生。Spring可以使得简单的JavaBean实现以前只有EJB(Enterprise Java Beans)才能实现的功能。Spring是一个IOC和AOP容器框架(IOC:控制反转,把创建对象过程交给Spring进行管理,AOP:面向切面,不修改源代码进行功能增强)
我们公司使用的SpringBoot版本 2.0.3,其依赖的Spring版本为5.0.7.RELEASE
1、什么是控制反转?
把对象的创建和对象之间的调用过程,交给Spring进行管理
2、实例化一个java对象的三种方式:
3、Spring框架中IOC的原理是什么?及其实现方案?20181222
1、什么是依赖注入?
2、spring的依赖注入有哪几种方式
<bean id ="orders" class ="cn.com.zcy.User">
<constructor-arg name = "name" value = "电脑">constructor-arg>
<constructor-arg name = "address" value = "apple">constructor-arg>
bean>
<bean id ="user" class ="cn.com.zcy.User">
<property name ="age" value = "27">property>
<property name = "author" value = "qiwenjie">property>
bean>
1、什么是AOP?
突然想到的,在标准中心项目中,记录操作类目、品牌下架日志的需求中,也可以采用该AOP方式来处理,在旗舰店的校验逻辑中,也可以采用AOP方式来实现,之前自己真的是傻乎乎的,采用的硬编码的方式。
面向切面,不修改源代码情况下进行功能增强
2、AOP实现原理是什么?
AOP术语
1、连接点 joinPoint
2、切入点
3、通知(增强) Advise
4、切面
3、AOP解决了什么问题?
4、AOP解释
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 动态代理代码
public interface UserDao {
public int add(int a,int b);
public String update(String id);
}
public class UserDaoImpl implements UserDao {
@Override
public int add(int a, int b) {
return a+b;
}
@Override
public String update(String id) {
return id;
}
}
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;
}
}
1、beanFactory
2、AplicationContext
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会这样做:
这个过程的源码在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方法中:
1、作用域:得分类讨论,bean常用的scope有两种:singleton(单例)和prototype(多例)
如何选择?
2、生命周期:从对象创建到对象销毁的过程
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的方式有什么好处?
6、面试常问?
7、单实例bean注意的事项
8、多实例bean注意的事项
1、单例时
<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" lazy-init="true"/>
spring读取xml文件时,会创建对象,创建对象时先调用构造器,然后调用init-method属性值中所指定的方法,对象在被销毁的时候,会调用destroy-method属性值中所指定的方法。
2、多例时prototype
1、代理模式(proxy)
Spring使用工厂模式可以通过 BeanFactory 或 ApplicationContext 创建 bean 对象。
两者对比:
BeanFactory :延迟注入(使用到某个 bean 的时候才会注入),相比于ApplicationContext 来说会占用更少的内存,程序启动速度更快。
ApplicationContext :容器启动的时候,不管你用没用到,一次性创建所有 bean 。BeanFactory 仅提供了最基本的依赖注入支持, ApplicationContext 扩展了 BeanFactory ,除了有BeanFactory的功能还有额外更多功能,所以一般开发人员使用 ApplicationContext会更多。
ApplicationContext的三个实现类:
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();
}
}
在我们的系统中,有一些对象其实我们只需要一个,比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。
使用单例模式的好处:
Spring 中 bean 的默认作用域就是 singleton(单例)的。除了 singleton 作用域,Spring 中 bean 还有下面几种作用域:
Spring 实现单例的方式:
xml : <bean id="userService" class="top.snailclimb.UserService" scope="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));
}
}
}
模板方法模式是一种行为设计模式,它定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。 模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤的实现方式
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 模式与模板方法模式配合,既达到了代码复用的效果,同时增加了灵活性。
观察者模式是一种对象行为型模式。它表示的是一种对象与对象之间具有依赖关系,当一个对象发生改变的时候,这个对象所依赖的对象也会做出反应。Spring 事件驱动模型就是观察者模式很经典的一个应用。Spring 事件驱动模型非常有用,在很多场景都可以解耦我们的代码。比如我们每次添加商品的时候都需要重新更新商品索引,这个时候就可以利用观察者模式来解决这个问题。
Spring 事件驱动模型中的三种角色
事件角色
Spring 中默认存在以下事件,他们都是对 ApplicationContextEvent 的实现(继承自ApplicationContextEvent):
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并且写相应的构造函数
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(“你好”) ,控制台就会打印出:接收到的信息是:你好.
在Spring MVC中,DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由HandlerAdapter 适配器处理。HandlerAdapter 作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller 作为需要适配的类。
为什么要在 Spring MVC 中使用适配器模式?
if(mappedHandler.getHandler() instanceof MultiActionController){
((MultiActionController)mappedHandler.getHandler()).xxx
}else if(mappedHandler.getHandler() instanceof XXX){
...
}else if(...){
...
}
假如我们再增加一个 Controller类型就要在上面代码中再加入一行 判断语句,这种形式就使得程序难以维护,也违反了设计模式中的开闭原则 – 对扩展开放,对修改关闭。
装饰者模式可以动态地给对象添加一些额外的属性或行为。相比于使用继承,装饰者模式更加灵活。简单点儿说就是当我们需要修改原有的功能,但我们又不愿直接去修改原有的代码时,设计一个Decorator套在原有代码外面。其实在 JDK 中就有很多地方用到了装饰者模式,比如 InputStream家族,InputStream 类下有 FileInputStream (读取文件)、BufferedInputStream (增加缓存,使读取文件速度大大提升)等子类都在不修改InputStream 代码的情况下扩展了它的功能。
装饰者模式示意图
Spring 中配置 DataSource 的时候,DataSource 可能是不同的数据库和数据源。我们能否根据客户的需求在少修改原有类的代码下动态切换不同的数据源?这个时候就要用到装饰者模式。Spring 中用到的包装器模式在类名上含有 Wrapper或者 Decorator。这些类基本上都是动态地给一个对象添加一些额外的职责。
Spring 框架中用到了哪些设计模式?
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 中向属性、方法和构造方法中自动装配变量
什么是注解?
开启注解扫描
1、如果扫描多个包,多个包使用逗号隔开
2、扫描包上层目录
<context:component-scan base-package= "cn.com.zcy.service">context:component-scan>
最关键的注解:
@SpringBootApplication
public class SpringSecurityApplication {
public static void main(java.lang.String[] args) {
SpringApplication.run(SpringSecurityApplication.class, args);
}
}
@SpringBootApplication //该注解是一个组合注解,组合了
我们可以使用@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 {
}
@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
4、@Scope
声明 Spring Bean 的作用域,使用方法:
@Bean
@Scope("singleton")
public Person personSingleton() {
return new Person();
}
四种常见的 Spring Bean 的作用域:
5、@Configuration
一般用来声明配置类,可以使用 @Component注解替代,不过使用@Configuration注解声明配置类更加语义化。
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
5 种常见的请求类型:
GET 请求
@GetMapping("/users")
public ResponseEntity<List<User>> getAllUsers() {
return userRepository.findAll();
}
POST 请求
@PostMapping("/users")
public ResponseEntity<User> createUser(@Valid @RequestBody UserCreateRequest userCreateRequest) {
return userRespository.save(user);
}
PUT 请求
@PutMapping("/users/{userId}")
public ResponseEntity<User> updateUser(@PathVariable(value = "userId") Long userId, @Valid @RequestBody UserUpdateRequest userUpdateRequest) {
......
}
DELETE 请求
@DeleteMapping("/users/{userId}")
public ResponseEntity deleteUser(@PathVariable(value = "userId") Long userId){
......
}
PATCH 请求
@PatchMapping("/profile")
public ResponseEntity updateStudent(@RequestBody StudentUpdateRequest studentUpdateRequest) {
studentRepository.updateDetail(studentUpdateRequest);
return ResponseEntity.ok().build();
}
@Value(value="qiwenjie")
private String name;
掌握前后端传值的正确姿势,是做需求的第一步
举个简单的例子:
@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
@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 类上
我们的数据源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("${hangzhou2021}")
String hangzhou2021;
2、@ConfigurationProperties(常用)
@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(不常用)
@Component
@PropertySource("classpath:website.properties")
class WebSite {
@Value("${url}")
private String url;
省略getter/setter
......
}
即使在前端对数据进行校验的情况下,我们还是要对传入后端的数据再进行一遍校验,避免用户绕过浏览器直接通过一些 HTTP 工具直接向后端请求一些违法数据。
1、一些常用的字段验证的注解
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;
}
@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)
@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);
}
}
自定义 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;
相关注解:
@ControllerAdvice :注解定义全局异常处理类
@ExceptionHandler :注解声明异常处理方法
如何使用呢?拿我们在第 5 节参数校验这块来举例子。如果方法参数不对的话就会抛出MethodArgumentNotValidException,我们来处理这个异常。
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
/**
* 请求参数异常处理
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) {
......
}
}
更多关于 Spring Boot 异常处理的内容,看这两篇文章:
明天继续补充
在要开启事务的方法上使用@Transactional注解即可
@Transactional(rollbackFor = Exception.class)
public void save() {
......
}
我们知道 Exception 分为运行时异常 RuntimeException 和非运行时异常。在@Transactional注解中如果不配置rollbackFor属性,那么事物只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事物在遇到非运行时异常时也回滚。
@Transactional 注解一般用在可以作用在类或者方法上。
1、过滤 json 数据
//生成json时将userRoles属性过滤
@JsonIgnoreProperties({
"userRoles"})
public class User {
private String userName;
private String fullName;
private String password;
@JsonIgnore
private List<UserRole> userRoles = new ArrayList<>();
}
public class User {
private String userName;
private String fullName;
private String password;
//生成json时将userRoles属性过滤
@JsonIgnore
private List<UserRole> userRoles = new ArrayList<>();
}
2、格式化 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"
}
@SpringBootTest(webEnvironment = RANDOM_PORT)
@ActiveProfiles("test")
@Slf4j
public abstract class TestBase {
......
}
@Test
@Transactional
@WithMockUser(username = "user-id-18163138155", authorities = "ROLE_TEACHER")
void should_import_student_success() throws Exception {
......
}
完全注解开发
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注入本类即可。
(1)核心容器Core Container:包括Core、Beans、Context、Expression Language模块
(2)AOP、Aspects模块:
(3)数据集成与访问
Spring的jdbc和dao模块抽象化了这些样板代码,使得数据库连接变得简单
(4)web与远程调用
(5)instrumentation
(6)test
1、基于 @Transactional 的声明式事务管理:(默认)
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/>
<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{
}
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);
}
1、@Transactional注解可以作用于哪些地方?
@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 属性
readOnly 属性
rollbackFor 属性
noRollbackFor属性
使用aop实现的。aop在进行解析的时候,最终生成一个Advisor对象,这个Advisor对象中封装了切面织入所需要的所有信息,其中就包括Aspect:他是跨不同java类层面的横切性逻辑,实现形式上,可以是XML文件中配置的普通类,也可在类代码中用“@Aspect”注解声明,运行时spring框架创建Advisor来指代他:(源码中对应BeanFactoryTransactionAttributeSourceAdvisor)
Advisor最重要的两个部分:PointCut和Advice属性。
Join Point:它是Aspect可以切入的特定点,在Spring里面只有方法可以作为Join Point(是可利用的机会,具体由pointcut指定)
Advice:它定义了切面中能够采取的动作。如果去看Spring源码,就会发现Advice、Join Point并没有定义在Spring自己的命名空间里,这是因为他们是源自AOP联盟,可以看作是Java工程师在AOP层面沟通的通用规范
分别解释这三个类:
总结:Spring支持AspectJ的注解式切面编程
1、使用@Aspect声明是一个切面;
2、使用@After、@Before、@Around定义建言(advice),可直接将拦截规则(切点)作为参数;
3、其中@After、@Before、@Around参数的拦截规则为切点(PointCut),为了使切点复用,可使用@PointCut专门定义拦截规则;
4、其中符合条件的每一个被拦截处为连接点(JointPoint)
示例:
1、添加Spring AOP支持及AsectJ依赖
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、配置类
6、运行
使用@Transactional注解时需要注意许多的细节,不然你会发现@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,事务将不会发生回滚。
3、@Transactional 注解属性 rollbackFor 设置错误
rollbackFor 可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性。
// 希望自定义的异常可以进行回滚
@Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class
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);
}
那为啥会出现这种情况?其实这还是由于使用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);
}
@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,那事务就从根本上失效了。
Spring 对事务控制的支持统一在 TransactionDefinition 类中描述,该类有以下几个重要的接口方法:
Spring核心工厂是BeanFactory (一种容器,提供了基本的DI支持)
常用的BeanFactory 实现:
有DefaultListableBeanFactory 、 XmlBeanFactory 、 ApplicationContext等。
XMLBeanFactory,最常用的就是org.springframework.beans.factory.xml.XmlBeanFactory,它根据XML文件中的定义加载beans。该容器从XML 文件读取配置元数据并用它去创建一个完全配置的系统或应用。
我们希望一个一个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容器,
1、Spring Aware 将Bean和Spring框架耦合
spring aware目的是为了让Bean获得Spring容器的服务,因为applicationContext接口集成了MessageSource接口、ApplicationPublisher接口和ResourceLoader接口,所以Bean继承ApplicationContextAware可以获得Spring容器的所有服务,但原则上我们需要什么就实现什么接口
2、多线程(@EnableAsync 通过任务执行器TaskExecutor来实现多线程和并发编程)
使用ThreadPoolTaskExecutor实现一个基于线程池的TaskExecutor,实际开发中任务一般是异步的,我们在配置类中通过@EnableAsync开启对异步任务的支持,并通过在实际执行的Bean方法中使用
@Async注解来声明其是一个异步任务。
3、计划任务(通过@Scheduled支持多种类型的计划任务,包含cron/fixDelay/fixRate)
通过@Scheduled声明方法是计划任务,使用fixedRate属性每隔固定时间执行
使用cron属性可按照指定时间执行(Unix下) 开启计划任务@EnableScheduling
4、条件注解@Conditional
5、组合注解 使用一个注解表示两个注解 例如@Configuration和@ComponentScan被其他注解代替
6、@Enable* 注解工作原理
已有的@Enable*
原理:所有的注解都有一个@import注解,用于导入配置类,自动开启的实现是导入了一些自动配置的Bean,导入方式分为以下三种:
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;
}
1、SSM整合
Dao层
pojo和映射文件以及接口使用逆向工程生成(有各种工具);
SqlMapConfig.xml用于配置mybatis核心配置文件;
ApplicationContext-dao.xml整合spring在dao层的配置(数据源、会话工厂、扫描Mapper);
service层
事务 ApplicationContext-trans.xml
@Service注解扫描 ApplicationContext-service.xml
controller层
SpringMvc.xml
注解扫描:扫描@Controller注解
注解驱动:替我们显示的配置最新版的处理器映射器(handlerMapping)和处理器适配器(handlerAdapter)
视图解析器:显示的配置是为了在controller中不用每个方法都写页面的全路径(requestMapping)
web.xml
springMvc前端控制器配置
spring监听
1、springMvc是什么?(市场占有率40%,web开发当之无愧的霸主)
2、springMvc执行流程(已经滚瓜乱熟了)
SpringMVC启动流程?
待补充
启动流程和运行流程有何区别?20181222
待补充
1、 @RequestMapping 用来处理请求地址映射的注解,可用于类或方法上
注解在方法上的@RequestMapping路径会继承注解在类上的路径
2、@RequestBody 注解实现接收 http 请求的 json 数据,将 json 数据转换为 java 对象
3、@Controller 它标记的类就是一个控制层对象
4、@Resource 和@Autowired 都是做 bean 的注入时使用
5、@PathVariable 用于将请求 URL 中的模板变量映射到功能处理方法的参数上,即取出uri模板中的变量作为参数。
6、@requestParam
@requestParam 主要用于在 SpringMVC 后台控制层获取参数,类似一种是 request.getParameter(“name”),他有三个常用参数 defaultValue,required,value
7、@Component 相当于通用的注解,当不知道一些类归到哪个层时使用,但是不建议
8、@RestController (组合注解@Controller和@ResponseBody)
如何开启注解处理器和适配器?
1、解决post请求乱码:
<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、默认类型:
2、基本类型:string, double, float, integer, long. boolean
3、pojo类型:页面上input框的name属性值必须要等于pojo的属性名称
4、vo类型(view object 用于表现层):页面上input框的name属性值必须要等于vo中的属性.属性.属性…
5、自定义转换器converter:
1、Rest是一种web服务实现方式,Http接口按照Rest风格设计就是 restful http
要求url中没有动词,只有名词,没有参数
例如:@RequestMapping(value="/viewItems/{id}")
2、REST架构的主要原则:
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 的幂等性?
2、如何保证接口的幂等性?****
有些接口可天然实现幂等性,比如查询接口:增加、更新、删除都要保证幂等性。那么如何来保证幂等性呢?
4、最佳实践
5、springMVC实现RESTful服务
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指定真正的请求方法
SpringMVC通过ModelAndView,把结果集拿到以后,从配置文件里获取对应的bean,类反射
区别:
备注:如今的开发基本不使用struts2了,基本都是sprigboot这一条脚手架,里面集成了springMvc。 2021010
使用方法:
1、拦截器interceptor:
2、过滤器:filter
3、监视器:listener
4、区别:
我们定义一个配置类MyMvcConfig,继承WebMVCConfigurerAdapter,并在此类中使用@EnableWebMvc注解,开启对springMVC的配置支持
1、静态资源映射 在配置里重写addResourceHandlers方法来实现
@override
public void addResourceHandlers(ResourceHandlerRegistry registry){
registry.addResourceHandler("/asserts/**").addResourceLocation("classpath:/asserts/");//前者是对外暴露的访问路径 后者是文件放置的目录
}
2、拦截器设置Interceptor
3、@ControllerAdvice 将对控制器的全局配置放置在同一个位置
(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
public void initBinder(WebDataBinder webDataBinder){
webDataBinder.setDisallowedFields("id");
}
@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、其他配置
5、文件上传配置(必备)springMvc通过配置MultipartResolver来上传文件,在spring的控制器中,通过MultipartFile file来接受文件
<div class="upload">
<form action="upload" enctype="multipart/form-data" method="post">
<input type="file" name="file"/>br>
<input type="submit" value="上传"/>
form>
div>
public void addViewController(ViewControllerRegistry registry){
registry.addViewController("/index").setViewName("/index");
registry.addViewController("/toUpload").setViewName("/upload");
}
@Bean
public MultipartResolver multipartResolver(){
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(1000000);
return multipartResolver;
}
@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里的数据
1、spring boot是什么
2、Springboot的特点?
3、SpringBoot注解大全:
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元注解,只是使用了不同的条件。
核心功能:
java配置:java Config提供了配置spring ioc容器的纯java方法,有助于避免使用xml配置,优点在于
日志管理(slf4j日志抽象层+logback日志实现层)
1、首先引入maven文件
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
SpringBoot提供了spring-boot-start-test启动器,该启动器提供了常见的单元测试库:
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
@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
@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
@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);
}
}
开发技巧:
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接口
Springboot自身的缓存在ConcurrentMap<>中,在实际开发中我们使用缓存中间件
几个重要的概念
@Cacheable/@CachePut的区别:前者先调缓存,后者先更新方法,在把结果存在缓存中,两者可以结合使用(@caching),注意:要保证取缓存的key和存缓存的key是一致的
几个属性:
redis数据类型 去看数据库部分
springboot redis保存数据:1、以jdk底层序列化方式 默认 2、json格式的序列化器,底层是jackson2的jar包
kafka/activeMQ/RabbitMQ 以rabbitMQ为例,端口号5672(客户端访问)/15672(访问管理页面) 引入的依赖项springboot-starter-activemq
MQ的作用:异步处理/应用解耦/流量削峰(秒杀)
JMS:java消息服务 ActiveMQ(队列类型有queue/topic)AMQP:高级消息队列协议 rabbitMQ(交换器类型有direct/fanout/topic)
异步任务 @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、配置中心配置项
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、 代码编写
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
解决办法:
4.2、在job任务接口实现类中,如果需要获取spring context,可以使用工具类
4.3、启动报错:
Caused by: java.lang.NoSuchMethodError: com.google.gson.stream.JsonWriter.jsonValue(Ljava/lang/String;)Lcom/google/gson/stream/
解决方法:
4.4、问题反馈
5、ElasticJob 原理分析
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版本)
shiro或security 注解@EnableWebSecurity 引入依赖:spring-boot-starter-security ,配置类必须扩展WebSecurityConfigurerAdapter并覆盖其方法。
1、Spring security与apache shiro的对比?
在分布式系统中国,国内常用zookeeper+dubbo组合(我们公司的分布式框架组合),而springboot推荐使用全栈的Spring(springboot+springcloud)
在开发中修改了一个java文件后想看到效果不得不重启应用,希望能在不重启应用的情况下,程序可以自动部署(该模块在生产环境中被禁用)
Spring Loaded/JRebel/springboot Devtools
作用:帮助我们访问生产环境中正在运行的应用程序的当前状态,监视器模块公开了一组可直接作为HTTP URL访问的REST端点来检查状态 .beans 容器中所有bean的信息;autconfig所有自动配置信息 auditevents审计信息 configprops 所有配置属性 dump线程状态信息 env当前环境信息 health应用健康状态; info当前应用信息 metrics应用的各项指标 mappings应用@RequestMapping映射路径 shutdown关闭当前应用 trace追踪信息
1、依赖注入 Spring:构造器注入xml文件 springboot:@Bean(value)+java配置
2、包扫描 spring:< context:component-scan> springboot:@ComponentScan(value)
1. 加载位置与顺序
SpringBoot启动会扫描以下位置的application.properties/yml文件作为spring boot的默认配置文件:
file:./config/ file: 指当前项目根目录
file:./
classpath:/config/ classpath: 指当前项目的resources目录
classpath:/
以上是按照优先级从高到低的顺序,所有位置的文件都会被加载,高优先级配置内容会覆盖低优先级配置的内容,并形成互补配置;当然,我们也可以通过spring.config.location来改变默认配置
建议:整体上把握前沿框架的应用范围和内部设计,了解主要组件和具体用途
细节待补充 20210110
xxxxx
除了spring.jar文件,Spring还包括有其它13个独立的jar包
1、spring-core.jar (核心)
2、spring-beans.jar (核心)
3、spring-aop.jar
4、spring-context.jar (核心)
5、spring-dao.jar
6、spring-hibernate.jar
7、spring-jdbc.jar
8、spring-orm.jar
9、spring-remoting.jar
10、spring-support.jar
11、spring-web.jar
12、spring-webmvc.jar
13、spring-mock.jar
五色令人目盲;五音令人耳聋;五味令人口爽;驰骋畋猎,令人心发狂;难得之货,令人行妨;是以圣人为腹不为目,故去彼取此。 --《道德经》