文章目录
什么是Spring框架?
什么是Spring?
- 轻量级的DI/IOC 和AOP容器的开源框架
作用
-
管理创建和组装对象之间的依赖关系 使⽤前:⼿⼯创建
Controller -> Service -> Dao UserControoler private UserService userService = new UserService();
使⽤后:Spring创建,⾃动注⼊
-
⾯向切⾯编程(AOP)可以解耦核⼼业务和边缘业务的关系
-
场景:⽤户调⽤下单购买视频接⼝,需要判断登录,拦截器是AOP思想的⼀种实现
使⽤前:代码写逻辑,每次下单都调⽤⽅法判断,多个⽅法需要判断登录则都需要 登录⽅法
判断
使⽤后:根据⼀定的⽅法或者路径规则进⾏判断是否要调⽤,降低代码耦合度
-
包含java⼤型项⽬⾥⾯常⻅解决⽅案 web层、业务层、数据访问层等
-
极其便利的整合其他主流技术栈,⽐如redis、mq、mybatis、jpa
-
社区庞⼤和活跃,在微服务、⼤数据、云计算都有对应的组件
-
如何在快速添加Maven依赖?
1.直接百度maven 跳入maven仓库 地址:访问外网,可能需要特殊工具,如:你懂的
https://mvnrepository.com/
2.在搜索栏输入:spring
3.spring 必备的三个依赖:spring-context \ spring-core \ spring-beans
将图中的依赖复制到自己项目的pom.xml文件中
SpringFramework 5.x核心之IOC容器
什么是IOC?
- Inverse of Control(控制反转)是⼀种设计思想 将原本在程序中⼿动创建对象的流程,交由Spring框架来管理 核⼼:把创建对象的控制权反转给Spring框架,对象的⽣命周期由
Spring统⼀管理
案例
applicationContext.xml
<bean id="video" class="net.maoni.domain.Video">
<property name="id" value="9"/>
<property name="title" value="Spring 5.x"/>
</bean>
bean标签 id属性:指定Bean的名称,在Bean被别的类依赖时使⽤
name属性:⽤于指定Bean的别名,如果没有id,也可以⽤name
class属性:⽤于指定Bean的来源,要创建的Bean的class类,需要全限定名
实现类:
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Video video = (Video)applicationContext.getBean("video");
System.out.println(video.getTitle());
SpringFramework 5.x核心之DI依赖注入
什么是DI?
Dependency Injection ,依赖注⼊
IOC容器在运⾏期间,动态地将对象某种依赖关系注⼊到对象之中,⽐如视频订单对象,依赖⽤视频对象
案例
applicationContext.xml
<!-- videoOrder对象依赖video对象,通过下述ref标签动态地将依赖关系注入到对象中-->
<bean id="videoOrder" class="net.maoni.domain.VideoOrder">
<property name="id" value="8"/>
<property name="outTradeNo" value="Spring 5.x"/>
<property name="video" ref="video"/>
</bean>
通过下述ref标签动态地将依赖关系注入到对象中
测试类
VideoOrder videoOrder =(VideoOrder) context.getBean("videoOrder");
System.out.println(videoOrder.getVideo());
System.out.println(videoOrder.getOutTradeNo());
测试结果
Spring5.x bean的作用域和注入
Spring 5.x bean的scope作用域讲解
spring的bean属性scope作⽤域
scope属性
-
singleton:单例, 默认值,调⽤getBean⽅法返回是同⼀个对象,实例会被缓存起来,效率⽐
较⾼ 当⼀个bean被标识为singleton时候,spring的IOC容器中只会存在⼀个该bean
-
prototype:多例,调⽤getBean⽅法创建不同的对象,会频繁的创建和销毁对象造成很⼤的
开销
-
其他少⽤ (作⽤域 只在 WebApplicationContext)
-
request :每个Http请求都会创建⼀个新的bean
-
session: 每个Http Session请求都会创建⼀个新的bean
-
global session(基本不⽤)
-
singleton案例
applicationContext.xml
<bean id="video" class="net.maoni.domain.Video" scope="singleton">
<property name="id" value="9"/>
<property name="title" value="Spring 5.x"/>
</bean>
测试:
private static void TestSingleton(ApplicationContext context){
Video video1 = (Video) context.getBean("video");
Video video2 = (Video) context.getBean("video");
System.out.println(video1==video2);
}
结果显示为:True
证明在俩次创建对象后使用的是同一个对象
prototype案例
applicationContext.xml
<bean id="video" class="net.maoni.domain.Video" scope="prototype">
<property name="id" value="9"/>
<property name="title" value="Spring 5.x"/>
</bean>
测试:
private static void TestSingleton(ApplicationContext context){
Video video1 = (Video) context.getBean("video");
Video video2 = (Video) context.getBean("video");
System.out.println(video1==video2);
}
结果显示为:false
证明在俩次创建对象后使用的不是同一个对象
Spring 5.x 常见的注入方式
使用set方法注入
applicationContext.xml
<bean id="video" class="net.maoni.domain.Video" scope="singleton">
<property name="id" value="9"/>
<property name="title" value="Spring 5.x"/>
</bean>
在video的方法里实现封装属性的set方法就可以了,本人自己使用了lombok里的@Data注解
使用带参的构造函数注入
applicationContext.xml
<bean id="video" class="net.maoni.domain.Video">
<constructor-arg name="title" value="spring 5.x"></constructor-arg>
</bean>
封装类
多了一个video()的含参构造
@Data
public class Video {
private Integer id;
private String title;
public Video(String title){
System.out.println("Video 带参构造函数可能会被调用");
this.title=title;
}
}
测试实现类
/**
* 注入测试
* @param context
*/
private static void TestInject(ApplicationContext context){
Video video = (Video) context.getBean("video");
System.out.println(video.getTitle());
}
结果显示:
特别容易出现的问题
在使用了带参注入的方式,就不能保留set方法注入,特别容易出现的一种情况就是在video封装类保留了带参构造方法,这样会造成报错.
List-Map 类型的注入
复杂类型注入
封装类
省略了get和set的方法
private List<String> chapterList; //list
private Map<Integer,String> videoMap; //map
applicationContext.xml
<bean id="video" class="net.maoni.domain.Video">
<!-- list类型注入-->
<property name="chapterList">
<list>
<value>第一章Springboot</value>
<value>第二章Mybatis</value>
<value>第三章Spring</value>
</list>
</property>
<!--map类型注入-->
<property name="videoMap">
<map>
<entry key="1" value="SpringCloud"></entry>
<entry key="2" value="面试课程"></entry>
<entry key="3" value="JavaWeb课程"></entry>
</map>
</property>
</bean>
测试实现类
/**
* list和map方式注入测试
* @param context
*/
private static void TestInjectCollection(ApplicationContext context){
Video video = (Video) context.getBean("video");
System.out.println(video.getChapterList().toString());
System.out.println(video.getVideoMap().values().toString());
}
结果:
Spring IOC 容器Bean之间的依赖和继承
bean继承–parent
- 两个类之间⼤多数的属性都相同,避免重复配置,通过bean标签的parent属性重⽤已有的Bean元素的配置信息 继承指的是配置信息的复⽤,和Java类的继承没有关系
Video封装类
private Integer id;
private String title;
Video2封装类
private Integer id;
private String title;
private String summary;
俩个封装类之间出现重复的属性的时候,可以使用parent标签,使俩者之间 video2继承video,避免重复配置
applicationContext.xml
<!--set注入-->
<bean id="video" class="net.maoni.domain.Video" scope="singleton">
<property name="id" value="9"/>
<property name="title" value="Spring 5.x"/>
</bean>
<bean id="video2" class="net.maoni.domain.Video2" scope="singleton" parent="video">
<property name="summary" value="这是一个summary" />
</bean>
测试类
/**
* 测试bean继承 将需要继承类的属性全部拿过来
* @param context
*/
private static void TestExtend(ApplicationContext context){
Video2 video2 =(Video2) context.getBean("video2");
System.out.println(video2.getId());
System.out.println(video2.getSummary());
System.out.println(video2.getTitle());
}
结果
属性依赖–depends-on
- 如果类A是作为类B的属性, 想要类A⽐类B先实例化,设置两个Bean的依赖关系
VideoOrder封装类
@Data
public class VideoOrder {
private Integer id;
private String outTradeNo; //订单号
private Video video;
}
video封装类在上图(自己找)
…
applicationContext.xml
<!--设置两个bean的关系,video要先于videoOrder实例化-->
<bean id="videoOrder" class="net.maoni.domain.VideoOrder" depends-on="video">
<property name="id" value="8"/>
<property name="outTradeNo" value="Spring 5.x"/>
<property name="video" ref="video"/>
</bean>
测试类
private static void TestInject(ApplicationContext context){
Video video = (Video) context.getBean("video");
System.out.println(video.getTitle());
VideoOrder videoOrder = (VideoOrder) context.getBean("videoOrder");
System.out.println(videoOrder.getVideo().getTitle());
}
结果
Spring 5.x bean 的生命周期和二次处理
spring ioc容器Bean的⽣命周期的init和destroy⽅法
封装类
@Data
public class Video {
private Integer id;
private String title;
public void init(){
System.out.println("video 类 init 方法被调用");
}
public void destroy(){
System.out.println("video 类 destroy 方法被调用");
}
public Video(){
System.out.println("video 无参构造函数被调用");
}
}
applicationContext.xml
init-method 标签 和 destroy-method 标签
<bean id="video" class="net.maoni.domain.Video" scope="singleton" init-method="init" destroy-method="destroy">
<property name="id" value="9"/>
<property name="title" value="Spring 5.x"/>
</bean>
测试类
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
TestInitDestroy(context);
((ClassPathXmlApplicationContext) context).registerShutdownHook();
}
private static void TestInitDestroy(ApplicationContext context){
Video video =(Video) context.getBean("video");
System.out.println(video.getTitle());
}
运算结果显示
Bean的二次加工-Spring5.x后置处理器 BeanPostProcessor
1.什么是BeanPostProcessor?
-
是Spring IOC容器给我们提供的⼀个扩展接⼝
-
在调⽤初始化⽅法前后对 Bean 进⾏额外加⼯,ApplicationContext 会⾃动扫描实现了BeanPostProcessor的 bean,并注册这些 bean 为后置处理器
-
是Bean的统⼀前置后置处理⽽不是基于某⼀个bean
2.执行顺序
Spring IOC容器实例化Bean
调⽤BeanPostProcessor的postProcessBeforeInitialization⽅法
调⽤bean实例的初始化⽅法
调⽤BeanPostProcessor的postProcessAfterInitialization⽅法
3.注意
接⼝重写的两个⽅法不能返回null,如果返回null那么在后续初始化⽅法将报空指针异常或
者通过getBean()⽅法获取不到bean实例对象
4.注册多个BeanPostProcessor
当注册多个时,执行顺序按照getOrder方法中的返回值来决定
在Spring机制中可以指定后置处理器调⽤顺序,通过BeanPostProcessor接⼝实现类实现Ordered接⼝getOrder⽅法,该⽅法返回整数,默认值为 0优先级最⾼,值越⼤优先级越低
Spring5.X bean自动装配Autowire 属性
Spring的Bean的自动装配属性autowire(俩种方式)
属性注入
- set⽅法、构造函数等,属于⼿⼯注⼊
- 有没办法实现⾃动装配注⼊?
Spring自动注入
-
使⽤元素的 autowire 属性为⼀个 bean 定义指定⾃动装配模式
-
autowire设置值
-
no:没开启(默认状态)
-
byName: 根据bean的id名称,注⼊到对应的属性⾥⾯
<bean id="videoOrder" class="net.maoni.domain.VideoOrder" autowire="byName"> <property name="id" value="8"/> <property name="outTradeNo" value="sadsadqdqqw"/> </bean>
-
byType:根据bean需要注⼊的类型,注⼊到对应的属性⾥⾯
-
如果按照类型注⼊,存在2个以上bean的话会抛异常 error: expected single matching bean but found 2
<bean id="videoOrder" class="net.maoni.domain.VideoOrder" autowire="byType"> <property name="id" value="8"/> <property name="outTradeNo" value="sadsadqdqqw"/> </bean>
-
-
constructor: 通过构造函数注⼊,需要这个类型的构造函数
<bean id="videoOrder" class="net.maoni.domain.VideoOrder" autowire="constructor"> <property name="id" value="8"/> <property name="outTradeNo" value="sadsadqdqqw"/> </bean>
封装类
@Data public class VideoOrder { private Integer id; private String outTradeNo; //订单号 private Video video; public VideoOrder(){ System.out.println("VideoOrder无参构造函数被调用"); } public VideoOrder(Video video){ this.video =video; } }
-
Spring5.X ⾯向切⾯编程 AOP
什么是AOP面向切面编程
什么是AOP
- Aspect Oriented Program ⾯向切⾯编程
- 在不改变原有逻辑上增加额外的功能,⽐如解决系统层⾯的问题,或者增加新的功能
场景
- 权限控制
- 缓存
- 日志处理
- 事务控制
AOP思想把功能分2个部分,分离系统中的各种关注点
- 核⼼关注点
- 业务的主要功能
- 横切关注点
- ⾮核⼼、额外增加的功能
好处
-
减少代码侵⼊,解耦
-
可以统⼀处理横切逻辑
-
⽅便添加和删除横切逻辑
AOP面向切面编程相关核心概念
横切、通知、连接点、切⼊点、切⾯???
- 横切关注点
- 对哪些⽅法进⾏拦截,拦截后怎么处理,这些就叫横切关注点
- ⽐如 权限认证、⽇志、事物
- 通知 Advice
- 在特定的切⼊点上执⾏的增强处理,有5种通知,后⾯讲解
- 做啥? ⽐如你需要记录⽇志,控制事务 ,提前编写好通⽤的模块,需要的地⽅直接调⽤
- 连接点 JointPoint
- 要⽤通知的地⽅,业务流程在运⾏过程中需要插⼊切⾯的具体位置
- ⼀般是⽅法的调⽤前后,全部⽅法都可以是连接点
- 只是概念,没啥特殊
- 切⼊点 Pointcut
- 不能全部⽅法都是连接点,通过特定的规则来筛选连接点, 就是Pointcut,选中那⼏个你想要的⽅法
- 在程序中主要体现为书写切⼊点表达式(通过通配、正则表达式)过滤出特定的⼀组JointPoint连接点
- 过滤出相应的 Advice 将要发⽣的joinpoint地⽅
- 切⾯ Aspect
- 通常是⼀个类,⾥⾯定义 切⼊点和通知 , 定义在什么地⽅; 什么时间点、做什么事情
- 通知advice指明了时间和做的事情(前置、后置等)
- 切⼊点pointcut 指定在什么地⽅⼲这个事情
- web接⼝设计中,web层->⽹关层->服务层->数据层,每⼀层之间也是⼀个切⾯,对象和对象,⽅法和⽅法之间都是⼀个个切⾯
- ⽬标 target
- ⽬标类,真正的业务逻辑,可以在⽬标类不知情的条件下,增加新的功能到⽬标类的链路上
- 织⼊ Weaving
- 把切⾯(某个类)应⽤到⽬标函数的过程称为织⼊
- AOP代理
- AOP框架创建的对象,代理就是⽬标对象的加强
- Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理
AOP里面的通知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 之后记录⽇志"); } .... }
Spring5.X AOP切入点表达式讲解
-
切入点表达式
-
除了返回类型、⽅法名和参数外,其它项都是可选的 (修饰符基本都是省略不写)
访问修饰符 返回值类型 包和类 方法(必填) 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..*.*(..))
-
简述 Spring AOP里面的代理
简述静态代理和动态代理
什么是代理
-
为某⼀个对象创建⼀个代理对象,程序不直接⽤原本的对象,⽽是由创建的代理对象来控制对原对象,通过代理类这中间⼀层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间
-
A ->B-> C
什么是静态代理
- 由程序创建或特定⼯具⾃动⽣成源代码,在程序运⾏前,代理类的.class⽂件就已经存在
什么是动态代理
- 在程序运⾏时,运⽤反射机制动态创建⽽成,⽆需⼿动编写代码
- JDK动态代理
- CGLIB动态代理
静态代理讲解
什么是静态代理
- 由程序创建或特定⼯具⾃动⽣成源代码,在程序运⾏前,代理类的.class⽂件就已经存在
- 通过将⽬标类与代理类实现同⼀个接⼝,让代理类持有真实类对象,然后在代理类⽅法中调⽤真实类⽅法,在调⽤真实类⽅法的前后添加我们所需要的功能扩展代码来达到增强的⽬的
- A -> B -> C
优点
- 代理使客户端不需要知道实现类是什么,怎么做的,⽽客户端A只需知道代理即可
- ⽅便增加功能,拓展业务逻辑
缺点
- 代理类中出现⼤量冗余的代码,非常不利于扩展和维护
- 如果接⼝增加⼀个⽅法,除了所有实现类需要实现这个⽅法外,所有代理类也需要实现此⽅法。增加了代码维护的复杂度
测试
目的:在调用接口中的方法同时可以调用其他的方法,列如打印日志,获取运行代码的时间(在不修改原实现类中的方法)
接口:
public interface PayService {
/**
* 支付回调
* @param outTradeNO
* @return
*/
String callback(String outTradeNO);
/**
* 下单
* @param userId
* @param produceId
* @return
*/
int save(int userId,int produceId);
}
实现类Impl
public class PayServiceImpl implements PayService {
@Override
public String callback(String outTradeNO) {
System.out.println("PayServiceImpl 回调方法 callback");
return outTradeNO;
}
@Override
public int save(int userId, int produceId) {
System.out.println("PayServiceImpl 回调方法 save");
return produceId;
}
}
静态代理类
/**
* 测试静态代理
*/
public class StaticProxyPayServiceImpl implements PayService{
private PayService payService;
public StaticProxyPayServiceImpl (PayService payService){
this.payService = payService;
}
@Override
public String callback(String outTradeNO) {
System.out.println("StaticProxyPayServiceImpl callback begin");
String result = payService.callback(outTradeNO);
System.out.println("StaticProxyPayServiceImpl callback end");
return result;
}
@Override
public int save(int userId, int produceId) {
System.out.println("StaticProxyPayServiceImpl callback begin");
int state =payService.save(userId,produceId);
System.out.println("StaticProxyPayServiceImpl callback end");
return state;
}
}
测试类
创建静态代理类的对象,传入实现类的对象参数,在实现save方法时会执行静态代理类中的save方法在这个save方法里又去执行了实现类里的save方法
public class PayTest {
public static void main(String[] args) {
// PayService payService =new PayServiceImpl();
// payService.callback("abc");
PayService payService =new StaticProxyPayServiceImpl(new PayServiceImpl());
payService.save(232,2322);
}
}
运行的结果
StaticProxyPayServiceImpl callback begin
PayServiceImpl 回调方法 save
StaticProxyPayServiceImpl callback end
动态代理—JDK动态代理
什么是动态代理
- 在程序运⾏时,运⽤反射机制动态创建⽽成,⽆需⼿动编写代码
- JDK动态代理与静态代理⼀样,⽬标类需要实现⼀个代理接⼝,再通过代理对象调⽤⽬标⽅法
实操
接口
同 上面静态代理里的接口 一样
实现类
同 上面静态代理里的接口 一样
JDK动态代理类
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);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try{
System.out.println("通过JDK动态代理"+method.getName()+"打印log日志");
result = method.invoke(targetobject,args);
}catch (Exception e){
e.printStackTrace();
}
return result;
}
}
测试实现类
/**
* 测试JDK动态代理实现
*/
JDKProxy jdkProxy =new JDKProxy();
//获取代理类的对象
PayService payServiceProxy = (PayService) jdkProxy.newProxyInstance(new PayServiceImpl());
//调用目标方法
payServiceProxy.callback("sadasda");
结果
通过JDK动态代理callback打印log日志
PayServiceImpl 回调方法 callback
动态代理—CGLib动态代理
什么是动态代理?
- 在程序运行时,运用反射机制动态创建而成,无需手动编写代码
- CgLib动态代理的原理是对指定的业务生成一个子类,并覆盖其中的业务方法来实现代理
实操
接口
同 上面静态代理里的接口 一样
实现类
同 上面静态代理里的接口 一样
CgLib动态代理类
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();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object result = null;
try{
System.out.println("通过CGLIB动态代理调用"+method.getName()+"打印log日志");
methodProxy.invokeSuper(o,objects);
}catch (Exception e){
e.printStackTrace();
}
return result;
}
}
测试类
/**
* 测试CGlib动态代理实现
*/
CglibProxy cglibProxy =new CglibProxy();
PayService payService = (PayService)cglibProxy.newProxyInstance(new PayServiceImpl());
//调用目标方法
payService.callback("1234a5s4d6");
CGLib动态代理和JDK动态代理总结
总结
-
动态代理与静态代理相⽐较,最⼤的好处是接⼝中声明的所有⽅法都被转移到调⽤处理器⼀个集中的⽅法中处理,解耦和易维护
-
两种动态代理的区别:
- JDK动态代理:要求⽬标对象实现⼀个接⼝,但是有时候⽬标对象只是⼀个单独的对象,并没有实现任何的接⼝,这个时候就可以⽤CGLib动态代理
- CGLib动态代理,它是在内存中构建⼀个⼦类对象从⽽实现对⽬标对象功能的扩展
- JDK动态代理是⾃带的,CGlib需要引⼊第三⽅包
- CGLib动态代理基于继承来实现代理,所以⽆法对final类、private⽅法和static⽅法实现代理
-
Spring AOP中的代理使⽤的默认策略:
- 如果⽬标对象实现了接⼝,则默认采⽤JDK动态代理
- 如果⽬标对象没有实现接⼝,则采⽤CgLib进⾏动态代理
- 如果⽬标对象实现了接口,程序⾥⾯依旧可以指定使⽤CGlib动态代理
面向切面编程Spring AOP实战配置
基于Spring的AOP实现通用日志打印
-
需求分析:针对Videoservice接⼝实现⽇志打印
-
三个核⼼包
- spring-aop:AOP核⼼功能,例如代理⼯⼚
- aspectjweaver:简单理解,⽀持切⼊点表达式
- aspectjrt:简单理解,⽀持aop相关注解
-
引入相关jar包
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.6.11</version></dependency>
-
定义service接⼝和实现类
实现类
public class VideoServiceImpl implements VideoService{ @Override public int save(Video video) { System.out.println("保存video"); return 1; } @Override public Video findById(int id) { System.out.println("根据id查询video"); return new Video(); }}
-
定义横切关注点
//横切关注点 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()); } }
-
配置xml
-
添加schema(注:在xml中beans标签内 添加)
<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">
-
配置xml
<bean id="timeHandler" class="net.maoni.aop.TimeHandler"/> <bean id="videoService" class="net.maoni.service.VideoServiceImpl" /> <!--aop 配置--> <aop:config> <!-- 配置横切关注点 ref必须指向bean的id或者name--> <aop:aspect id="time" ref="timeHandler"> <!--定义切入点表达式 切入点 通知VideoService接口里的所有方法--> <!--<aop:pointcut id="allMethodlogPointCut" expression="execution(* net.maoni.service.VideoService.*(..))"/>--> <!--定义切入点表达式 切入点 通知VideoService接口里的save方法--> <aop:pointcut id="allMethodlogPointCut" expression="execution(* net.maoni.service.VideoService.save*(..))"/> <!--advice通知 before and after--> <aop:before method="printBefore" pointcut-ref="allMethodlogPointCut"/> <aop:after method="printAfter" pointcut-ref="allMethodlogPointCut"/> </aop:aspect> </aop:config>
-
测试类
private static void TestAop(ApplicationContext context){
VideoService videoService =(VideoService) context.getBean("videoService");
videoService.save(new Video());
videoService.findById(2);
}
测试结果
printBefore 日志 time2021-07-27T10:12:02.249
保存video
printAfter 日志 time2021-07-27T10:12:02.250
根据id查询video
从结果中可以看出,执行save方法时执行了通知方法,在执行findById时并没有执行… 根据需求可以配置多个横切关注点
Spring5.X Xml配置转换到注解配置
Spring使用xml和注解的优缺点
- spring的使⽤⽅式有两种 xml配置和注解
- 注解的优势:配置简单,维护⽅便
- xml的优势:单修改xml时不⽤改源码,不⽤重新编译和部署
- 结论: 看团队开发规范进⾏选择,没有强调⼀定⽤哪个 更多的是xml+注解配合使⽤,⽐如spring整合mybatis
Spring5.X的注解配置项目
-
开启注解配置和包的扫描
-
spring中使用注解描述上下文 : AnnotationConfigApplicationContext
AnnotationConfigApplicationContext context= new AnnotationConfigApplicationContext();
-
Test
加注解的类
@Component("videoService")
public class VideoServiceImpl implements VideoService{
@Override
public int save(Video video) {
System.out.println("保存video");
return 1;
}
@Override
public Video findById(int id) {
System.out.println("根据id查询video");
return new Video();
}
}
@Componentpublic class Video {
private Integer id; private String title; public void init(){
System.out.println("video 类 init 方法被调用"); } public void destroy(){
System.out.println("video 类 destroy 方法被调用"); }}
测试类
public static void main(String[] args) {
// spring 中注解使用 描述spring中注解的上下文
AnnotationConfigApplicationContext context= new AnnotationConfigApplicationContext();
// 扫描包或者子包下的全部类
context.scan("net.maoni");
context.refresh(); //完成初始化操作,核心方法
VideoService videoService = (VideoService)context.getBean("videoService"); //获取bean
videoService.findById(2);
Video video = (Video)context.getBean("video");
video.init();
}
}
结果
根据id查询video
video 类 init 方法被调用
总结:
使用Component注解描述类时,里面的参数类似xml里面的id或者name,用于告诉spring,这个类,不加参数默认为这个类的首字母小写
重点:Component属于通用组件
spring的xml和注解对比
常用注解
-
bean定义
- xml方式
- 注解方式
- @Component 通⽤组件 细分:
- @Controller (⽤于web层)
- @Service (⽤于service层)
- @Repository (⽤于dao仓库层)
- @Component 通⽤组件 细分:
-
bean 取名
- xml方式: id或者name
- 注解方式:@Component("…")
-
bean的注入
- xml注入:通过标签注入( 和 )
- 注解注入: 类型注⼊@Autowired 名称注⼊@Qualififier
-
bean的生命周期
-
xml方式: init-method、destroy-method
<bean id="video" class="net.maoni.domain.Video" scope="singleton" init-method="init" destroy-method="destroy">
-
注解方式: @PostConstruct初始化、@PreDestroy销毁
在video的封装类里
@PostConstruct public void init(){ System.out.println("video 类 init 方法被调用"); } @PreDestroy public void destroy(){ System.out.println("video 类 destroy 方法被调用"); }
-
-
bean的作用范围
-
xml方式:scope属性
<bean id="video" class="net.maoni.domain.Video" scope="singleton" init-method="init" destroy-method="destroy">
-
注解方式:@Scope
@Scope("prototype") //作用域设置为多列 public class Video { ....}
-
使用@Confifiguration和@Bean注解定义第三方bean
作用:当类没有被spring管理时,可以用@Confifiguration和@Bean注解定义第三方bean,生成的bean 也可以被sping ioc容器进行管理
创建一个专门用来管理第三方创建的bean的包,在里面创建一个类用来管理创建的bean,如下所示
@Configuration
public class AppConfig {
//使用@Bean注解 表明这个bean 交给spring 进行管理,如果没有指定名称,默认
//方法名首字母小写 如下 在bean容器中的名称为videoOrder
@Bean(initMethod = "init",destroyMethod = "destory")
public VideoOrder videoOrder(){
return new VideoOrder();
}
}
生成bean的类
@Data
public class VideoOrder {
private Integer id;
private String outTradeNo; //订单号
private Video video;
public VideoOrder(){
System.out.println("VideoOrder无参构造函数被调用");
}
public VideoOrder(Video video){
this.video =video;
}
public void init(){
System.out.println("执行VideoOrder init方法");
}
public void destory(){
System.out.println("执行VideoOrder destory方法");
}
}
测试
VideoOrder videoOrder = (VideoOrder)context.getBean("videoOrder");
分析
VideoOrder类里含有无参构造方法,在获取bean对象时会执行无参构造,其次 initMethod = “init”,destroyMethod = "destory"为该类的init 和destory
在上述的AppConfig类里可以添加多个第三方bean
结果
VideoOrder无参构造函数被调用
执行VideoOrder init方法
Spring的自动映射配置文件PropertySource注解讲解
@PropertySource注解的使用
- @PropertySource: 指定加载配置⽂件,配置⽂件映射到实体类
- 使⽤@Value映射到具体的java属性
具体实现
假设配置一个配置properties
# 定义服务端ip
server.host=127.0.0.1
# 定义端口号
server.port =999
配置实体类
@Configuration
@PropertySource(value = "classpath:config.properties") //@PropertySource 指定加载配置文件并把配置文件映射到实体类
@Data
public class CustomConfig {
@Value("${server.host}")
private String host;
@Value("${server.port}")
private Integer port;
}
注:通过value加载配置文件路径 通过@value 注入配置文件指定属性
在需要用到的类里将CustomConfig配置实体类封装,然后调用方法即可
@Autowired //注入bean
private CustomConfig customConfig
@Override
public Video findById(int id) {
System.out.println("根据id查询video");
System.out.println(customConfig.getHost());
return new Video();
}
测试类中调用接口实现类中的方法
VideoService videoService = (VideoService)context.getBean("videoService"); //获取bean
videoService.findById(2);
运行结果显示
VideoOrder无参构造函数被调用
执行VideoOrder init方法
根据id查询video
127.0.0.1 ------------------ 为配置properties 的属性
基于Spring注解配置AOP面向切面编程
Spring AOP注解的基础准备
- 声明切⾯类 @Aspect(切⾯): 通常是⼀个类,⾥⾯可以定义切⼊点和通知
- 配置切⼊点和通知
@Component //告诉spring 要扫描这个类
@Aspect //告诉spring 这个是切面类,里面定义切入点和通知
public class LogAdvice {
//切入点表达式
@Pointcut("execution(* net.maoni.service.VideoServiceImpl.*(..))")
public void aspect(){
}
//前置通知
@Before("aspect()")
public void beforeLog(JoinPoint joinPoint){
System.out.println("前置通知 方法执行");
}
//后置通知
@After("aspect()")
public void afterLog(JoinPoint joinPoint){
System.out.println("后置通知 方法执行");
}
}
开启Spring AOP注解配置和扫描
开启SpringAOP注解配置
@Configuration //告诉spring,这是一个配置类
@ComponentScan("net.maoni") //告诉spring扫描指定的包
@EnableAspectJAutoProxy //开启spring 对aspect的支持
public class AnnotationAppConfig {
}
切面类(定义切入点和通知)
@Component
@Aspect //告诉spring 这个是切面类,里面定义切入点和通知
public class LogAdvice {
//切入点表达式,也可以在切入点上执行切入点表达式
@Pointcut("execution(* net.maoni.service.VideoServiceImpl.*(..))")
public void aspect(){
}
//前置通知
// @Before("aspect()") //这是第一种方式,需要写一个aspect()函数
// 第二种方式,直接在()里加入切入点表达式
@Before("execution(* net.maoni.service.VideoServiceImpl.*(..))")
public void beforeLog(JoinPoint joinPoint){
System.out.println("前置通知 方法执行");
}
//后置通知
@After("execution(* net.maoni.service.VideoServiceImpl.*(..))")
public void afterLog(JoinPoint joinPoint){
System.out.println("后置通知 方法执行");
}
}
效果测试
public class App {
public static void main(String[] args) {
// spring 中注解使用 描述spring中注解的上下文
AnnotationConfigApplicationContext context= new AnnotationConfigApplicationContext(AnnotationAppConfig.class);
VideoService videoService = (VideoService)context.getBean("videoService"); //获取bean
videoService.findById(2);
}
}
前置通知 方法执行
根据id查询video
127.0.0.1
后置通知 方法执行
AOP案例实战之环绕通知统计接口耗时
-
配置环绕通知:打印⽅法请求耗时时间
-
环绕通知获取⽬标⽅法和参数和目标类名
- 获取调用的类名:joinPoint.getTarget().getClass().getName();
- 获取调用的方法名:joinPoint.getSignature();
- 获取调用的参数值:joinPoint.getArgs();
//环绕通知 @Around("execution(* net.maoni.service.VideoServiceImpl.*(..))") public void around(JoinPoint joinPoint){ // 获取调用的类名 String target=joinPoint.getTarget().getClass().getName(); System.out.println("调用的接口名:"+ target); // 获取调用的方法名 Object method = joinPoint.getSignature(); System.out.println("调用方法名:"+method); // 获取调用的参数值 Object [] args =joinPoint.getArgs(); for (Object m:args){ System.out.println("调用接口中的参数值:"+ m); } 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");
调用的接口名:net.maoni.service.VideoServiceImpl 调用方法名:Video net.maoni.service.VideoService.findById(int) 调用接口中的参数值:2