快速上手Spring框架(内容比较多,都是干货)1.什么是spring? spring是一个轻量级的控制反转(IOC)和
1.什么是spring?
spring是一个轻量级的控制反转(IOC)
和的面向切面编程(AOP)
的容器框架,主要是用来管理对象的生命周期的,这样就可以更加方便控制对象资源消耗 , 它集合了javaEE全功能解决方案
1.1 spring框架体系介绍:
- core container : 核心容器 , 主要通过BeanFactory管理对象的生命周期 控制哪些对象是单例模式 哪些对象是多例模式 它就是IOC
- spring AOP : 面向切面编程 底层实现是JDK动态代理
- Data access : 数据库访问模块 spring支持对于持久层框架的整合 spring可以整合 Mybatis Mybatis Plus Hibernate jdbc ...
- web : 控制层的功能 跟前端交互的 spring支持对于控制层框架整合 spring整合servlet struts2 springmvc ...
- Test : 测试模块 spring支持整合测试框架 整合junit
2. IOC --- 重点 面试题
IOC就是控制反转,就是将创建对象控制权 交给spring容器去管理 这样spring就可以集中管理这些对象生命周期 哪些只需要创建一个(单例) 哪些对象需要创建多个 这样就可以节省整个项目的资源消耗 这种方式,对象控制权原来是自己 后面交给spring容器 控制权由主动变成了被动就是控制反转
IOC控制反转是通过DI(依赖注入)实现的
User u=new User(); --- 对象控制权时我自己
User u2=spring容器帮我创建 --- 对象控制权就是spring容器
控制权由主动变成了被动就是控制反转
2.1 DI --重点
DI就是依赖注入 ,就是表示类和类的依赖关系(依赖属性) 不用自己赋值 交给spring会帮你自动注入(自动赋值), 注入过程中spring推荐你使用接口来当作依赖属性 因为接口可以提供很多种实现类 这样spring可以更加方便切换实现类而不修改代码 这样就降低了各层之间的依赖关系 实现了解耦合
2.2 DI依赖注入方式
-
set注入: 在类中给依赖属性 添加set方法 并且修改spring配置文件 添加bean标签 表示对象 里面添加property标签 用于给属性赋值 底层bean是通过无参构造方法来创建对象 创建完对象后 才通过set方法 给依赖属性赋值
优势在于: 注入的属性顺序和个数都是可选的 所以set注入
适合注入复杂依赖关系 更加直观
-
构造方法注入: 在类中添加有参构造方法 并且修改spring配置文件 添加bean标签 表示对象 里面添加constructor-arg标签 配置有参构造的参数 底层是调用有参构造 在对象创建的时候来完成所有属性的注入 如果有一个属性出现问题 导致创建对象也失败 而且可能会产生循环依赖的问题 所以它
不适合做复杂关系注入
-
注解注入: 借助于IOC注解和DI依赖注入注解来完成属性的注入
- IOC注解:@Controller @Service @Repository @Component
- DI注解: @Autowired(spring推荐使用) @Resource
- 最后spring配置文件:一定要配置扫描包(不能忘扫描 也不能多扫描)
2.3 循环依赖
循环依赖就是两个类相互依赖 比如A类依赖于B属性 ,B类也依赖于A属性 但是如果采用构造方法注入 会在对象实例化时借助于有参构造来完成依赖关系 但是A需要等B创建好 而B对象也需要等待A创建好 这就发生了循环依赖的问题
<!--假设A和B都提供有参构造--> public A(B b){}
<bean id="a" class="A类">
<construtor-arg ref="b"/>
</bean>
<bean id="b" class="B类"> public B(A a){}
<construtor-arg ref="a"/>
</bean>
解决方案:
- 可以重新设计 不设计成相互依赖的情况
- 如果不重新设计 可以使用set注入 或者 注解注入 原因是他们可以先利用无参构造把对象创建好 再完成依赖属性的注入
- 也可以添加一个@Lazy延迟加载注解 可以对依赖注入添加该注解 这样这个依赖属性不会立马注入 只会注入一个代理对象 只有当首次使用时 才会完成对象实例化过程 才进行注入
2.4 spring管理bean生命周期
spring默认管理的bean都是单例模式,除了默认的还提供很多种生命周期
- 单例: 默认的 读取spring配置文件(服务器启动) 就会加载所有的bean标签 也会扫描一些包的注解 创建bean对象 服务器关闭的时候就可以正常销毁
- 多例: 一个类spring使用一次创建一个新的 每次使用结束后 这个bean就会自动回收
- 请求request: 一个类spring每次发送请求 会创建一个新的对象 请求结束了自动回收
- 会话session: 一个类 spring每次会话的创建 一个新的对象 会话超时 或者强制销毁 对象才会回收
- 全局session: global session 就是application 属于应用级别的
2.4.1spring如何设置不同的生命周期
-
可以通过spring配置文件 修改bean标签 添加scope属性 来指定对象作用范围
- 注:如果要修改request session和全局session需要添加一个配置 否则失效
-
也可以通过@Scope注解 来设置对象作用范围
3. AOP
AOP是面向切面编程,主要用于将项目中的通用功能(事务 异常 日志 权限 资源回收) 和 主要的业务功能(正常处理注册 登录 删除 ...)进行分离, spring就可以将这些通用功能做成一个切面 指定的方法只要经过这个切面就会自动添加通用功能对于开发者而言 只需要关注于业务功能 底层实现是JDK动态代理实现的 可以不改变原程序基础上做增强处理(切面 也就是通用功能)
好处:
- 可以实现业务功能和通用功能的解耦合
- 可以很方便的添加额外功能 而且不影响原程序的运行 提高拓展性
3.1 AOP种的几个重要概念
-
Aspect: 切面,指定AOP中定义通用功能的类
-
JoinPoint: 连接点 业务功能作用在切面的位置
-
PointCut: 切入点 就是连接点的集合 目的是指定哪些方法需要被切入 spring是通过表达式和通配符方式 共同描述多种方法的 比如:让所有业务层方法 全部经过切面
execution(* com.sc.service.impl.*.*(..)) 任意返回值 包名.包名.所有类.所有方法(任意参数) void test(参数){ }
-
Advice: 通知 指定切面作用在 业务功能的时机和位置 (就是切面中的一个功能)
3.2 AOP通知类型
- 前置通知: aop:before标签 是在目标方法执行之前调用
- 后置通知: aop:after-returning标签 是在目标执行之后并且正常返回才调用
- 最后通知: aop:after标签 是在目标执行之后调用 类似于finally
- 异常通知: aop:after-throwing标签 是在目标方法执行时 发生异常了 才会执行
- 环绕通知: aop:around标签 是在目标方法执行前后都会调用(包含了前面四种通知)
try{
//前置通知
通过aop动态调用业务层所有方法
//后置通知
}catch(Exception e){
//异常通知
}finally{
//最后通知
}
3.3 spring如何实现aop
-
通过配置文件编写
-
写个类(表示切面) 随便写几个方法(表示通知)
//AOP实现日志功能: 让所有业务层方法执行的时候 可以添加日志 //日志切面 public class MyLog { /* public void aa(){ System.out.println("我是前置通知"); } public void bb(Object result){ System.out.println("我是后置通知,参数表示目标方法返回值"); } public void cc(Exception e){ System.out.println("我是异常通知,参数表示目标方法发生异常"); } public void dd(){ System.out.println("我是最后通知"); } */ //配置环绕通知方法 public Object ee(ProceedingJoinPoint jp){ Object result=null; try { System.out.println("前置"); result=jp.proceed(); //等价于jdk动态代理invoke() 表示目标方法调用 System.out.println("后置"); }catch (Throwable e){ System.out.println("异常"); e.printStackTrace(); //打印堆栈信息 }finally { System.out.println("最后"); } return result; } }
-
通过spring配置文件告诉它 哪个对象切面 哪个方法是通知
<!--aop配置--> <aop:config> <!--1.配置切入点:告诉spring哪些方法需要经过切面--> <aop:pointcut id="pc" expression="execution(* com.sc.service.impl.*.*(..))"/> <!--2.配置切面: 告诉spring哪个bean是负责实现切面--> <!--可以配置多个切面--> <aop:aspect id="log" ref="myLog"> <!--告诉spring切面的哪个方法是做什么通知--> <!-- <aop:before method="aa" pointcut-ref="pc" /> <aop:after-returning method="bb" returning="result" pointcut-ref="pc"/> <aop:after-throwing method="cc" throwing="e" pointcut-ref="pc"/> <aop:after method="dd" pointcut-ref="pc"/> --> <aop:around method="ee" pointcut-ref="pc"/> </aop:aspect> </aop:config> <bean id="myLog" class="com.sc.aop.MyLog"></bean>
-
-
通过aop注解来完成
-
通过@Component注解 让spring扫描创建bean对象
-
通过@Aspect注解 标注切面
-
通过@Pointcut注解配置切入点
-
最后@Before @After @AfterReturning @AfterThrowing @Around
@Component //通过IOC扫描 等价于编写了bean标签 @Aspect //标注我是切面 等价于配置Aop:aspect标签 public class MyLog2 { //配置切入点注解: 等价于 aop:pointcut标签 @Pointcut("execution(* com.sc.service.impl.*.*(..))") public void pc(){} /* @Before("pc()") public void before(){ System.out.println("前置"); } @AfterReturning(value = "pc()",returning = "result") public void afterReturning(Object result){ System.out.println("后置"); } @After("pc()") public void after(){ System.out.println("最后"); } @AfterThrowing(value = "pc()",throwing ="e") public void afterThrowing(Exception e){ System.out.println("异常"); } */ @Around("pc()") public Object around(ProceedingJoinPoint jp){ Object result= null; //获取目标方法的名称 String methodName=jp.getSignature().getName(); try { Object[] args=jp.getArgs(); System.out.println("\033[33m"+new Date()+"【前置】:"+methodName+ "开始调用,参数:"+ Arrays.toString(args)+"\033[0m"); result = jp.proceed(); System.out.println("\033[33m"+new Date()+"【后置】:"+methodName+ "运行结束,返回值:"+result+"\033[0m"); } catch (Throwable e) { //e.printStackTrace(); System.out.println("\033[33m"+new Date()+"【异常】:"+methodName+ "运行时发生异常:"+e+"\033[0m"); } finally { System.out.println("\033[33m"+new Date()+"【最后】:"+methodName+ "运行结束"+"\033[0m"); } return result; } }
-
spring配置文件 只要开启AOP注解即可
<aop:aspectj-autoproxy/>
-
3.4 spring如何实现事务功能
-
声明式事务: 通过配置文件 配置事务严格的策略 事务传播特性
- 加载jdbc配置文件
- 创建数据库连接池(德鲁伊连接池)
- 创建事务管理类(类似于spring写好的环绕通知)
- 配置事务管理策略(控制事务传播特性 控制哪些方法加事务 哪些只读事务)
- 配置AOP==>配置切入点 关联前面的事务策略
-
注解式事务: 配置完后 只需要通过@Transactional注解 就可以做事务
-
加载jdbc配置文件
-
创建数据库连接池(德鲁伊连接池)
-
创建事务管理类(类似于spring写好的环绕通知)
-
开启事务注解
使用时什么方法需要做事务 添加@Transactional注解,也可以写在类上 表示这个类的所有方法都需要做事务
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--spring实现事务--> <!--1.加载jdbc配置文件 读取后${key} 获取value--> <context:property-placeholder location="classpath*:jdbc.properties"/> <!--2.创建德鲁伊连接池--> <bean id="ds" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <!--必要配置--> <property name="url" value="${jdbc.url}"/> <property name="driverClassName" value="${jdbc.driver}"/> <!--bug如果jdbc的key是username和password spring会把它当成系统用户账号和密码--> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <!--可选配置: 可以不写 因为都有默认值--> <!--初始连接大小--> <property name="initialSize" value="5"/> <!--最小连接数--> <property name="minIdle" value="5"/> <!--最大连接数--> <property name="maxActive" value="20"/> <!--最大等待时间 单位是毫秒--> <property name="maxWait" value="60000"/> <!--...--> </bean> <!--3.配置事务管理类--> <bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--注入连接池--> <property name="dataSource" ref="ds"/> </bean> <!-- <!–4.配置声明式事务策略–> <tx:advice id="ad" transaction-manager="tm"> <tx:attributes> <!–配置规则: 配置不同种类的方法要做什么样的事务 还有事务的传播特性 name: 表示哪种方法 通常通过通配符编写 read-only: 设置是否是只读事务 默认是false propagation: 设置事务传播特性 REQUIRED 比如name:add* 表示切入点里面所有类的所有方法只要add开头的方法都算 –> <tx:method name="add*" read-only="false" propagation="REQUIRED"/> <tx:method name="insert*" read-only="false" propagation="REQUIRED"/> <tx:method name="save*" read-only="false" propagation="REQUIRED"/> <tx:method name="del*" read-only="false" propagation="REQUIRED"/> <tx:method name="delete*" read-only="false" propagation="REQUIRED"/> <tx:method name="update*" read-only="false" propagation="REQUIRED"/> <tx:method name="edit*" read-only="false" propagation="REQUIRED"/> <!–如果是查询 一般都是只读事务 因为查询不改数据–> <tx:method name="select*" read-only="true" propagation="REQUIRED"/> <tx:method name="query*" read-only="true" propagation="REQUIRED"/> <tx:method name="show*" read-only="true" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <!–5.配置aop: 切入点–> <aop:config> <aop:pointcut id="pc" expression="execution(* com.sc.service.impl.*.*(..))"/> <aop:advisor advice-ref="ad" pointcut-ref="pc"/> </aop:config>--> <!--4.开启事务注解--> <tx:annotation-driven transaction-manager="tm"/> <aop:aspectj-autoproxy/> </beans>
-
4.spring加载配置文件方式
-
添加MVC依赖 (包含spring监听器)
<!--spring版本一定要和springmvc版本是一致的 否则出现error--> <!--springmvc核心依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.0.3.RELEASE</version> </dependency>
-
添加全局配置:配置spring配置文件地址
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring-two.xml</param-value> </context-param>
-
配置spring提供的监听器: 加载spring配置文件
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
5.spring常用注解
-
IOC扫描注解 : 一般是使用在类上面 , spring配置文件 必须提供扫描包 spring只要扫描到类上有这类的注解 它就会自动创建该类的对象(等价于spring帮你自动编写了bean标签) 下面四个注解底层实现都是一样的 Controller和Service和Repository共同父类注解都是Component ,只是他们标记的身份不同 而且还会提供默认bean的id: 类名首字母小写 如果想修改bean的id :@IOC扫描注解("id")比如:@Controller("controller")
- @Controller: 标注控制层的的注解,因为只有控制层才能接收请求
- @Service: 标注业务层的注解,只有业务层才能正常完成事务
- @Repository: 标记mapper(Dao)层注解 ,后期可以不用写 spring后期可以整合mybatis的mapper接口(创建它的实现类) 不是靠扫描创建出来的
- @Component: 标注其他层的注解 比如:过滤器 拦截器 插件 工具类
-
DI依赖注入注解 : 一般写在成员变量上的注解,用于通过spring IOC容器中的bean对象给该成员变量赋值 前提是: IOC容器必须有这个bean对象
@Resource和@Autowired注解区别 ---面试题
-
@Autowired: 是spring提供的注解,自动根据spring IOC容器 bean对象类型去匹配 再根据依赖属性名 和容器中的bean的id匹配 如果匹配上了则自动注入(赋值) 如果匹配不上 spring则会抛出没有bean的异常 如果匹配上了 但是不止一个 spring也会抛出异常因为不知道注入哪个类型
解决方案: 通过@Qualifier("bean的id") 用于指定使用哪个bean来注入
-
@Resource: 是java自带的注解,先匹配容器中bean的类型 匹配上了自动赋值 否则报错 如果匹配上了 但是不止一个 看还可以根据bean的id去匹配关联的属性名 也会自动赋值 如果关联属性名和bean的id不同 也可以@Resource(name="bean的id")
-
-
AOP注解 :
- @Aspect: 配置切面
- @PointCut: 配置切入点
- @Before: 前置通知 @AfterReturning:后置通知 @AfterThrowing:异常通知 @After:最后通知
- @Around: 环绕通知
-
MVC注解 :
- @RequestMapping @GetMapping @PostMapping
- @DateTimeFormat @JsonFormat
- @RequestBody @ResponseBody
- @RequestParam ...
-
其他注解 :
- @Scop: 添加作用域(指定bean对象作用范围)
6.spring事务传播特性
spring一共提供了七种事务传播特性, 通过它可以控制哪些方法支持事务 哪些不支持 哪个支持嵌套事务 随着业务复杂很可能出现 业务层要调用其他业务层的情况 这种时候 就会出现一个事务嵌套到了另一个事务中了.
- required: 默认值 必须有一个事务 ,如果事务不存在 则开启一个新的事务
- required_new: 新的事务 必须运行在自己新建的事务中
- supports: 支持事务, 不要求有事务 但是有事务会一直支持
- not_supports: 不支持事务, 如果有事务 也不会运行
- never: 永不支持事务 如果有事务会抛出异常
- nested: 嵌套事务 可以支持多个事务之间嵌套在一起的 里面事务不能影响外层事务
- mandatory: 必须要有事务 没有事务则抛出异常
转载自:https://juejin.cn/post/7413931157455536180