Spring Framework5.X
spring框架,使用的好处
-
什么是Spring:轻量级的 DI / IoC 和 AOP 容器的开源框架
-
有啥好处:
-
管理创建和组装对象之间的依赖关系 使用前:手工创建
UserControoler private UserService userService = new UserService();
使用后:Spring创建,自动注入
-
面向切面编程(AOP)可以解耦核心业务和边缘业务的关系
场景:用户调用下单购买视频接口,需要判断登录,拦截器是AOP思想的一种实现
使用前:代码写逻辑,每次下单都调用方法判断,多个方法需要判断登录则都需要 登录方法判断
使用后:根据一定的方法或者路径规则进行判断是否要调用,降低代码耦合度 -
包含java大型项目里面常见解决方案 web层、业务层、数据访问层等
-
极其便利的整合其他主流技术栈,比如redis、mq、mybatis、jpa
-
社区庞大和活跃,在微服务、大数据、云计算都有对应的组件
-
-
为什么要学?(springboot帮我们简化了很多配置)
- 使用springboot2.x后,很少会接触到各种细化的bean配置,但是底层实现流程和原理是必须掌握的。
构建Spring5.X项目
-
创建maven项目
-
添加依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.5.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-core --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.2.5.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-beans --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>5.2.5.RELEASE</version> </dependency>
-
添加配置文件applicationContext.xml
bean标签
id属性:指定Bean的名称,在Bean被别的类依赖时使用
name属性:用于指定Bean的别名,如果没有id,也可以用name
class属性:用于指定Bean的来源,要创建的Bean的class类,需要全限定名
<?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/spring-beans.xsd">
<bean id="video" class="net.xdclass.sp.domain.Video">
<property name="id" value="9"/>
<property name="title" value="Spring 5.X" />
</bean>
</beans>
Video
package net.xdclass.sp.domain;
public class Video {
private int id;
private String title;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
app
package net.xdclass.sp;
import net.xdclass.sp.domain.Video;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String [] args){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Video video = (Video)applicationContext.getBean("video");
System.out.println(video.getTitle());
}
}
IOC
- 什么是IOC Inverse of Control(控制反转)是一种设计思想 将原本在程序中手动创建对象的流程,交由Spring框架来管理 核心:把创建对象的控制权反转给Spring框架,对象的生命周期由Spring统一管理。把spring ioc 当成一个容器,里面存储管理的对象称为Bean,类实例
- 案例实操 配置文件里面定义一个bean,通过代码去获取
<bean name="video" class="net.xdclass.sp.domain.Video">
<property name="id" value="9"/>
<property name="title" value="Spring 5.X课程" />
</bean>
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Video video = (Video)applicationContext.getBean("video");
System.out.println(video.getTitle())
DI
- 什么是DI Dependency Injection ,依赖注入
IOC容器在运行期间,动态地将对象某种依赖关系注入到对象之中,比如视频订单对象,依赖用视频对象 - 案例实操
Video
package net.xdclass.sp.domain;
public class Video {
private int id;
private String title;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@Override
public String toString() {
return "Video{" +
"id=" + id +
", title='" + title + '\'' +
'}';
}
}
VideoOrder
package net.xdclass.sp.domain;
public class VideoOrder {
private int id;
private String outTradeNo;
private Video video;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getOutTradeNo() {
return outTradeNo;
}
public void setOutTradeNo(String outTradeNo) {
this.outTradeNo = outTradeNo;
}
public Video getVideo() {
return video;
}
public void setVideo(Video video) {
this.video = video;
}
@Override
public String toString() {
return "VideoOrder{" +
"id=" + id +
", outTradeNo='" + outTradeNo + '\'' +
", video=" + video +
'}';
}
}
applicationContext.xml
<?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/spring-beans.xsd">
<bean id="video" class="net.xdclass.sp.domain.Video">
<property name="id" value="9"/>
<property name="title" value="Spring 5.X课程" />
</bean>
<bean id="videoOrder" class="net.xdclass.sp.domain.VideoOrder">
<property name="id" value="12"/>
<property name="outTradeNo" value="244242121"/>
<property name="video" ref="video"/>
</bean>
</beans>
App
package net.xdclass.sp;
import net.xdclass.sp.domain.Video;
import net.xdclass.sp.domain.VideoOrder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String [] args){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//Spring 5.X课程
Video video = (Video)applicationContext.getBean("video");
System.out.println(video.getTitle());
VideoOrder videoOrder = (VideoOrder)applicationContext.getBean("videoOrder");
//VideoOrder{id=12, outTradeNo='244242121', video=Video{id=9, title='Spring 5.X课程'}}
System.out.println(videoOrder);
}
}
作用域
scope属性
-
singleton:单例, 默认值,调用getBean方法返回是同一个对象,实例会被缓存起来,效率比较高,当一个bean被标识为singleton时候,spring的IOC容器中只会存在一个该bean
-
prototype: 多例,调用getBean方法创建不同的对象,会频繁的创建和销毁对象造成很大的开销
-
其他少用 (作用域 只在 WebApplicationContext)
- request :每个Http请求都会创建一个新的bean
- session: 每个Http Session请求都会创建一个新的bean
- global session(基本不用)
<!--<bean id="video" class="net.xdclass.sp.domain.Video" scope="singleton"> -->
<bean id="video" class="net.xdclass.sp.domain.Video" scope="prototype">
<property name="id" value="9"/>
<property name="title" value="Spring 5.X课程" />
</bean>
private static void testScope(ApplicationContext context){
Video video1 = (Video)context.getBean("video");
Video video2 = (Video)context.getBean("video");
//靠匹配内存地址,== 是匹配内存地址
System.out.println( video1 == video2 );
}
常见注入方式
- 使用set方法注入
<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 id="video" class="net.xdclass.sp.domain.Video" >
<constructor-arg name="title" value="面试专题课程第一季"></constructor-arg>
</bean>
public Video(String title) {
this.title = title;
}
- POJO类型注入(property 没有使用value属性,而是使用了ref属性,也是通过setting方式注入的)
<bean id="video" class="net.xdclass.sp.domain.Video" >
<constructor-arg name="title" value="面试专题课程第一季"></constructor-arg>
</bean>
<bean id="videoOrder" class="net.xdclass.sp.domain.VideoOrder" >
<property name="id" value="8" />
<property name="outTradeNo" value="23432fnfwedwefqwef2"/>
<property name="video" ref="video"/>
</bean>
- 注意: 类的构造函数重写的时候,一定要保留空构造函数!!!否则setting注入方式会报错!!!
List、Map类型的注入
- 复杂类型注入,添加两个属性
<bean id="video" class="net.xdclass.sp.domain.Video" >
<!--list类型注入-->
<property name="chapterList">
<list>
<value>第一章SpringBoot</value>
<value>第二章Mybatis</value>
<value>第三章Spring</value>
</list>
</property>
<property name="videoMap">
<map>
<entry key="1" value="SpringCloud课程"></entry>
<entry key="2" value="面试课程"></entry>
<entry key="3" value="javaweb课程"></entry>
</map>
</property>
</bean>
public class Video {
private int id;
private String title;
private List<String> chapterList;
private Map<Integer,String> videoMap;
//省略set get方法
}
IOC容器中Bean之间的依赖和继承
- bean继承:两个类之间大多数的属性都相同,避免重复配置,通过bean标签的parent属性重用已有的Bean元素的配置信息,继承指的是配置信息的复用,和Java类的继承没有关系
<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 id="video2" class="net.xdclass.sp.domain.Video2" scope="singleton" parent="video">
<property name="summary" value="这个是summary"></property>
</bean>
- 属性依赖: 如果类A是作为类B的属性, 想要类A比类B先实例化,设置两个Bean的依赖关系
<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的关系,video要先于videoOrder实例化-->
<bean id="videoOrder" class="net.xdclass.sp.domain.VideoOrder" depends-on="video">
<property name="id" value="8" />
<property name="outTradeNo" value="23432fnfwedwefqwef2"/>
<property name="video" ref="video"/>
</bean>
IOC容器中Bean的生命周期init()、destroy()方法
package net.xdclass.sp.domain;
public class Video {
private int id;
private String title;
public Video(String title) {
this.title = title;
}
public void init(){
System.out.println("video 类 init 方法被调用");
}
public void destroy(){
System.out.println("video 类 destroy 方法被调用");
}
public Video(){
System.out.println("video 类 空构造函数被调用");
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@Override
public String toString() {
return "Video{" +
"id=" + id +
", title='" + title + '\'' +
'}';
}
}
<bean id="video" class="net.xdclass.sp.domain.Video" init-method="init" destroy-method="destroy">
<property name="id" value="9"/>
<property name="title" value="Spring 5.X课程" />
</bean>
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//Spring 5.X课程
Video video = (Video)applicationContext.getBean("video");
System.out.println(video.getTitle());
//注册钩子函数才会触发调用destroy()
((ClassPathXmlApplicationContext)applicationContext).registerShutdownHook();
后置处理器 BeanPostProcessor
- 什么是BeanPostProcessor
- 是Spring IOC容器给我们提供的一个扩展接口
- 在调用初始化方法前后对 Bean 进行额外加工,ApplicationContext 会自动扫描实现了BeanPostProcessor的 bean,并注册这些 bean 为后置处理器
- 是Bean的统一前置后置处理而不是基于某一个bean
- 执行顺序
video 空构造函数被调用(Spring IOC容器实例化Bean)
调用BeanPostProcessor的postProcessBeforeInitialization方法
调用bean实例的初始化方法 init()
调用BeanPostProcessor的postProcessAfterInitialization方法
注意:接口重写的两个方法不能返回null,如果返回null那么在后续初始化方法将报空指针异常或者通过getBean()方法获取不到bean实例对象
public class CustomBeanPostProcessor implements BeanPostProcessor,Ordered {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("CustomBeanPostProcessor1 postProcessBeforeInitialization beanName="+beanName);
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("CustomBeanPostProcessor1 postProcessAfterInitialization beanName="+beanName);
return bean;
}
public int getOrder() {
return 1;
}
}
注册自定义的CustomBeanPostProcessor
<bean class="net.xdclass.sp.processor.CustomBeanPostProcessor"/>
可以注册多个BeanPostProcessor顺序
- 在Spring机制中可以指定后置处理器调用顺序,通过BeanPostProcessor接口实现类实现Ordered接口getOrder方法,该方法返回整数,默认值为 0优先级最高,值越大优先级越低
BeanPostProcessor 和 Bean的init()、destroy()区别?
- init、destroy是针对单个Bean。
- BeanPostProcessor 对容器中所有Bean都生效。
bean自动装配Autowire 属性
-
属性注入
- 前面学过属性注入,set方法、构造函数等,属于手工注入
- 有没办法实现自动装配注入?
-
Spring自动注入
- 使用元素的 autowire 属性为一个 bean 定义指定自动装配模式
- autowire设置值
- no:没开启
- byName: 根据bean的id名称,注入到对应的属性里面 , video
- byType:根据bean需要注入的类型,注入到对应的属性里面
- 如果按照类型注入,存在2个以上bean的话会抛异常
- expected single matching bean but found 2
- constructor: 通过构造函数注入,需要这个类型的构造函数 VideoOrder(xx,xx,Video video)
<bean id="video" class="net.xdclass.sp.domain.Video" init-method="init" destroy-method="destroy">
<property name="id" value="9"/>
<property name="title" value="Spring 5.X课程" />
</bean>
<!--<bean id="videoOrder" class="net.xdclass.sp.domain.VideoOrder" autowire="byName">-->
<!--<bean id="videoOrder" class="net.xdclass.sp.domain.VideoOrder" autowire="byType">-->
<bean id="videoOrder" class="net.xdclass.sp.domain.VideoOrder" autowire="constructor">
<property name="id" value="8" />
<property name="outTradeNo" value="23432fnfwedwefqwef2"/>
<!-- <property name="video" ref="video"/> -->
</bean>
AOP
-
什么是AOP
- Aspect Oriented Program 面向切面编程
- 在不改变原有逻辑上增加额外的功能,比如解决系统层面的问题,或者增加新的功能
-
场景
- 权限控制
- 缓存
- 日志处理
- 事务控制
-
AOP思想把功能分两个部分,分离系统中的各种关注点
-
核心关注点
- 业务的主要功能
-
横切关注点
- 非核心、额外增加的功能
-
-
好处
- 减少代码侵入,解耦
- 可以统一处理横切逻辑
- 方便添加和删除横切逻辑
AOP核心概念
-
横切关注点
- 对哪些方法进行拦截,拦截后怎么处理,这些就叫横切关注点
- 比如 权限认证、日志、事务
-
通知 Advice
- 在特定的切入点上执行的增强处理,有5种通知,后面讲解
- 做啥? 比如你需要记录日志,控制事务 ,提前编写好通用的模块,需要的地方直接调用
-
连接点 JointPoint
- 要用通知的地方,业务流程在运行过程中需要插入切面的具体位置,
- 一般是方法的调用前后,全部方法都可以是连接点
- 只是概念,没啥特殊
-
切入点 Pointcut
- 不能全部方法都是连接点,通过特定的规则来筛选连接点, 就是Pointcut,选中那几个你想要的方法
- 在程序中主要体现为书写切入点表达式(通过通配、正则表达式)过滤出特定的一组 JointPoint连接点
- 过滤出相应的 Advice 将要发生的joinpoint地方
-
切面 Aspect
- 通常是一个类,里面定义 **切入点+通知 **, 定义在什么地方; 什么时间点、做什么事情
- 通知 advice指明了时间和做的事情(前置、后置等)
- 切入点 pointcut 指定在什么地方干这个事情
- web接口设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面,对象和对象,方法和方法之间都是一个个切面
-
目标 target
- 目标类,真正的业务逻辑,可以在目标类不知情的条件下,增加新的功能到目标类的链路上
-
织入 Weaving
- 把切面(某个类)应用到目标函数的过程称为织入
-
AOP代理
- AOP框架创建的对象,代理就是目标对象的加强
- Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理
Advice的类型
-
@Before前置通知
- 在执行目标方法之前运行
-
@After后置通知
- 在目标方法运行结束之后
-
@AfterReturning返回通知
- 在目标方法正常返回值后运行
-
@AfterThrowing异常通知
- 在目标方法出现异常后运行
-
@Around环绕通知
- 在目标方法完成前、后做增强处理 ,环绕通知是最重要的通知类型 ,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint,需要手动执行 joinPoint.procced()
AOP面向切面编程流程
//目标类 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
-
优点
- 代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可
- 方便增加功能,拓展业务逻辑
-
缺点
- 代理类中出现大量冗余的代码,非常不利于扩展和维护
- 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度
PayService 接口
package net.xdclass.sp.proxy;
public interface PayService {
/**
* 支付回调
* @param outTradeNo
* @return
*/
String callback(String outTradeNo);
/**
* 下单
* @param userId
* @param productId
* @return
*/
int save(int userId, int productId);
}
PayServiceImpl 目标类
package net.xdclass.sp.proxy;
public class PayServiceImpl implements PayService{
public String callback(String outTradeNo) {
System.out.println("目标类 PayServiceImpl 回调 方法 callback");
return outTradeNo;
}
public int save(int userId, int productId) {
System.out.println("目标类 save 回调 方法 save");
return productId;
}
}
StaticProxyPayServiceImpl 静态代理类
package net.xdclass.sp.proxy;
public class PayServiceImpl implements PayService{
public String callback(String outTradeNo) {
System.out.println("目标类 PayServiceImpl 回调 方法 callback");
return outTradeNo;
}
public int save(int userId, int productId) {
System.out.println("目标类 save 回调 方法 save");
return productId;
}
}
ProxyTest 测试类
package net.xdclass.sp.proxy;
public class ProxyTest {
public static void main(String[] args) {
// PayService payService = new PayServiceImpl();
// payService.callback("asdasd");
PayService payService = new PayServiceImpl();
StaticProxyPayServiceImpl staticProxyPayService = new StaticProxyPayServiceImpl(payService);
staticProxyPayService.callback("asdasd");
staticProxyPayService.save(123, 52522331);
}
}
控制台输出
StaticProxyPayServiceImpl callback begin
目标类 PayServiceImpl 回调 方法 callback
StaticProxyPayServiceImpl callback end
StaticProxyPayServiceImpl save begin
目标类 save 回调 方法 save
StaticProxyPayServiceImpl save end
AOP的实现策略之JDK动态代理
什么是动态代理
- 在程序运行时,运用反射机制动态创建而成,无需手动编写代码
- 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;
//获取代理对象
public Object newProxyInstance(Object targetObject){
this. targetObject = targetObject;
//绑定关系,也就是和具体的哪个实现类关联
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(),this);
}
public Object invoke(Object proxy, Method method, Object[] args) {
Object result = null;
try{
System.out.println("通过JDK动态代理调用 "+method.getName() +", 打印日志 begin");
result = method.invoke(targetObject,args);
System.out.println("通过JDK动态代理调用 "+method.getName() +", 打印日志 end");
}catch (Exception e){
e.printStackTrace();
}
return result;
}
}
PayService 接口
package net.xdclass.sp.proxy;
public interface PayService {
/**
* 支付回调
* @param outTradeNo
* @return
*/
String callback(String outTradeNo);
/**
* 下单
* @param userId
* @param productId
* @return
*/
int save(int userId, int productId);
}
PayServiceImpl 实现类
package net.xdclass.sp.proxy;
public class PayServiceImpl implements PayService{
public String callback(String outTradeNo) {
System.out.println("目标类 PayServiceImpl 回调 方法 callback");
return outTradeNo;
}
public int save(int userId, int productId) {
System.out.println("目标类 save 回调 方法 save");
return productId;
}
}
JdkProxy 动态代理
package net.xdclass.sp.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkProxy implements InvocationHandler {
private Object targetObject;
/**
* 代理类和目标类绑定关系
*/
public Object newProxyInstance(Object targetObject){
this.targetObject = targetObject;
//生成代理对象
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try{
System.out.println("通过JDK动态代理调用" + method.getName() + ",打印日志 begin");
result = method.invoke(targetObject, args);
System.out.println("通过JDK动态代理调用" + method.getName() + ",打印日志 end");
}catch (Exception e){
e.printStackTrace();
}
return result;
}
}
ProxyTest
package net.xdclass.sp.proxy;
public class ProxyTest {
public static void main(String[] args) {
//JDK动态代理
JdkProxy jdkProxy = new JdkProxy();
//获取代理类对象
PayService payService = (PayService)jdkProxy.newProxyInstance(new PayServiceImpl());
//调用真正的方法
payService.callback("asdads");
payService.save(123, 22321);
}
}
控制台输出
通过JDK动态代理调用callback,打印日志 begin
目标类 PayServiceImpl 回调 方法 callback
通过JDK动态代理调用callback,打印日志 end
通过JDK动态代理调用save,打印日志 begin
目标类 save 回调 方法 save
通过JDK动态代理调用save,打印日志 end
AOP的实现策略之CGLib动态代理
什么是动态代理
- 在程序运行时,运用反射机制动态创建而成,无需手动编写代码
- CgLib动态代理的原理是对指定的业务类生成一个子类,并覆盖其中的业务方法来实现代理
CglibProxy
package net.xdclass.sp.proxy;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxy implements MethodInterceptor {
//目标类
private Object targetObject;
//绑定关系
public Object newProxyInstance(Object targetObject){
this.targetObject = targetObject;
//用于绑定目标类和代理类的关系
Enhancer enhancer = new Enhancer();
//设置代理类的父类(目标类)
enhancer.setSuperclass(this.targetObject.getClass());
//设置回调函数
enhancer.setCallback(this);
//创建子类(代理对象)
return enhancer.create();
}
//类似invoke() 进行拦截和增强
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object result = null;
try{
System.out.println("通过CGLIB动态代理调用 "+method.getName() +", 打印日志 begin");
result = methodProxy.invokeSuper(o,args);
System.out.println("通过CGLIB动态代理调用 "+method.getName() +", 打印日志 begin");
}catch (Exception e){
e.printStackTrace();
}
return result;
}
}
ProxyTest 测试类
package net.xdclass.sp.proxy;
public class ProxyTest {
public static void main(String[] args) {
//CGlib动态代理
CglibProxy cglibProxy = new CglibProxy();
PayService payService = (PayService)cglibProxy.newProxyInstance(new PayServiceImpl());
payService.callback("123123");
payService.save(123,1232313);
}
}
控制台输出
通过CGLIB动态代理调用 callback, 打印日志 begin
目标类 PayServiceImpl 回调 方法 callback
通过CGLIB动态代理调用 callback, 打印日志 begin
通过CGLIB动态代理调用 save, 打印日志 begin
目标类 save 回调 方法 save
通过CGLIB动态代理调用 save, 打印日志 begin
CGLib动态代理和JDK动态代理总结
-
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理,解耦和易维护
-
两种动态代理的区别:
- JDK动态代理:要求目标对象实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以用CGLib动态代理
- CGLib动态代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展
- JDK动态代理是自带的,CGlib需要引入第三方包
- CGLib动态代理基于继承来实现代理,所以无法对final类、private方法和static方法实现代理
-
Spring AOP中的代理使用的默认策略:
- 如果目标对象实现了接口,则默认采用JDK动态代理
- 如果目标对象没有实现接口,则采用CgLib进行动态代理
- 如果目标对象实现了接扣,程序里面依旧可以指定使用CGlib动态代理
面向切面编程Spring AOP示例
需求分析:针对VideoService接口实现日志打印。
三个核心包:
- spring-aop:AOP核心功能,例如代理工厂
- aspectjweaver:简单理解,支持切入点表达式
- aspectjrt:简单理解,支持aop相关注解
引入相关包
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.11</version>
</dependency>
定义service接口和实现类
VideoService
package net.xdclass.sp.service;
import net.xdclass.sp.domain.Video;
public interface VideoService {
int save(Video video);
Video findById(int id);
}
VideoServiceImpl
package net.xdclass.sp.service;
import net.xdclass.sp.domain.Video;
public class VideoServiceImpl implements VideoService{
public int save(Video video) {
System.out.println("保存video");
return 1;
}
public Video findById(int id) {
System.out.println("根据id找视频");
return new Video();
}
}
定义横切关注点 TimeHandler
package net.xdclass.sp.aop;
import java.time.LocalDateTime;
//横切关注点
public class TimeHandler {
public void printBefore(){
System.out.println("printBefore 日志 time = " + LocalDateTime.now().toString());
}
public void printAfter(){
System.out.println("printAfter 日志 time = " + LocalDateTime.now().toString());
}
}
引入schema,配置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/spring-beans.xsd
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">
<bean id="timeHandler" class="net.xdclass.sp.aop.TimeHandler"/>
<bean id="videoService" class="net.xdclass.sp.service.VideoServiceImpl"/>
<!--aop配置-->
<aop:config>
<!--横切关注点-->
<aop:aspect id="timeAspect" ref="timeHandler">
<!--定义切入点表达式-->
<!--<aop:pointcut id="allMethodLogPointCut" expression="execution(* net.xdclass.sp.service.VideoService.sav*(..))"/>-->
<aop:pointcut id="allMethodLogPointCut" expression="execution(* net.xdclass.sp.service.VideoService.*(..))"/>
<!--配置前置通知和后置通知-->
<aop:before method="printBefore" pointcut-ref="allMethodLogPointCut"/>
<aop:after method="printAfter" pointcut-ref="allMethodLogPointCut"/>
</aop:aspect>
</aop:config>
</beans>
测试类
package net.xdclass.sp;
import net.xdclass.sp.domain.Video;
import net.xdclass.sp.domain.VideoOrder;
import net.xdclass.sp.service.VideoService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String [] args){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
VideoService videoService = (VideoService)applicationContext.getBean("videoService");
videoService.save(new Video());
videoService.findById(22);
}
}
控制台输出
printBefore 日志 time = 2020-11-12T20:48:26.529
保存video
printAfter 日志 time = 2020-11-12T20:48:26.529
printBefore 日志 time = 2020-11-12T20:48:26.530
根据id找视频
printAfter 日志 time = 2020-11-12T20:48:26.530
Xml配置转换到注解配置
-
spring的使用方式有两种 xml配置和注解
- 有些公司只用其中一种,也有公司xml 配置与注解配置一起使用
-
注解的优势:配置简单,维护方便
-
xml的优势:单修改xml时不用改源码,不用重新编译和部署
-
结论: 看团队开发规范进行选择,没有强调一定用哪个 更多的是xml+注解配合使用,比如spring整合mybatis
注解配置项目
- 开启注解配置和包扫描
public static void main(String [] args){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//扫描指定的包,包括子包
context.scan("net.xdclass");
//里面完成初始化操作,核心方法
context.refresh();
VideoService videoService = (VideoService) context.getBean("videoService");
videoService.findById(2);
Video video = (Video) context.getBean("video");
}
VideoServiceImpl
package net.xdclass.sp.service;
import net.xdclass.sp.domain.Video;
import org.springframework.stereotype.Component;
@Component("videoService")
public class VideoServiceImpl implements VideoService{
public int save(Video video) {
System.out.println("保存video");
return 1;
}
public Video findById(int id) {
System.out.println("根据id找视频");
return new Video();
}
}
xml和注解对比
常用注解
-
bean定义
- xml方式:
- 注解方式:@Component 通用组件 细分:
- @Controller (用于web层)
- @Service (用于service层)
- @Repository (用于dao仓库层)
-
bean取名
- xml方式:通过id或者name
- 注解方式:@Component("XXXX")
-
bean注入
- xml方式:通过 bean标签下的property
- 注解方式:类型注入@Autowired 名称注入@Qualifier
-
bean生命周期
- xml方式:init-method、destroy-method
- 注解方式:@PostConstruct初始化、@PreDestroy销毁 :直接加在方法上即可
-
bean作用范围
- xml方式:scope属性
- 注解方式:@scope("prototype")
spring的@Configuration和@Bean注解定义第三方bean
- @Configuration标注在类上,相当于把该类作为spring的xml配置文件中的beans,作用为:配置spring容器(应用上下文)
- @bean注解:用于告诉方法产生一个Bean对象,然后这个Bean对象交给Spring管理,Spring将会将这个Bean对象放在自己的IOC容器中
- 注意点:SpringIOC容器管理一个或者多个bean,这些bean都需要在@Configuration注解下进行创建
@Configuration
public class AppConfig {
//使用@bean注解,表明这个bean交个spring 进行管理
// 如果没有指定名称,默认采用 方法名 + 第一个字母小写 作为bean的名称
@Bean(name = "videoOrderName",initMethod = "init",destroyMethod = "destroy")
@Scope
public VideoOrder videoOrder(){
return new VideoOrder();
}
}
PropertySource配置文件映射Bean属性
- @PropertySource,指定加载配置文件
- 配置文件映射到实体类
- 使用@Value映射到具体的java属性
@Configuration
@PropertySource(value = {"classpath:config.properties"})
public class CustomConfig {
@Value("${server.host}")
private String host;
@Value("${server.port}")
private int port;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}
@Autowired
private CustomConfig customConfig;
psvm(){
System.out.println(customConfig.getHost());
}
基于注解的AOP示例
- 声明切面类 @Aspect(切面): 通常是一个类,里面可以定义切入点和通知
- 配置切入点和通知
package net.xdclass.sp.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
//告诉spring,这个一个切面类,里面可以定义切入点和通知
@Aspect
public class LogAdvice {
//切入点表达式
@Pointcut("execution(* net.xdclass.sp.service.VideoServiceImpl.*(..))")
public void aspect(){
}
//前置通知
@Before("aspect()")
public void beforeLog(JoinPoint joinPoint){
System.out.println("LogAdvice beforeLog");
}
//后置通知
@After("aspect()")
public void afterLog(JoinPoint joinPoint){
System.out.println("LogAdvice afterLog");
}
}
- 开启SpringAOP注解配置
package net.xdclass.sp.aop;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("net.xdclass")
@EnableAspectJAutoProxy //开启了spring对aspect的支持
public class AnnotationAppConfig {
}
启动类加载AnnotationAppConfig配置类
public class App {
public static void main(String [] args){
//加载配置类AnnotationAppConfig
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationAppConfig.class);
VideoService videoService = (VideoService) context.getBean("videoService");
videoService.findById(2);
}
}
控制台输出
LogAdvice beforeLog
根据id找视频
LogAdvice afterLog
环绕通知统计接口耗时
LogAdvice
package net.xdclass.sp.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
//告诉spring,这个一个切面类,里面可以定义切入点和通知
@Aspect
public class LogAdvice {
//切入点表达式
@Pointcut("execution(* net.xdclass.sp.service.VideoServiceImpl.*(..))")
public void aspect(){
}
//前置通知
@Before("aspect()")
public void beforeLog(JoinPoint joinPoint){
System.out.println("LogAdvice beforeLog");
}
//后置通知
@After("aspect()")
public void afterLog(JoinPoint joinPoint){
System.out.println("LogAdvice afterLog");
}
/**
* 环绕通知
* @param joinPoint
*/
@Around("aspect()")
public void around(JoinPoint joinPoint){
Object target = joinPoint.getTarget().getClass().getName();
System.out.println("调用者="+target);
//目标方法签名
System.out.println("调用方法="+joinPoint.getSignature());
//通过joinPoint获取参数
Object [] args = joinPoint.getArgs();
System.out.println("参数="+args[0]);
long start = System.currentTimeMillis();
System.out.println("环绕通知 环绕前=========");
//执行连接点的方法
try {
((ProceedingJoinPoint)joinPoint).proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("环绕通知 环绕后=========");
System.out.println("调用方法总耗时 time = " + (end - start) +" ms");
}
}
App
package net.xdclass.sp;
import net.xdclass.sp.aop.AnnotationAppConfig;
import net.xdclass.sp.domain.Video;
import net.xdclass.sp.domain.VideoOrder;
import net.xdclass.sp.service.VideoService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String [] args){
//加载配置类AnnotationAppConfig
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationAppConfig.class);
VideoService videoService = (VideoService) context.getBean("videoService");
videoService.findById(2);
}
}
package net.xdclass.sp;
import net.xdclass.sp.aop.AnnotationAppConfig;
import net.xdclass.sp.domain.Video;
import net.xdclass.sp.domain.VideoOrder;
import net.xdclass.sp.service.VideoService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String [] args){
//加载配置类AnnotationAppConfig
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationAppConfig.class);
VideoService videoService = (VideoService) context.getBean("videoService");
videoService.findById(2);
}
}
控制台输出
调用者=net.xdclass.sp.service.VideoServiceImpl
调用方法=Video net.xdclass.sp.service.VideoService.findById(int)
参数=2
环绕通知 环绕前=========
LogAdvice beforeLog
根据id找视频
环绕通知 环绕后=========
调用方法总耗时 time = 2000 ms
LogAdvice afterLog
Spring常见事务管理
-
事务:多个操作,要么同时成功,要么失败后一起回滚
- 具备ACID四种特性
- Atomic(原子性)
- Consistency(一致性)
- Isolation(隔离性)
- Durability(持久性)
- 具备ACID四种特性
-
你知道常见的Spring事务管理方式吗
-
编程式事务管理:
* 代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法,通过TransactionTempalte手动管理事务(用的少)
- 声明式事务管理:
* 通过AOP实现,可配置文件方式或者注解方式实现事务的管理控制(用的多)
声明式事务管理本质:本质是对方法前后进行拦截,底层是建立在 AOP 的基础之上在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务
Spring事务的传播属性和隔离级别
-
事物传播行为介绍:
- 如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为
- @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) 可重复读(会出现幻读)
- @Transactional(isolation = Isolation.SERIALIZABLE) 串行化
-
MYSQL: 默认为REPEATABLE_READ级别
NEW SSM @Transactional事务控制
多表操作,通过@Transactional控制事务
- 启动类加注解 @EnableTransactionManagement
- 业务类 加 @Transactional