Spring总结

Spring总结


Spring Framework:非侵入式、组件化

Core Container

Spring 框架的核心模块,也可以说是基础模块,主要提供 IoC 依赖注入功能的支持。Spring 其他所有的功能基本都需要依赖于该模块

  • spring-core:Spring 框架基本的核心工具类。
  • spring-beans:提供对 bean 的创建、配置和管理等功能的支持。
  • spring-context:提供对国际化、事件传播、资源加载等功能的支持。
  • spring-expression:提供对表达式语言(Spring Expression Language) SpEL 的支持,只依赖于 core 模块,不依赖于其他模块,可以单独使用。

AOP

  • spring-aspects:该模块为与 AspectJ 的集成提供支持。
  • spring-aop:提供了面向切面的编程实现。

Data Access/Integration

  • spring-jdbc:提供了对数据库访问的抽象 JDBC。不同的数据库都有自己独立的 API 用于操作数据库,而 Java 程序只需要和 JDBC API 交互,这样就屏蔽了数据库的影响。
  • spring-tx:提供对事务的支持。
  • spring-orm:提供对 Hibernate、JPA、iBatis 等 ORM 框架的支持。
  • spring-oxm:提供一个抽象层支撑 OXM(Object-to-XML-Mapping),例如:JAXB、Castor、XMLBeans、JiBX 和 XStream 等。
  • spring-jms : 消息服务。自 Spring Framework 4.1 以后,它还提供了对 spring-messaging 模块的继承。

Spring Web

  • spring-web:对 Web 功能的实现提供一些最基础的支持。
  • spring-webmvc:提供对 Spring MVC 的实现。
  • spring-websocket:提供了对 WebSocket 的支持,WebSocket 可以让客户端和服务端进行双向通信。
  • spring-webflux:提供对 WebFlux 的支持。WebFlux 是 Spring Framework 5.0 中引入的新的响应式框架。与 Spring MVC 不同,它不需要 Servlet API,是完全异步。

Messaging

  • spring-messaging 是从 Spring4.0 开始新加入的一个模块,主要职责是为 Spring 框架集成一些基础的报文传送应用。

MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。

IOC

IoC(控制反转,Inversion of Control):

  • 控制:指的是对象创建(实例化、管理)的权力

  • 反转:控制权交给外部环境(Spring 框架、IoC 容器)

    反转了什么?【对象的创建权力/对象和对象关系的维护权力】交给第三方容器管理

IoC容器管理所有java对象的实例化与初始化,控制对象与对象之间的依赖关系

在实际项目中一个 Service 类可能依赖了很多其他的类,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。

IoC容器根据Bean的定义信息【xml中的bean定义信息转化为BeanDefinition,被BeanDefinitionReader读取】,通过BeanFactory工厂+反射来实例化对象,然后进行初始化形成最终的对象Bean

Spring Bean:IoC容器管理的Java对象

spring用反射创建对象到Map<String,BeanDefinition> beanDifinitionMap中:IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。

DI(Dependency Injection,依赖注入):Spring创建对象的过程中,将对象属性通过配置进行注入

自动装配:

@Autowire 自动装配:属性、setter方法、构造方法、构造方法形参上都可以【与@Qualifier(value=“”)联合可在同类型的多个类中精准选定bean】

@Resource 也可完成属性注入,属性、setter方法【属于JDK扩展包中的,JDK8以外的需要引入jakarta.annotation-api依赖】

Autowire 默认采用byType,若想按名称则需与Qualifier一起用

Resource 默认采用byName,未指定name时将属性名当作name,找不到的话则按类型

bean生命周期:

bean无参构建(实例化bean对象)
给bean设置相关属性 set方法
检查Aware相关接口并设置相关依赖【BeanNameAware。。。】
bean后置处理器(初始化之前) BeanPostProcessor接口的postProcessBeforeInitialization()
如果 Bean 实现了InitializingBean接口,执行afterPropertiesSet()方法。
bean对象初始化(调用初始化方法)init-method设置
bean后置处理器(初始化之后)BeanPostProcessor接口的postProcessAfterInitialization()

