Spring学习笔记
IOC/AOP
- IOC
- 管理创建和组装对象之间的依赖关系
- 使⽤前:⼿⼯创建
- 使⽤后:Spring创建,⾃动注⼊
- 核⼼:把创建对象的控制权反转给Spring框架,对象的⽣命周期由Spring统⼀管理
- AOP
- ⾯向切⾯编程(AOP)可以解耦核⼼业务和边缘业务的关系
- 场景:⽤户调⽤下单购买视频接⼝,需要判断登录,拦截器是AOP思想的⼀种实现
- 使⽤前:代码写逻辑,每次下单都调⽤⽅法判断,多个⽅法需要判断登录则都需要 登录⽅法判断
- 使⽤后:根据⼀定的⽅法或者路径规则进⾏判断是否要调⽤,降低代码耦合度
配置文件
命名空间
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/springbeans.xsd">
beans
用于配置bean,注册将来会注入的对象到Spring IOC容器中
<!--
bean标签 id属性:指定Bean的名称,在Bean被别的类依赖时使⽤
name属性:⽤于指定Bean的别名,如果没有id,也可以⽤name
class属性:⽤于指定Bean的来源,要创建的Bean的class类,需要全限定名
-->
<bean id="Video" name="Video" class="site.leric.domain.Video">
<!--
name 指定要注入的属姓名
value 注入值
-->
<property name="id" value="9"/>
<property name="title" value="Spring 5.x"/>
</bean>
依赖注入
<bean id="VideoOrder" class="site.leric.domain.VideoOrder">
<property name="id" value="8"/>
<property name="outTradeNo" value="1454265hjnlkj"/>
<!--
video属性 依赖于 video
ref 将id为video的bean 注入到VideoOrder中的video属性中
-->
<property name="video" ref="Video"/>
</bean>
bean的作用域
- scope属性
- singleton:单例, 默认值,调⽤getBean⽅法返回是同⼀个对象,实例会被缓存起来,效率⽐较⾼ 当⼀个bean被标识为singleton时候,spring的IOC容器中只会存在⼀个该bean
- prototype: 多例,调⽤getBean⽅法创建不同的对象,会频繁的创建和销毁对象造成很⼤的
开销 - 其他少⽤ (作⽤域 只在 WebApplicationContext (web环境下))
- request :每个Http请求都会创建⼀个新的bean
- session: 每个Http Session请求都会创建⼀个新的bean
- global session(基本不⽤)
<!-- 使用scope属性指定作用域 -->
<bean id="Video" name="Video" class="site.leric.domain.Video" scope="prototype">
<property name="id" value="9"/>
<property name="title" value="Spring 5.x"/>
</bean>
三种注入方式
setter注入
会调用POJO的setter方法进行注入
<bean id="video" class="net.xdclass.sp.domain.Video" scope="singleton">
<property name="id" value="9"/>
<property name="title" value="Spring 5.X课程" />
</bean>
若bean里面有依赖的话也是通过setter方法进行注入
构造器注入
会调用POJO的构造函数进行注入。
注意:要定义带参数构造函数,且类的构造函数重写的时候,⼀定要保留空构造函数以保证其他注入方法正常运行
<bean id="VideoConstructor" class="site.leric.domain.Video">
<!-- <constructor-arg name="title" value="测试"/>-->
<!--使用索引注入-->
<constructor-arg index="0" value="测试"/>
</bean>
POJO类型注⼊
property 没有使⽤value属性,⽽是使⽤了ref属性
<bean id="VideoOrder" class="site.leric.domain.VideoOrder">
<property name="id" value="8"/>
<property name="outTradeNo" value="1454265hjnlkj"/>
<!--POJO注入-->
<property name="video" ref="Video"/>
</bean>
List与Map的注入
注入list类型
<!--注入list类型-->
<property name="chapterList">
<list>
<value>第一章</value>
<value>第二章</value>
<value>第三章</value>
</list>
</property>
注入map类型
<!--注入list类型-->
<property name="videoMap">
<map>
<entry key="1" value="第一节"/>
<entry key="2" value="第二节"/>
<entry key="3" value="第三节"/>
</map>
</property>
SpringIOC内bean的继承
-
bean继承:两个类之间⼤多数的属性都相同,避免重复配置,通过bean标签的parent属性重⽤已有的Bean元素的配置信息
继承指的是配置信息的复⽤,和Java类的继承没有关系
<!-- 使用parent属性指定bean继承video1,video2公用部分 --> <bean id="Video2" class="site.leric.domain.Video2" parent="Video"> <property name="summary" value="简介"/> </bean>
-
属性依赖: 如果类A是作为类B的属性, 想要类A⽐类B先实例化,设置两个Bean的依赖关系
<!-- 指定Video先于VideoOrder实例化 --> <bean id="VideoOrder" class="site.leric.domain.VideoOrder" depends-on="Video"> <property name="id" value="8"/> <property name="outTradeNo" value="1454265hjnlkj"/ <!-- video属性 依赖于 video ref 将id为video的bean 注入到VideoOrder中的video属性中 --> <property name="video" ref="Video"/> </bean>
生命周期:初始化与销毁
在POJO自定义初始化与销毁方法
// 初始化方法
public void init(){
System.out.println("Video被初始化");
}
// 销毁方法
public void destroy(){
System.out.println("Video被销毁");
}
在配置文件对应的bean中配置初始化方法与销毁方法
<bean id="Video" name="Video" class="site.leric.domain.Video" scope="singleton"
init-method="init" destroy-method="destroy">
...
</bean>
==============================
Video3 空参构造
Video被初始化
Video被销毁
后置处理器BeanPostProessor
-
什么是BeanPostProcessor
- 是Spring IOC容器给我们提供的⼀个扩展接⼝,实现了规定接口的bean成为后置处理器,实现bean初始化前后的一些处理
- 在调⽤初始化⽅法前后对 Bean 进⾏额外加⼯,ApplicationContext 会⾃动扫描实现了BeanPostProcessor的 bean,并注册这些 bean 为后置处理器
- 是Bean的统⼀前置后置处理⽽不是基于某⼀个bean
- 注意:接⼝重写的两个⽅法不能返回null,如果返回null那么在后续初始化⽅法将报空指针异常或者通过getBean()⽅法获取不到bean实例对象
public class CustomBeanPostProcessor implements BeanPostProcessor, Ordered { public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("CustomerBeanPostProcessor1 postProcessBeforeInitialization beanName" + beanName); return bean; } public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("CustomerBeanPostProcessor1 postProcessAfterInitialization beanName" + beanName); return bean; } public int getOrder() { return 0; } ===============配置文件注册处理器=============== <!-- 注册BeanPostProcessor --> <bean class="site.leric.processor.CustomBeanPostProcessor"/>
-
执行顺序
Spring IOC容器实例化Bean 调⽤BeanPostProcessor的postProcessBeforeInitialization⽅法 调⽤bean实例的初始化⽅法 调⽤BeanPostProcessor的postProcessAfterInitialization⽅法
-
可以注册多个BeanPostProcessor顺序
- 在Spring机制中可以指定后置处理器调⽤顺序,通过BeanPostProcessor接⼝实现类实现Ordered接⼝getOrder⽅法,该⽅法返回整数,默认值为 0优先级最⾼,值越⼤优先级越低
使用autoWired属性实现自动注入
-
属性注⼊
- 前⾯学过属性注⼊,set⽅法、构造函数等,属于⼿⼯注⼊
-
有没办法实现⾃动装配注⼊?
-
Spring⾃动注⼊
-
使⽤元素的 autowire 属性为⼀个 bean 定义指定⾃动装配模式
-
autowire设置值
-
no:没开启
-
byName: 根据bean的id名称,注⼊到对应的属性⾥⾯
-
byType:根据bean需要注⼊的类型,注⼊到对应的属性⾥⾯
-
如果按照类型注⼊,存在2个以上bean的话会抛异常
-
-
No qualifying bean of type ‘site.leric.domain.Video3’ available: expected single matching bean but found 2: video,video2
```- constructor: 通过构造函数注⼊,需要这个类型的构造函数
-
<bean id="video" class="site.leric.domain.Video3"> <property name="id" value="5"/> <property name="title" value="autowired"/> </bean> <!--用autowire取代ref引入--> <bean id="VideoOrder2" class="site.leric.domain.VideoOrder3" autowire="byName"> <property name="id" value="8"/> <property name="outTradeNo" value="1454265hjnlkj"/> </bean>
// 被注入的类型的构造函数 public VideoOrder3() { System.out.println("VideoOrder空参数构造函数被调用"); }
-
AOP
-
什么是AOP
- Aspect Oriented Program ⾯向切⾯编程
-
在到达目标业务代码之前先对对象进行处理
- 在不改变原有逻辑上增加额外的功能,⽐如解决系统层⾯的问题,或者增加新的功能
-
场景
- 权限控制
- 缓存
- ⽇志处理
- 事务控制
-
AOP思想把功能分两个部分,分离系统中的各种关注点
- 核⼼关注点
- 业务的主要功能
- 横切关注点
- ⾮核⼼、额外增加的功能
- 核⼼关注点
-
好处
- 减少代码侵⼊,解耦
- 可以统⼀处理横切逻辑
- ⽅便添加和删除横切逻辑
核心概念
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-42LQQi8a-1597395358204)(D:\java\SpringBoot\项目\RESOURCES\image-20200812203405280.png)]
-
横切关注点(非核心功能)
-
对哪些⽅法进⾏拦截,拦截后怎么处理,这些就叫横切关注点⽐如 权限认证、⽇志、事物
-
通知 Advice
-
在特定的切⼊点上执⾏的增强处理,有5种通知,后⾯讲解做啥? ⽐如你需要记录⽇志,控制事务 ,提前编写好通⽤的模块,需要的地⽅直接调⽤
-
连接点 JointPoint
-
要⽤通知的地⽅,业务流程在运⾏过程中需要插⼊切⾯的具体位置,⼀般是⽅法的调⽤前后,全部⽅法都可以是连接点
-
只是概念,没啥特殊
-
切⼊点 Pointcut
-
不能全部⽅法都是连接点,通过特定的规则来筛选连接点, 就是Pointcut,选中那⼏个你想要的⽅法
-
在程序中主要体现为书写切⼊点表达式(通过通配、正则表达式)过滤出特定的⼀组JointPoint连接点
-
过滤出相应的 Advice 将要发⽣的joinpoint
-
-
切⾯ Aspect
-
通常是⼀个类,⾥⾯定义切⼊点+通知 , 定义在什么地⽅;什么时间点、做什么事情
-
通知advice指明了时间和做的事情(前置、后置等)
-
切⼊点 pointcut 指定在什么地⽅⼲这个事情
-
web接⼝设计中,web层->⽹关层->服务层->数据层,每⼀层之间也是⼀个切⾯,对象和对象,⽅法和⽅法之间都是⼀个个切⾯
-
-
⽬标 target
- ⽬标类,真正的业务逻辑,可以在⽬标类不知情的条件下,增加新的功能到⽬标类的链路上
-
织⼊ Weaving
- 把切⾯(某个类)应⽤到⽬标函数的过程称为织⼊
-
AOP代理
- AOP框架创建的对象,代理就是⽬标对象的加强
- Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理
Advide的五种类型
通知的注解都加在方法之前
类型 | 说明 |
---|---|
@Before前置通知 | 在执⾏⽬标⽅法之前运⾏ |
@After后置通知 | 在⽬标⽅法运⾏结束之后 |
@AfterReturning返回通知 | 在⽬标⽅法正常返回值后运⾏ |
@AfterThrowing异常通知 | 在⽬标⽅法出现异常后运⾏ |
@Around环绕通知 | 在⽬标⽅法完成前、后做增强处理 ,环绕通知是最重要的通知类型 ,像事务,⽇志等都是环绕通知,注意编程中核⼼是⼀个ProceedingJoinPoint,需要⼿动执⾏ joinPoint.procced() --> 需要手动调用 |
例子
- ⽤户下单
- 核⼼关注点:创建订单
- 横切关注点:记录⽇志、控制事务
- ⽤户观看付费视频
- 核⼼关注点:获取播放地址
- 横切关注点:记录⽇志、权限认证
//⽬标类 VideoOrderService; ⾥⾯每个⽅法都是连接点,;切⼊点是CUD类型的⽅法,R读取的不作为切⼊点
//CRDU全称:增加(Create)、读取查询(Retrieve)、更新(Update)和删除(Delete)
VideoOrderService{
//新增订单
addOrder(){ }
//查询订单
findOrderById(){}
//删除订单
delOrder(){}
//更新订单
updateOrder(){}
}
// ================================================================================================================
//权限切⾯类 = 切⼊点(什么地方)+通知(做什么)
PermissionAspects{
//切⼊点 定义了什么地⽅(过滤)
@Pointcut("execution(public int net.xdclass.sp.service.VideoOrderService.*(..))")
public void pointCut(){}
//before 通知 表示在⽬标⽅法执⾏前切⼊, 并指定在哪个⽅法前切⼊
//做什么事情,怎么做
@Before("pointCut()")
public void permissionCheck(){
System.out.println("在 xxx 之前执⾏权限校验");
}
....
}
// ================================================================================================================
//⽇志切⾯类 = 切⼊点+通知
LogAspect{
//切⼊点 定义了什么地⽅
@Pointcut("execution(public int net.xdclass.sp.service.VideoOrderService.*(..))")
public void pointCut(){}
//after 通知 表示在⽬标⽅法执⾏后切⼊, 并指定在哪个⽅法前切⼊
//什么时候,做什么事情
@After("pointCut()")
public void logStart(){
System.out.println("在 xxx 之后记录⽇志");
}
....
}
切入点表达式
用于过滤连接点成为切入点的表达式
-
切⼊点表示式
-
除了返回类型、⽅法名和参数外,其它项都是可选的 (修饰符基本都是省略不写)
-
访问修饰符 返回值类型(必填) 包和类 ⽅法(必填) execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
-
访问修饰符 返回值类型 包和类 ⽅法(参数) @Pointcut("execution(public int net.xdclass.sp.service.VideoOrderService.*(..))")
-
-
常⻅匹配语法
-
*:匹配单个任何字符 或方法;
-
…:匹配任何数量字符,可以多个,在类型模式中匹配任何数量⼦包;在⽅法参数模式中匹配任何数量参数
-
() 匹配⼀个不接受任何参数的⽅法 (..) 匹配⼀个接受任意数量参数的⽅法 (*) 匹配了⼀个接受⼀个任何类型的参数的⽅法 (*,Integer) 匹配了⼀个接受两个参数的⽅法,其中第⼀个参数是任意类型,第⼆个参数必须是Integer类型
-
常⻅例⼦
-
任意公共⽅法
execution(public * *(..))
-
任何⼀个名字以“save”开始的⽅法
execution(* save*(..))
-
VideoService接⼝定义的任意⽅法(识别)
execution(* net.xdclass.service.VideoService.*(..))
-
在service包中定义的任意⽅法(识别)
execution(* net.xdclass.service.*.*(..))
-
匹配 service 包,⼦孙包下所有类的所有⽅法(识别)
execution(* net.xdclass.service..*.*(..))
代理
基本概念
- 什么是代理
- 为某⼀个对象创建⼀个代理对象,程序不直接⽤原本的对象,⽽是由创建的代理对象来控制对原对象,通过代理类这中间⼀层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间
- A ->B-> C
- 什么是静态代理
- 由程序创建或特定⼯具⾃动⽣成源代码,在程序运⾏前,代理类的.class⽂件就已经存在
- 什么是动态代理
- 在程序运⾏时,运⽤反射机制动态创建⽽成,⽆需⼿动编写代码
- JDK动态代理
- CGLIB动态代理
- 在程序运⾏时,运⽤反射机制动态创建⽽成,⽆需⼿动编写代码
静态代理
- 什么是静态代理
- 由程序创建或特定⼯具⾃动⽣成源代码,在程序运⾏前,代理类的.class⽂件就已经存在通过将⽬标类与代理类实现同⼀个接⼝,让代理类持有真实类对象,然后在代理类⽅法中调⽤真实类⽅法,在调⽤真实类⽅法的前后添加我们所需要的功能扩展代码来达到增强的⽬的
- A -> B -> C
- 优点
- 代理使客户端不需要知道实现类是什么,怎么做的,⽽客户端只需知道代理即可⽅便增加功能,拓展业务逻辑
- 缺点
- 代理类中出现⼤量冗余的代码,⾮常不利于扩展和维护
- 如果接⼝增加⼀个⽅法,除了所有实现类需要实现这个⽅法外,所有代理类也需要实现此⽅法。增加了代码维护的复杂度
public class StaticProxyPayServiceImpl implements PayService {
private PayService payService;
public StaticProxyPayServiceImpl(PayService payService){
this.payService = payService;
}
public String callback(String outTradeNo) {
System.out.println("StaticProxyPayServiceImpl callback begin");
String result = payService.callback(outTradeNo);
System.out.println("StaticProxyPayServiceImpl callback end");
return result;
}
public int save(int userId, int productId) {
System.out.println("StaticProxyPayServiceImpl save begin");
int id = payService.save(userId, productId);
System.out.println("StaticProxyPayServiceImpl save end");
return id;
}
}
动态代理
-
什么是动态代理
- 在程序运⾏时,运⽤反射机制动态创建⽽成,⽆需⼿动编写代码
- JDK动态代理与静态代理⼀样,⽬标类需要实现⼀个代理接⼝,再通过代理对象调⽤⽬标⽅法
//定义⼀个java.lang.reflect.InvocationHandler接⼝的实现类,重写invoke⽅法 //Object proxy:被代理的对象 //Method method:要调⽤的⽅法 //Object[] args:⽅法调⽤时所需要参数 public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
-
即是通过编写一个能够获取代理对象以及目标方法的代理类来增强原有的方法
public class JDKProxy implements InvocationHandler { // 注入被代理对象 private Object targetObject; /** * 绑定代理类与被代理类 * @param targetObject 被代理类对象 * @return 返回代理对象 */ public Object newInstance(Object targetObject) { // 获取被代理对象 this.targetObject = targetObject; /* ClassLoader loader 代理类的类加载器 Class<?>[] interfaces 代理类实现的接口 InvocationHandler 动态代理处理器,实际执行的代理方法 */ // 这里将被代理对象与代理对象绑定 return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this); } /** * 代理方法 * @param proxy 被代理的对象 * @param method 要目标的⽅法 * @param args ⽅法调⽤时所需要参数 * @return 目标方法的返回值 * @throws Throwable */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; // 增加额外的方法 System.out.println("Jdk动态代理打印日志:"+method.getName()); // 执行目标方法 result = method.invoke(targetObject, args); return result; }
-
CGLib动态代理的原理是对指定的业务类⽣成⼀个⼦类,并覆盖其中的业务⽅法来实现代理
CGLib动态代理需要springframework的支持
public class CGLibProxy implements MethodInterceptor { // 注入被代理类 private Object targetObject; // 绑定被代理类与代理类 public Object newInstance(Object targetObject){ // 注入 this.targetObject = targetObject; Enhancer enhancer = new Enhancer(); // 设置代理类的父类(被代理类/目标类) enhancer.setSuperclass(this.targetObject.getClass()); // 设置回调函数 enhancer.setCallback(this); // 创建子类(代理对象) return enhancer.create(); } // 增强方法 public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { Object result = null; // 增强方法 System.out.println("通过CGLib动态代理"+method.getName()); // 调用父类的方法(目标方法) result = methodProxy.invokeSuper(o,args); return result; }
-
动态代理与静态代理相⽐较
最⼤的好处是接⼝中声明的所有⽅法都被转移到调⽤处理器⼀个集中的⽅法中处理,解耦和易维护
-
两种动态代理的区别:
- JDK动态代理:要求⽬标对象实现⼀个接⼝,但是有时候⽬标对象只是⼀个单独的对象,并没有实现任何的接⼝,这个时候就可以⽤CGLib动态代理
- CGLib动态代理,它是在内存中构建⼀个⼦类对象从⽽实现对⽬标对象功能的扩展
- JDK动态代理是⾃带的,CGlib需要引⼊第三⽅包
- CGLib动态代理基于继承来实现代理,所以⽆法对final类、private⽅法和static⽅法实现代理
-
Spring AOP中的代理使⽤的默认策略:
- 如果⽬标对象实现了接⼝,则默认采⽤JDK动态代理
- 如果⽬标对象没有实现接⼝,则采⽤CgLib进⾏动态代理
- 如果⽬标对象实现了接⼝,程序⾥⾯依旧可以指定使⽤CGlib动态代理
AOP实战
-
需求分析:针对Videoservice接⼝实现⽇志打印
-
三个核⼼包
- spring-aop:AOP核⼼功能,例如代理⼯⼚
- aspectjweaver:简单理解,⽀持切⼊点表达式
- aspectjrt:简单理解,⽀持aop相关注解
-
添加schema
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd" xmlns:aop="http://www.springframework.org/schema/aop"
-
定义横切关注点
public class TimeHandler { public void printBefore(){ System.out.println("printBefore 日志 time = " +System.currentTimeMillis()); } public void printAfter(){ System.out.println("printAfter 日志 time = " +System.currentTimeMillis()); }
-
切面织入
<!-- 配置AOP织入--> <!-- 配置bean--> <bean id="timeHandler" class="site.leric.aop.TimeHandler"/> <bean id="videoService" class="site.leric.service.inpl.VideoServiceImpl"/> <!-- 配置AOP--> <aop:config> <!-- 配置切面类(横切关注点)--> <aop:aspect id="timeAspect" ref="timeHandler"> <!-- 配置切入点--> <aop:pointcut id="allMethodLogPointcut" expression="execution(* site.leric.service.inpl.VideoServiceImpl.*(..))"/> <!-- 配置通知--> <aop:before method="printBefore" pointcut-ref="allMethodLogPointcut"/> <aop:after method="printAfter" pointcut-ref="allMethodLogPointcut"/> </aop:aspect> </aop:config>
使用注解配置Spring
- spring的使⽤⽅式有两种 xml配置和注解
- 有些公司只⽤其中⼀种,也有公司xml 配置与注解配置⼀起使⽤
- 注解的优势:配置简单,维护⽅便
- xml的优势:单修改xml时不⽤改源码,不⽤重新编译和部署
- 结论: 看团队开发规范进⾏选择,没有强调⼀定⽤哪个 更多的是xml+注解配合使⽤,⽐如spring整合mybatis
开启配置和包扫描
@Componet(“video”) 指定IOC扫描,并指定别名为video,若不指定别名则自动为类名的小写
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 指定扫描包路径,自动扫描子包
context.scan("site.leric");
// 完成初始化方法,刷新IOC容器
context.refresh();
// 获取bean
VideoService videoService = (VideoService) context.getBean("VideoService");
videoService.findByid(2);
讲解spring的xml和注解对⽐
-
bean定义
-
xml⽅式:
-
<bean id="video" class="site.leric.domain.Video3"> <property name="id" value="5"/> <property name="title" value="autowired"/> </bean>
-
-
注解⽅式:
-
@Component 通⽤组件 细分:
-
@Controller (⽤于web层)
-
@Service (⽤于service层)
-
@Repository (⽤于dao仓库层)
-
@Component("VideoService") public class VideoServiceImpl implements VideoService { public int save(Video video) { System.out.println("保存Video"); return 1; } }
-
-
bean取名
-
xml
<bean id="video">
-
注解
@Component("VideoService")
-
-
bean依赖注入
-
xml利用<property>标签注入
<property name="video" ref="video"/>
-
注解
-
类型注⼊@Autowired
@Autowired private Video video;
-
名称注⼊@Qualifier
@Qualifier("Video") private Video video;
-
-
-
bean⽣命周期
-
xml⽅式:init-method、destroy-method
<bean id="Video3" class="site.leric.domain.Video3" init-method="init" destroy-method="destroy" > <property name="id" value="6"/> <property name="title" value="初始化与销毁"/> </bean>
-
注解⽅式:@PostConstruct初始化、@PreDestroy销毁
// 初始化方法 @PostConstruct public void init(){ System.out.println("Video被初始化 video1"); } // 销毁方法 @PreDestroy public void destroy(){ System.out.println("Video被销毁"); }
-
-
bean作⽤范围
-
xml⽅式:scope属性
<bean id="Video" name="Video" class="site.leric.domain.Video" scope="singleton" >
-
注解⽅式:@scope注解
@Component("Video") @Scope("prototype") // 设置bean作用范围 public class Video { ... }
-
-
管理第三方类的注解
-
在扫描包路径范围内新建一个类作为配置类
-
@Configuration标注在配置类类上,相当于把该类作为spring的xml配置⽂件中的,作⽤为:配置spring容器(应⽤上下⽂)
-
@bean注解:⽤于告诉⽅法产⽣⼀个Bean对象,然后这个Bean对象交给Spring管理,Spring将会将这个Bean对象放在⾃⼰的IOC容器中
-
注意点:SpringIOC容器管理⼀个或者多个bean,这些bean都需要在@Configuration注解下进⾏创建
@Configuration // 配置为IOC容器
public class AppConfig {
// @Bean("VideoOrder") // 在容器中注册bean,并取得别名
@Bean // 若写别名则bean名称为方法名+第一个字母小写
@Scope("pprototype")
@
public VideoOrder videoOrder(){
return new VideoOrder();
}
}
自动映射property文件
- @PropertySource,指定加载配置⽂件
- 配置⽂件映射到实体类
- 使⽤@Value映射到具体的java属性
@Configuration
@PropertySource(value = {"classpath:config.properties"})
public class CustomConfig {
@Value("${server.host}")
private String host;
@Value("${server.port}")
private int port;
获取bean的方法必须集中放在同一个配置类中
AOP的注解配置方式
- 声明切⾯类 @Aspect(切⾯): 通常是⼀个类,⾥⾯可以定义切⼊点和通知
- 配置切⼊点和通知
@Component // 标识扫描
@Aspect // 标识切面类
public class LogAdvice {
// 也可以直接在通知上填写切入点表达式
@Pointcut("execution(* site.leric.service.VideoService.*(..))") // 编写切入点表达式
public void aspect() {
}
// @Before("aspect()") // 标识前置通知 指定使用的切入点
@Before("execution(* site.leric.service.VideoService.*(..))") // 也可以直接填入切入点表达式
public void beforeLog(JoinPoint joinPoint) {
System.out.println("before log");
}
@After("aspect()") // 标识后置通知 指定使用的切入点
public void afterLog(JoinPoint joinPoint) {
System.out.println("after log");
}
-
开启切面注解配置和扫描
- 新建配置类
@Configuration // 标识为配置类 @ComponentScan("site.leric") // 指定扫描路径 @EnableAspectJAutoProxy // 开启spring对aspect的支持 public class AnnocationConfig { // 里面什么都不用写 }
-
main函数注册ioc容器
// 开启AOP的主函数 public class AOPApp { public static void main(String[] args) { // 把配置类的class文件传入配置容器 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnocationConfig.class); VideoService videoService = (VideoService) context.getBean("VideoService"); videoService.findByid(2); }
环绕通知
可以实现前置通知与后置通知的全部功能
@Around("aspect()")
public void around(JoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("环绕通知 环绕前" + start);
// 调用切入点方法
((ProceedingJoinPoint) joinPoint).proceed();
long end = System.currentTimeMillis();
System.out.println("环绕通知 环绕后" + end);
System.out.println("方法前后耗时" + (end - start) + "ms");
}
Spring事务管理
- 事务:多个操作,要么同时成功,要么失败后⼀起回滚
- 具备ACID四种特性
- Atomic(原⼦性)
- Consistency(⼀致性)
- Isolation(隔离性)
- Durability(持久性)
- 具备ACID四种特性
- 两种管理方式
- 编程式事务管理
- 代码中调⽤beginTransaction()、commit()、rollback()等事务管理相关的⽅法,通过TransactionTempalte**⼿动管理事务(**⽤的少)
- 声明式事务管理
- 通过AOP实现,可配置⽂件⽅式或者注解⽅式实现事务的管理控制(⽤的多)
- 本质是对⽅法前后进⾏拦截,底层是建⽴在 AOP 的基础之上
在⽬标⽅法开始之前创建或者加⼊⼀个事务,在执⾏完⽬标⽅法之后根据执⾏情况提交或者回滚事
务
- 编程式事务管理
事务传播行为
配置是否开启新的事物
-
如果在开始当前事务之前,⼀个事务上下⽂已经存在,此时有若⼲选项可以指定⼀个事务性⽅法的执⾏⾏为
-
⭐@Transactional(propagation=Propagation.REQUIRED) 需要事务
如果有事务, 那么加⼊事务, 没有的话新建⼀个(默认情况下)
-
⭐ @Transactional(propagation=Propagation.NOT_SUPPORTED) 不支持事务
不为这个⽅法开启事务
-
⭐ @Transactional(propagation=Propagation.REQUIRES_NEW) 一定开一个新事务,优先执行
不管是否存在事务,都创建⼀个新的事务,原来的挂起,新的执⾏完毕,继续执⾏⽼的事务
-
⭐ @Transactional(propagation=Propagation.MANDATORY)
必须在⼀个已有的事务中执⾏,否则抛出异常
-
@Transactional(propagation=Propagation.NEVER)
必须在⼀个没有的事务中执⾏,否则抛出异常(与Propagation.MANDATORY相反)
-
@Transactional(propagation=Propagation.SUPPORTS) 有就用,没就算了
如果其他bean调⽤这个⽅法,在其他bean中声明事务,那就⽤事务.如果其他bean没有声明事务,那就不⽤事务.
-
@Transactional(propagation=Propagation.NESTED)
如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏;
如果当前没有事务,则该取值等价于Propagation.REQUIRED。
-
事务隔离级别
事务隔离级别: 是指若⼲个并发的事务之间的隔离程度
-
@Transactional(isolation = Isolation.READ_UNCOMMITTED) 读取未提交数据
(会出现脏读,不可重复读) 基本不使⽤
-
@Transactional(isolation = Isolation.READ_COMMITTED) 读取已提交数据
(会出现不可重复读和幻读)
-
@Transactional(isolation = Isolation.REPEATABLE_READ) 可重复读
(会出现幻读)MySql默认
-
@Transactional(isolation = Isolation.SERIALIZABLE) 串⾏化
(最安全)
Spring整合SpringBoot与MyBatis
-
快速创建SpringBoot+Spring+Mybatsi项⽬
- https://start.spring.io/
-
需要加入依赖包
- Spring Web
- MySQL Driver
- MyBatis FrameWork
-
配置文件
spring.datasource.driver-class-name =com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springboot? useUnicode=true&characterEncoding=utf-8&useSSL=false spring.datasource.username=root spring.datasource.password=root #使⽤阿⾥巴巴druid数据源,默认使⽤⾃带的 #spring.datasource.type =com.alibaba.druid.pool.DruidDataSource #开启控制台打印sql mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl server.port= 80
-
配置启动类
@SpringBootApplication @MapperScan("site.lreic.SSMDemo.mapper") // 扫描包mapper,就不用在mapper类上再注解@MapperScan public class SsmDemoApplication { public static void main(String[] args) { SpringApplication.run(SsmDemoApplication.class, args); } }
-
开启事务管理
- 启动类加上 @EnableTransactionManagement
- 业务类Service层加上 @Transactional
- @Transactional注解默认设定 REQUIRED的事务传播(Propagation)
- 自动完成提交以及异常回滚操作
ZABLE) 串⾏化
(最安全)
Spring整合SpringBoot与MyBatis
-
快速创建SpringBoot+Spring+Mybatsi项⽬
- https://start.spring.io/
-
需要加入依赖包
- Spring Web
- MySQL Driver
- MyBatis FrameWork
-
配置文件
spring.datasource.driver-class-name =com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springboot? useUnicode=true&characterEncoding=utf-8&useSSL=false spring.datasource.username=root spring.datasource.password=root #使⽤阿⾥巴巴druid数据源,默认使⽤⾃带的 #spring.datasource.type =com.alibaba.druid.pool.DruidDataSource #开启控制台打印sql mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl server.port= 80
-
配置启动类
@SpringBootApplication @MapperScan("site.lreic.SSMDemo.mapper") // 扫描包mapper,就不用在mapper类上再注解@MapperScan public class SsmDemoApplication { public static void main(String[] args) { SpringApplication.run(SsmDemoApplication.class, args); } }
-
开启事务管理
- 启动类加上 @EnableTransactionManagement
- 业务类Service层加上 @Transactional
- @Transactional注解默认设定 REQUIRED的事务传播(Propagation)
- 自动完成提交以及异常回滚操作
小结:通过MyBatis与数据库进行交互,通过Spring解耦各种Bean,通过SpringBoot的MVC架构处理http请求