Spring 中 Bean 的作用域通常有下面几种:

  • singleton : IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。
  • prototype : 每次获取都会创建一个新的 bean 实例。也就是说,连续 getBean() 两次,得到的是不同的 Bean 实例。
  • request (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。
  • session (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。
  • application/global-session (仅 Web 应用可用):每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。
  • websocket (仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)

**Bean是线程安全的吗?**prototype 作用域下,每次获取都会创建一个新的 bean 实例,不存在资源竞争问题,所以不存在线程安全问题。singleton 作用域下,IoC 容器中只有唯一的 bean 实例,可能会存在资源竞争问题(取决于 Bean 是否有状态)。如果这个 bean 是有状态的话,那就存在线程安全问题(有状态 Bean 是指包含可变的成员变量的对象)。

对于有状态单例 Bean 的线程安全问题,常见的有两种解决办法:

  1. 在 Bean 中尽量避免定义可变的成员变量。
  2. 在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。

AOP

(案例:日志记录)需要把附加的日志功能从业务代码中抽离出来,困难在于日志功能中有方法的参数,不能简单的抽出来搞个工具类就行

代理模式:通过调用代理对象的方法,来让代理对象调用目标方法

动态代理:对日志功能进行统一管理

public class ProxyFactory {

    //目标对象
    private Object target;

    public ProxyFactory(Object target){
        this.target = target;
    }

    
    public Object getProxy(){
        ClassLoader classLoader = target.getClass().getClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("before");
                Object result = method.invoke(target, args);
                System.out.println("after");
                return result;
            }
        };
        return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
    }
}

横向关注点:分散在各个模块中解决相同的问题(日志管理…)

通知(增强):添加的功能(通知方法)

切面:封装通知方法的类

连接点:spring允许使用通知的地方

切入点:实际去实现增强的方法,通过切入点定位到特定的连接点(连接点=数据库中的记录,切入点=SQL语句)

JDK动态代理:拜把兄弟(有接口,代理对象与目标对象实现同样的接口)

cglib动态代理:认干爹(无接口,代理对象继承目标对象)

Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理

AspectJ:AOP框架,Spring借用了它的注解(本质上静态代理,将代理逻辑“织入”到目标对象的字节码文件中,实现了动态的效果)

需要开启aop中的自动
<aop:aspectj-autoproxy/>


@Aspect
@Component
public class LogAspect {
	//切入点表达式 也可写为"execution(* org.annoaop.CalculatorImpl.*(..))"
    @Before(value = "execution(public void org.annoaop.CalculatorImpl.*(..))")
    public void beforeMethod(){
        System.out.println("before:");
    }
    
    //返回目标方法的结果
    @AfterReturning(value = "execution(public void org.annoaop.CalculatorImpl.*(..))",returning = "result")
    public void afterReturningMethod(Object result){
        System.out.println("returning:" + result);
    }
    
    //发生异常后触发
    @AfterThrowing(value = "execution(public void org.annoaop.CalculatorImpl.*(..))")
    public void afterReturningMethod(Throwable ex){
        System.out.println("error:" + ex);
    }
    
    //在后置返回、后置异常之后发生
    @After(value = "execution(public void org.annoaop.CalculatorImpl.*(..))")
    public void afterMethod(JoinPoint joinpoint){
    	String methodName = joinpoint.getSignature().getName();	//通过joinpoint可以得到目标方法相关信息
        System.out.println("after:" + methodName);
    }
    
    @Around(value = "execution(public void org.annoaop.CalculatorImpl.*(..))")
    public void aroundMethod(ProceedingJoinPoint proceed){ 
        try {
            System.out.println("around-before");
            proceed.proceed();	//由此来执行目标方法
            System.out.println("around-after-returning");
        } catch (Throwable e) {
            System.out.println("around-ex");
            throw new RuntimeException(e);
        }finally {
            System.out.println("around-after");
        }

    }
}

切入点重用:

@PointCut(value = "execution(public void org.annoaop.CalculatorImpl.*(..))")
public void pointCut(){}

@Before(value = "pointCut()")	//同一切面类内重用切入点,不同类时前面需要加包名
xxx

切面优先级:内外嵌套顺序(外面>里面的,或者用@Order来指定)

xml配置:

<aop:config>
	//配置切面类
	<aop:aspect ref="logAspect">
		//配置切面点
		<aop:pointcut id="pointcut" expression="execution(public void org.annoaop.CalculatorImpl.*(..))"/>
		//前置通知
		<aop:before method="beforeMethod" point-ref="pointcut"/>
		
	</aop:aspect ref="logAspect">
</aop:config>

Spring MVC

  • DispatcherServlet核心的中央处理器,负责接收请求、分发,并给予客户端响应。
  • HandlerMapping处理器映射器,根据 uri 去匹配查找能处理的 Handler ,并会将请求涉及到的拦截器和 Handler 一起封装。
  • HandlerAdapter处理器适配器,根据 HandlerMapping 找到的 Handler ,适配执行对应的 Handler
  • Handler请求处理器,处理实际请求的处理器。
  • ViewResolver视图解析器,根据 Handler 返回的逻辑视图 / 视图,解析并渲染真正的视图,并传递给 DispatcherServlet 响应客户端

  1. 客户端(浏览器)发送请求, DispatcherServlet拦截请求。
  2. DispatcherServlet 根据请求信息调用 HandlerMappingHandlerMapping 根据 uri 去匹配查找能处理的 Handler(也就是我们平常说的 Controller 控制器) ,并会将请求涉及到的拦截器和 Handler 一起封装。
  3. DispatcherServlet 调用 HandlerAdapter适配器执行 Handler
  4. Handler 完成对用户请求的处理后,会返回一个 ModelAndView 对象给DispatcherServletModelAndView 顾名思义,包含了数据模型以及相应的视图的信息。Model 是返回的数据对象,View 是个逻辑上的 View
  5. ViewResolver 会根据逻辑 View 查找实际的 View
  6. DispaterServlet 把返回的 Model 传给 View(视图渲染)。
  7. View 返回给请求者(浏览器

统一异常处理怎么做?

@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {

    @ExceptionHandler(BaseException.class)
    public ResponseEntity<?> handleAppException(BaseException ex, HttpServletRequest request) {
      //......
    }

    @ExceptionHandler(value = ResourceNotFoundException.class)
    public ResponseEntity<ErrorReponse> handleResourceNotFoundException(ResourceNotFoundException ex, HttpServletRequest request) {
      //......
    }
}

给所有或者指定的 Controller 织入异常处理的逻辑(AOP)

ExceptionHandlerMethodResolvergetMappedMethod 方法决定了异常具体被哪个被 @ExceptionHandler 注解修饰的方法处理异常。

@Nullable
	private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
		List<Class<? extends Throwable>> matches = new ArrayList<>();
    //mappedMethods 中存放了异常和处理异常的方法的对应关系
		for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
			if (mappedException.isAssignableFrom(exceptionType)) {
				matches.add(mappedException);
			}
		}
    // 不为空说明有方法处理异常
		if (!matches.isEmpty()) {
      // 按照匹配程度从小到大排序
			matches.sort(new ExceptionDepthComparator(exceptionType));
      // 返回处理异常的方法
			return this.mappedMethods.get(matches.get(0));
		}
		else {
			return null;
		}
	}

首先找到可以匹配处理异常的所有方法信息,然后对其进行从小到大的排序,最后取最小的那一个匹配的方法(即匹配度最高的那个)

事务

编程式事务:在代码中硬编码(不推荐使用) : 通过 TransactionTemplate或者 TransactionManager 手动管理事务

声明式事务:在 XML 配置文件中配置或者直接基于注解(推荐使用) : 实际是通过 AOP 实现(基于@Transactional 的全注解方式使用最多)

传播行为:解决业务层方法之间互相调用的事务问题

正确的事务传播行为可能的值如下:

1.TransactionDefinition.PROPAGATION_REQUIRED

如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。【@Transactional注解默认使用就是这个事务传播行为。】

2.TransactionDefinition.PROPAGATION_REQUIRES_NEW

不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。

3.TransactionDefinition.PROPAGATION_NESTED

如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则创建一个新的事务

4.TransactionDefinition.PROPAGATION_MANDATORY(mandatory:强制性)

如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。这个使用的很少。

若是错误的配置以下 3 种事务传播行为,事务将不会发生回滚:

  • TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常

隔离级别:

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

TransactionDefinition.ISOLATION_READ_UNCOMMITTED :最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读

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

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

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

脏读(读取未提交数据):A事务读取B事务尚未提交的数据,此时如果B事务发生错误并执行回滚操作,那么A事务读取到的数据就是脏数据

不可重复读(前后读取同一数据内容不一致):事务A在执行读取操作,由于整个事务A处理时间长,前后读取同一条数据需要经历很长的时间 。而在事务A第一次读取数据,比如此时读取了小明的年龄为20岁,事务B执行更改操作,将小明的年龄更改为30岁,此时事务A第二次读取到小明的年龄时,发现其年龄是30岁,和之前的数据不一样了,也就是数据不重复了,系统无法读取到重复的数据,成为不可重复读。

幻读(前后读取的数据总量不一致):事务A在执行读取操作,需要两次统计数据的总量,前一次查询数据总量后,此时事务B执行了新增数据的操作并提交后,这个时候事务A读取的数据总量和之前统计的不一样,就像产生了幻觉一样,平白无故的多了几条数据,成为幻读。

@Transactional(rollbackFor = Exception.class)注解了解吗?

@Transactional 注解中如果不配置rollbackFor属性,那么事务只会在遇到RuntimeException的时候才会回滚,加上 rollbackFor=Exception.class,可以让事务在遇到非运行时异常时也回滚

作用于类:当把@Transactional 注解放在类上时,表示所有该类的 public 方法都配置相同的事务属性信息。

作用于方法:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息。

SpringBoot(TODO)

Spring Boot 通过@EnableAutoConfiguration开启自动装配,通过 SpringFactoriesLoader 最终加载META-INF/spring.factories中的自动配置类实现自动装配,自动配置类其实就是通过@Conditional按需加载的配置类,想要其生效必须引入spring-boot-starter-xxx包实现起步依赖

Spring security

  • permitAll():无条件允许任何形式访问,不管你登录还是没有登录。
  • anonymous():允许匿名访问,也就是没有登录才可以访问。
  • denyAll():无条件决绝任何形式的访问。
  • authenticated():只允许已认证的用户访问。
  • fullyAuthenticated():只允许已经登录或者通过 remember-me 登录的用户访问。
  • hasRole(String) : 只允许指定的角色访问。
  • hasAnyRole(String) : 指定一个或者多个角色,满足其一的用户即可访问。
  • hasAuthority(String):只允许具有指定权限的用户访问
  • hasAnyAuthority(String):指定一个或者多个权限,满足其一的用户即可访问。
  • hasIpAddress(String) : 只允许指定 ip 的用户访问。

使用 hasAuthority 更具有一致性,你不用考虑要不要加 ROLE_ 前缀,数据库什么样这里就是什么样!而 hasRole 则不同,代码里如果写的是 admin,框架会自动加上 ROLE_ 前缀,所以数据库就必须是 ROLE_admin

常用注解

1.SpringBootApplication

@SpringBootApplication = @Configuration+@EnableAutoConfiguration+@ComponentScan

@EnableAutoConfiguration:启用 SpringBoot 的自动配置机制

@ComponentScan:扫描被@Component (@Repository,@Service,@Controller)注解的 bean,注解默认会扫描该类所在的包下所有的类。

@Configuration:允许在 Spring 上下文中注册额外的 bean 或导入其他配置类

2.Bean

@RestController=@Controller +@ResponseBody 返回 JSON 或 XML 形式

@Controller的话一般是用在要返回一个视图的情况,这种情况属于比较传统的 Spring MVC 的应用,对应于前后端不分离的情况。

@Scope(“singleton”)

singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。

prototype : 每次请求都会创建一个新的 bean 实例。

request : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。

session : 每一个 HTTP Session 会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效

3.HTTP

@GetMapping(“users”) =@RequestMapping(value=“/users”,method=RequestMethod.GET)

@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
public ResponseEntity signUp(@RequestBody @Valid UserRegisterRequest userRegisterRequest)

Request请求的body 部分并且Content-Type 为 application/json 格式的数据

系统会使用HttpMessageConverter或者自定义的HttpMessageConverter将请求的 body 中的 json 字符串转换为 java 对象。

4.读取配置信息

@Value("${cui}")
String cui;

@ConfigurationProperties(prefix = "library")
class LibraryProperties 

5.JSR( Specification Requests)

验证请求体

public ResponseEntity<Person> getPerson(@RequestBody @Valid Person person)

验证请求参数【一定不要忘记在类上加上 @Validated 注解

@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);
    }
}

6.测试

@ActiveProfiles一般作用于测试类上, 用于声明生效的 Spring 配置文件。

@Test声明一个方法为测试方法

@Transactional被声明的测试方法的数据会回滚,避免污染测试数据。

@WithMockUser Spring Security 提供的,用来模拟一个真实用户,并且可以赋予权限。

面试题

参考资料

Java 面试指南 | JavaGuide(Java面试 + 学习指南)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值