Spring 注解
- (一)、AOP功能测试
- 1.AOP 使用步骤
- (1).导入AOP对应的依赖
- (2).编写业务逻辑类
- (3).编写切面类
- (4).编写配置类
- (5). 编写测试类
- (二)、AOP 原理
- 1.@EnableAspectJAutoProxy
- (1).@EnableAspectJAutoProxy源码
- (2).AspectJAutoProxyRegistrar 自定义注册bean源码
- (3).打断点进行Debug测试
- 2.AnnotationAwareAspectJAutoProxyCreator 分析
- (1).继承树
- (2).AbstractAutoProxyCreator
- (3).AbstractAdvisorAutoProxyCreator
- 3.注册 AnnotationAwareAspectJAutoProxyCreator
- (1).打断点进行Debug调试
- (2).运行流程 (创建和注册AnnotationAwareAspectJAutoProxyCreator)
- 4.AnnotationAwareAspectJAutoProxyCreator 执行时机
- 5.创建AOP代理对象
- 6.AOP原理的总结 ⭐
- (三)、声明式事务
- 1.声明式事务 - 环境搭建
- (1).导入依赖
- (2).实体类和业务层
- 2.声明式事务 - 测试成功
- (1).遇见问题
- (2).解决问题 -> 事务型(成功全成功_失败全失败)
- 3.声明式事务原理
- (四)、扩展原理
- 1.BeanFactoryPostProcessor ->来定制和修改beanFactory的内容
- 2.BeanDefinitionRegistryPostProcessor -> 额外添加组件
- 3.ApplicationListener -> 应用监听器
- (1).ApplicationListener 的应用
- 4.@EventListener 监听注解
- (1).@EventListener 注解监听方法事件
- (五)、Spring源码总结
- 1.Spring源码总结
- (六)、WEB 注解
- 1.简介与测试
- (1).创建一个Maven的web项目
- (2).创建页面支持
- (3).编写后端
- 2.ServletContainerInitalizer
- (1).创建我们的指定扫描的文件并编辑
- (2).编写被扫描的文件信息
- (3).将要被扫描的类全限定名写入文件中
- (4).运行结果
- 3.ServletContext 注册三大组件
- 4.SpringMVC整合分析
- 5.Servlet 异步
- (1).AsyncContext 实现异步处理
- (2).Callable 实现异步处理
- (3).DeferredResult 实现异步
(一)、AOP功能测试
AOP是指在程序的运行期间动态地将某段代码切入到指定方法、指定位置
进行运行的编程方式。AOP的底层是使用动态代理实现的。
1.AOP 使用步骤
* AOP【动态代理】 : 指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式;* 1.如何使用?* (1).导入AOP的依赖。* (2).定义一个业务逻辑类(MathCalculator)。在业务逻辑运行的时候将日志进行打印(方法之前,方法结束,方法异常)* (3).定义一个日志切面类(LogAspect)。切面类里面的方法需要动态的感知 业务逻辑类(MathCalculator)的运行情况* (3.1).通知方法: 前置通知@Before(在目标方法运行之前运行),* 后置通知@After(在目标方法运行之后运行,不在意正常结束还是异常结束),* 返回通知@AfterReturning(在目标方法正常返回之后运行),* 异常通知@AfterThrowing(在目标方法发生异常之后运行),* 环绕通知@Round(动态代理,手动推进目标方法运行 joinPoint.)* (4).给切面类(LogAspect)的目标方法标注何时何处地运行 (通知注解)* (5).将切面类和业务逻辑类(目标方法所在类) 都加入到容器中* (6).必须告诉Spring的IOC容器哪个类是切面类(我们需要给切面类添加一个注解 @Aspect)* (7).在配置类上开启 注解版的切面功能 ( <aop:aspectj-autoproxy></aop:aspectj-autoproxy> @EnableAspectJAutoProxy)* (8).测试,这个逻辑类一定要使用IOC容器的类,不能使用new
(1).导入AOP对应的依赖
<!-- Spring切面的包 --><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>4.3.12.RELEASE</version></dependency>
(2).编写业务逻辑类
package com.jsxs.aop;/*** @Author Jsxs* @Date 2023/8/19 11:49* @PackageName:com.jsxs.aop* @ClassName: MathCalculator* @Description: TODO* @Version 1.0*/
public class MathCalculator {public int div(int x,int y){return x/y;}
}
(3).编写切面类
- 使用非公共切入点 @Pointcut
标注是切面的注解,且使用四个通知方法和公共切点注解
package com.jsxs.aop;import org.aspectj.lang.annotation.*;/*** @Author Jsxs* @Date 2023/8/19 11:52* @PackageName:com.jsxs.aop* @ClassName: LogAspect* @Description: TODO* @Version 1.0*/@Aspect // 告诉IOC容器这是一个切面类 ⭐
public class LogAspect {// 1.抽取公共地切入点表达式 ⭐⭐@Pointcut("execution(public int com.jsxs.aop.MathCalculator.*(..))")public void pointCut(){}// 2.在目标方法之前运行切入:切入点表达式( public int com.jsxs.aop.MathCalculator.div(int,int))// *号代表这个类地所有方法, 一个.代表一个变量@Before("pointCut()") ⭐⭐⭐public void logStart(){System.out.println("除法运行..... 参数列表是:{}");}// 3.在目标方法之后进行切入, 使用公共地切入点表达式@After("pointCut()") ⭐⭐⭐⭐public void endStart(){System.out.println("除法运行结束....");}// 4.在目标方法返回正确地话@AfterReturning("pointCut()") ⭐⭐⭐⭐⭐public void success(){System.out.println("除法正常运行... 运行结果:{}");}// 5.在目标方法出现异常地话@AfterThrowing("pointCut()") ⭐⭐⭐⭐⭐⭐public void logError(){System.out.println("除法异常运行...");}
}
- 不使用公共切入点
标注是切面的注解,且使用四个通知方法
package com.jsxs.aop;import org.aspectj.lang.annotation.*;/*** @Author Jsxs* @Date 2023/8/19 11:52* @PackageName:com.jsxs.aop* @ClassName: LogAspect* @Description: TODO* @Version 1.0*/@Aspect // 告诉IOC容器这是一个切面类 ⭐
public class LogAspect {// 2.在目标方法之前运行切入:切入点表达式( public int com.jsxs.aop.MathCalculator.div(int,int)) // *号代表这个类地所有方法, 一个.代表一个变量 ⭐⭐@Before("execution(public int com.jsxs.aop.MathCalculator.*(..))")public void logStart(){System.out.println("除法运行..... 参数列表是:{}");}// 3.在目标方法之后进行切入, 使用公共地切入点表达式 ⭐⭐⭐@After("execution(public int com.jsxs.aop.MathCalculator.*(..))")public void endStart(){System.out.println("除法运行结束....");}// 4.在目标方法返回正确地话 ⭐⭐⭐⭐@AfterReturning("execution(public int com.jsxs.aop.MathCalculator.*(..))")public void success(){System.out.println("除法正常运行... 运行结果:{}");}// 5.在目标方法出现异常地话 ⭐⭐⭐⭐⭐@AfterThrowing("execution(public int com.jsxs.aop.MathCalculator.*(..))")public void logError(){System.out.println("除法异常运行...");}
}
(4).编写配置类
- 注解版开启aop切面
package com.jsxs.config;import com.jsxs.aop.LogAspect;
import com.jsxs.aop.MathCalculator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;/*** @Author Jsxs* @Date 2023/8/19 11:41* @PackageName:com.jsxs.config* @ClassName: MainConfigOfAOP* @Description: TODO AOP【动态代理】 : 指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式;* 1.如何使用?* (1).导入AOP的依赖。* (2).定义一个业务逻辑类(MathCalculator)。在业务逻辑运行的时候将日志进行打印(方法之前,方法结束,方法异常)* (3).定义一个日志切面类(LogAspect)。切面类里面的方法需要动态的感知 业务逻辑类(MathCalculator)的运行情况* (3.1).通知方法: 前置通知@Before(在目标方法运行之前运行),* 后置通知@After(在目标方法运行之后运行,不在意正常结束还是异常结束),* 返回通知@AfterReturning(在目标方法正常返回之后运行),* 异常通知@AfterThrowing(在目标方法发生异常之后运行),* 环绕通知@Round(动态代理,手动推进目标方法运行 joinPoint.)* (4).给切面类的目标方法标注何时何处地运行 (通知注解)⭐⭐* (5).将切面类和业务逻辑类(目标方法所在类) 都加入到容器中 ⭐⭐⭐* (6).必须告诉Spring的IOC容器哪个类是切面类(我们需要给切面类添加一个注解 @Aspect) ⭐⭐⭐⭐* (7).在配置类上开启 注解版的切面功能 ( <aop:aspectj-autoproxy></aop:aspectj-autoproxy> @EnableAspectJAutoProxy)* (8).测试,这个逻辑类一定要使用IOC容器的类,不能使用new* @Version 1.0*/@Configuration //⭐
@EnableAspectJAutoProxy //⭐⭐ 开启注解版本的依赖
public class MainConfigOfAOP {// 业务逻辑类 ⭐⭐⭐⭐@Beanpublic MathCalculator mathCalculator(){return new MathCalculator();}// 切面类 ⭐⭐⭐⭐⭐@Beanpublic LogAspect logAspect(){return new LogAspect();}}
- 非注解版开启aop依赖
(5). 编写测试类
package com.jsxs.Test;import com.jsxs.aop.MathCalculator;
import com.jsxs.config.MainConfigOfAOP;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;/*** @Author Jsxs* @Date 2023/8/19 12:47* @PackageName:com.jsxs.Test* @ClassName: AOP_Test* @Description: TODO* @Version 1.0*/
public class AOP_Test {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAOP.class);for (String beanDefinitionName : applicationContext.getBeanDefinitionNames()) {System.out.println(beanDefinitionName);}// 1.这个对象我们不能使用new创建,我们需要使用IOC容器的组件进行调用 ⭐MathCalculator calculator = applicationContext.getBean(MathCalculator.class);calculator.div(1,3);}
}
- 测试结果 不写类名 和 结果
- 测试结果 写类名 和 结果
1.我们需要在切面类进行更改
JoinPoint 这个类如果要使用一定要写在参数列表的第一位,否则可能不生效
package com.jsxs.aop;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;import java.util.Arrays;/*** @Author Jsxs* @Date 2023/8/19 11:52* @PackageName:com.jsxs.aop* @ClassName: LogAspect* @Description: TODO* @Version 1.0*/@Aspect // 告诉IOC容器这是一个切面类 ⭐
public class LogAspect {// 2.在目标方法之前运行切入:切入点表达式( public int com.jsxs.aop.MathCalculator.div(int,int))// *号代表这个类地所有方法, 一个.代表一个变量@Before("execution(public int com.jsxs.aop.MathCalculator.*(..))")public void logStart(JoinPoint joinPoint){ //⭐⭐ 切入点类可以获取名字和参数列表System.out.println(joinPoint.getSignature().getName()+"除法运行..... 参数列表是:{"+ Arrays.asList(joinPoint.getArgs())+"}");}// 3.在目标方法之后进行切入, 使用公共地切入点表达式@After("execution(public int com.jsxs.aop.MathCalculator.*(..))")public void endStart(){System.out.println("除法运行结束....");}// 4.在目标方法返回正确地话 ⭐⭐⭐ 获取返回结果,注解里面的参数名要和方法里面的参数名一致@AfterReturning(value = "execution(public int com.jsxs.aop.MathCalculator.*(..))",returning = "result")public void success(JoinPoint joinPoint,Object result){System.out.println(joinPoint.getSignature().getName()+"除法正常运行... 运行结果:{"+result+"}");}// 5.在目标方法出现异常地话 ⭐⭐⭐⭐ 获取异常信息@AfterThrowing(value = "execution(public int com.jsxs.aop.MathCalculator.*(..))",throwing = "exception")public void logError(Exception exception){System.out.println("除法异常运行..."+exception);}
}
(二)、AOP 原理
- 看给容器中注册了什么组件?
- 这个组件什么时候工作?
- 这个组件工作时候的功能?
1.@EnableAspectJAutoProxy
(1).@EnableAspectJAutoProxy源码
* AOP【原理】* 1.@EnableAspectJAutoProxy ->核心注解* (1).@Import(AspectJAutoProxyRegistrar.class) -> 给容器中导入AspectJAutoProxyRegistrar这个组件* internalAutoProxyCreator=AnnotationAwareAspectJAutoProxyCreator* 给容器中注入了一个AnnotationAwareAspectJAutoProxyCreator ⭐
@EnableAspectJAutoProxy ->核心注解 给容器中导入AspectJAutoProxyRegistrar这个组件
(2).AspectJAutoProxyRegistrar 自定义注册bean源码
通过这个类进行创建自定义组件
(3).打断点进行Debug测试
- registerBeanDefinitions 方法处打一个断点
- registerOrEscalateApcAsRequired()
- 给容器中注入AnnotationAwareAspectJAutoProxyCreator
2.AnnotationAwareAspectJAutoProxyCreator 分析
(1).继承树
CTRL+H 打开目录树
* AOP【原理】* 1.@EnableAspectJAutoProxy ->核心注解* (1).@Import(AspectJAutoProxyRegistrar.class) -> 给容器中导入AspectJAutoProxyRegistrar这个组件* internalAutoProxyCreator=AnnotationAwareAspectJAutoProxyCreator* 给容器中注入了一个AnnotationAwareAspectJAutoProxyCreator* 2. AnnotationAwareAspectJAutoProxyCreator* AnnotationAwareAspectJAutoProxyCreator* ->(继承) AspectJAwareAdvisorAutoProxyCreator* ->(继承) AbstractAutoProxyCreator implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware* 关注后置处理器(在bean初始化前后) 和 自动装配BeanFactory* (2.1). setBeanFactory* ->(继承) AbstractAdvisorAutoProxyCreator* ->(继承) ProxyConfig* ->(继承) Object
(2).AbstractAutoProxyCreator
AbstractAutoProxyCreator类 具有后置处理器的逻辑
具有setBeanFactry()的功能
(3).AbstractAdvisorAutoProxyCreator
这里初始化了 initBeanFactory()
3.注册 AnnotationAwareAspectJAutoProxyCreator
(1).打断点进行Debug调试
1.打上第一个断点
2.配置类上打上两个断点
(2).运行流程 (创建和注册AnnotationAwareAspectJAutoProxyCreator)
* 3.流程:* (1).传入配置类,创建IOC容器 -> new AnnotationConfigApplicationContext(MainConfigOfAOP.class);* (2).注册配置类register(annotatedClasses),重启IOC容器refresh()* (3).registerBeanPostProcessors(beanFactory); 注册bean的后置处理器来方便拦截bean的创建* (3.1).先获取IOC容器已经定义了的需要创建对象的所有 BeanPostProcessor* (3.2).给容器中添加别的BeanPostProcessor* (3.3).优先注册了priorityOrderedPostProcessors接口的BeanPostProcessor* (3.4).在给容器中注册实现了Ordered接口的BeanPostProcessor* (3.5).注册没实现优先级接口的 BeanPostProcessors* (3.6).注册BeanPostProcessors,实际上就是创建BeanPostProcessors对象,保存在容器中;* 创建internalAutoProxyCreator的BeanPostProcessors【AnnotationAwareAspectJAutoProxyCreator】* (1).创建Bean的实列* (2).populateBean:给bean的各种属性赋值* (3).initializeBean: 初始化bean* (1).invokeAwareMethods() :处理Aware接口的方法回调* (2).applyBeanPostProcessorsBeforeInitialization() 应用后置处理器* (3).invokeInitMethods()执行自定义的初始方法* (4).applyBeanPostProcessorsAfterInitialization() 执行后置处理器的postProcessor* (4).BeanPostProcessor(AnnotationAwareAspectJAutoProxyCreator) 创建成功* (3.7).把BeanPostProcessor注册到BeanFactory中 beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
1.创建IOC容器
2.发现refresh()方法中具有 后置处理器的功能 注册配置类register(annotatedClasses),重启IOC容器refresh()
3.registerBeanPostProcessors(beanFactory); 注册bean的后置处理器来方便拦截bean的创建
3.1 先获取IOC容器已经定义了的需要创建对象的所有 BeanPostProcessor
3.2 给容器中添加别的BeanPostProcessor
3.3 优先注册了priorityOrderedPostProcessors接口的BeanPostProcessor
3.4 在给容器中注册实现了Ordered接口的BeanPostProcessor
3.5注册没实现优先级接口的 BeanPostProcessors
3.6 注册BeanPostProcessors,实际上就是创建BeanPostProcessors对象,保存在容器中;
4.AnnotationAwareAspectJAutoProxyCreator 执行时机
* 4.执行时机* 1.遍历容器中所有的Bean,依次创建对象getBean(beanName)* getBean->doGetBean()->getSingleton->* 2.创建bean* (1).先从缓存中获取当前bean,如果能获取到,说明bean是之前被创建过的,直接使用,否则再创建* 只要创建好的Bean都会被缓存起来。** (2).CreateBean() 创建bean* (2.1).resolveBeforeForeInstantion(beanName,mbdToUse) --> 希望后置处理器在此能返回一个代理对象,如果能返回代理对象就使用,如果不能就继续* (2.2).doCreateBean() 真正的去创建一个bean实列* 3. AnnotationAwareAspectJAutoProxyCreator 在所有的bean创建之前会有一个拦截,InstantionAnwareBeanPostProcessor,会调用PostProcessBeforeInstantiation*
5.创建AOP代理对象
* 5.AOP原理* 1.每一个bean创建之前,调用postProcessBeforeInstantiation();* 关心 普通组件 和 切面组件 的创建* (1).判断当前bean是否在advisedBeans中(保存了所需要增强的bean)* (2).判断是否是切面* (3).是否需要跳过* (3.1).获取候选的增强器(切面里面的通知方法) 每一个封装的通知方法都是一个增强器 ,返回true* (3.2). 返回false -> 永远不跳过* 2.每一个bean创建之后,调用postProcessAfterInstantiation();* (1).获取当前bean的所有增强器(增强方法)* (1.1).找到的候选的所有增强器(找那些通知方法是需要切入当前bean方法的)* (1.2).获取能在当前bean使用的增强器* (1.3).给增强器进行排序* (2).保存当前bean在adviseBeans中* (3).如果当前bean需要增强,创建当前bean的代理对象。* (3.1).获取所有增强器(通知方法)* (3.2).保存到proxyFactory* (3.3).创建代理对象 ⭐ (两种)* (3.3.1).JDKDynamicAopProxy(config) ->jdk创建代理模式(有接口使用jdk)* (3.3.2).ObjenesisCglibAopProxy(config) ->cglib创建代理模式(无接口使用cglib)* ⭐ (3.4).给IOC容器返回当前组件使用cglib增强了的代理对象* ⭐ (3.5).以后容器中获取到的就是这个组件的代理对象,执行目标方法的时候,代理对象就会执行通知方法的流程
6.AOP原理的总结 ⭐
- @EnableAspectJAutoProxy
作用: 开启AOP注解功能
- @EnableAspectJAutoProxy 作用:
会给容器注册一个组件
(AnnotationAwareAspectJAutoProxyCreator) - AnnotationAwareAspectJAutoProxyCreator 是一个
后置处理器
- 容器的创建流程
- registerBeanPostProcessors(注册后置处理器),
作用:创建registerBeanPostProcessors
- finishBeanFactoryInitialization(beanFactory);
作用:初始化剩下的单实列bean
- 创建业务逻辑组件和切面组件
- AnnotationAwareAspectJAutoProxyCreator 拦截组件的创建过程
- 组件创建完之后,判断组件是否需要增强
- 是:
切面的通知方法,包装成增强器(Adviros)
; 业务逻辑组件创建一个代理对象
- 是:
- registerBeanPostProcessors(注册后置处理器),
- 执行目标方法
- 代理对象执行目标方法
- CglibAopProxy.intercept();
- 得到目标方法的拦截器链 (增强器包装成拦截器MethodInterceptor)
- 利用拦截器的链式机制,依次进入每一个拦截器进行执行
- 效果:
- 正常执行: 前置通知 -> 目标方法 -> 后置通知 -> 返回通知
- 出现异常: 前置通知 ->目标方法 -> 后置通知 -> 异常通知
(三)、声明式事务
1.声明式事务 - 环境搭建
(1).导入依赖
<!-- Spring包 ->IOC --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>4.3.12.RELEASE</version></dependency><!-- C3P0数据包 ->数据源 --><dependency><groupId>com.mchange</groupId><artifactId>c3p0</artifactId><version>0.9.5.2</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.6</version></dependency><!-- Spring切面的包 ->aop --><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>4.3.12.RELEASE</version></dependency><!-- Spring-JDBC -> jdbcTemplate --><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>4.3.12.RELEASE</version></dependency>
(2).实体类和业务层
1.实体类
package com.jsxs.bean;/*** @Author Jsxs* @Date 2023/8/22 14:27* @PackageName:com.jsxs.bean* @ClassName: Admin* @Description: TODO* @Version 1.0*/
public class Admin {private int id;private String name;private String password;public Admin() {}public Admin(int id, String name, String password) {this.id = id;this.name = name;this.password = password;}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}@Overridepublic String toString() {return "Admin{" +"id=" + id +", name='" + name + '\'' +", password='" + password + '\'' +'}';}
}
2.Dao层:
package com.jsxs.mapper;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;import javax.annotation.Resource;/*** @Author Jsxs* @Date 2023/8/22 14:28* @PackageName:com.jsxs.mapper* @ClassName: AdminMapper* @Description: TODO* @Version 1.0*/
@Repository
public class AdminMapper {@Resourceprivate JdbcTemplate jdbcTemplate;public void add(){String sql="insert into admin(name,password) values(?,?)";int i = jdbcTemplate.update(sql,222,222); // 后面的是参数也就是占位符,占位符有几个,参数也就有几个if (i>0){System.out.println("添加数据成功!!");}else {System.out.println("添加数据失败!!");}}}
3.业务层
package com.jsxs.service;import com.jsxs.mapper.AdminMapper;
import org.springframework.stereotype.Service;import javax.annotation.Resource;/*** @Author Jsxs* @Date 2023/8/22 14:30* @PackageName:com.jsxs.service* @ClassName: AdminServer* @Description: TODO* @Version 1.0*/@Service
public class AdminServer {@ResourceAdminMapper adminMapper;public void insert(){adminMapper.add();}
}
4.配置类
package com.jsxs.tx;import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;import javax.sql.DataSource;
import java.beans.PropertyVetoException;/*** @Author Jsxs* @Date 2023/8/22 14:06* @PackageName:com.jsxs.tx* @ClassName: TxConfig* @Description: TODO 声明式事务* 1. 导入相关依赖 : 数据源、数据库驱动、Spring-JDBC->(提供了JDBCTemplate)* 2.配置数据源,JDBCTemplate(Spring提供的简化数据库操作的工具)操作数据库* @Version 1.0*/@Configuration
@ComponentScan(value = "com.jsxs")
@PropertySource(value = "classpath:db.properties")
public class TxConfig {// 1.配置我们的数据源@Beanpublic DataSource dataSource() throws PropertyVetoException {// 1.1.c3p0数据源ComboPooledDataSource dataSource = new ComboPooledDataSource();dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/demo1");dataSource.setUser("root");dataSource.setPassword("121788");dataSource.setDriverClass("com.mysql.jdbc.Driver");return dataSource;}// 2.我们设置JDBC的模板,又因为这个模板需要数据源,所以我们要存放我们的数据源。又因为在配置类中的参数是先从IOC容器中拿取的,所以我们直接设置成参数即可。@Beanpublic JdbcTemplate jdbcTemplate(DataSource dataSource){return new JdbcTemplate(dataSource);}
}
5.测试
package com.jsxs.Test;import com.jsxs.service.AdminServer;
import com.jsxs.tx.TxConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;/*** @Author Jsxs* @Date 2023/8/22 14:45* @PackageName:com.jsxs.Test* @ClassName: AOP_TX* @Description: TODO* @Version 1.0*/
public class AOP_TX {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(TxConfig.class);for (String beanDefinitionName : applicationContext.getBeanDefinitionNames()) {System.out.println(beanDefinitionName);}AdminServer server = applicationContext.getBean(AdminServer.class);server.insert();}
}
2.声明式事务 - 测试成功
(1).遇见问题
* 1. 导入相关依赖 : 数据源、数据库驱动、Spring-JDBC->(提供了JDBCTemplate)* 2.配置数据源,JDBCTemplate(Spring提供的简化数据库操作的工具)操作数据库* ⭐ 3.给方法上标注@Transactional 表示当前方法是一个事务方法.* ⭐ 4.@EnableTransactionManagement 需要放在配置类进行开启注解的事务* ⭐ 5.添加PlatformTransactionManager这个组件
我们在插入的方法中故意设置一个异常的操作,然后我们执行这个方法但是我们发现我们并不会发生事务回滚(即 失败都失败,成功都成功!);
(2).解决问题 -> 事务型(成功全成功_失败全失败)
package com.jsxs.service;import com.jsxs.mapper.AdminMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;/*** @Author Jsxs* @Date 2023/8/22 14:30* @PackageName:com.jsxs.service* @ClassName: AdminServer* @Description: TODO* @Version 1.0*/@Service
public class AdminServer {@ResourceAdminMapper adminMapper;@Transactional // 声明标注事务的注解⭐public void insert(){adminMapper.add();// 2.故意制造异常 ⭐⭐System.out.println(1/0);}
}
- 基于XML文件的
- 基于注解
1.业务层添加上事务注解
package com.jsxs.service;import com.jsxs.mapper.AdminMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;/*** @Author Jsxs* @Date 2023/8/22 14:30* @PackageName:com.jsxs.service* @ClassName: AdminServer* @Description: TODO* @Version 1.0*/@Service
public class AdminServer {@ResourceAdminMapper adminMapper;@Transactionalpublic void insert(){adminMapper.add();System.out.println(1/0);}
}
2.需要在配置类上开启注解支持
package com.jsxs.tx;import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.EnableTransactionManagement;import javax.sql.DataSource;
import java.beans.PropertyVetoException;/*** @Author Jsxs* @Date 2023/8/22 14:06* @PackageName:com.jsxs.tx* @ClassName: TxConfig* @Description: TODO 声明式事务* 1. 导入相关依赖 : 数据源、数据库驱动、Spring-JDBC->(提供了JDBCTemplate)* 2.配置数据源,JDBCTemplate(Spring提供的简化数据库操作的工具)操作数据库* 3.给方法上标注@Transactional 表示当前方法是一个事务方法.* 4.@EnableTransactionManagement 需要放在配置类进行开启注解的事务* @Version 1.0*/@Configuration
@ComponentScan(value = "com.jsxs")
@PropertySource(value = "classpath:db.properties")
@EnableTransactionManagement ⭐
public class TxConfig {// 1.配置我们的数据源@Beanpublic DataSource dataSource() throws PropertyVetoException {// 1.1.c3p0数据源ComboPooledDataSource dataSource = new ComboPooledDataSource();dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/demo1");dataSource.setUser("root");dataSource.setPassword("121788");dataSource.setDriverClass("com.mysql.jdbc.Driver");return dataSource;}// 2.我们设置JDBC的模板,又因为这个模板需要数据源,所以我们要存放我们的数据源。又因为在配置类中的参数是先从IOC容器中拿取的,所以我们直接设置成参数即可。@Beanpublic JdbcTemplate jdbcTemplate(DataSource dataSource){return new JdbcTemplate(dataSource);}
}
3.进行测试的操作
package com.jsxs.Test;import com.jsxs.service.AdminServer;
import com.jsxs.tx.TxConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;/*** @Author Jsxs* @Date 2023/8/22 14:45* @PackageName:com.jsxs.Test* @ClassName: AOP_TX* @Description: TODO* @Version 1.0*/
public class AOP_TX {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(TxConfig.class);for (String beanDefinitionName : applicationContext.getBeanDefinitionNames()) {System.out.println(beanDefinitionName);}AdminServer server = applicationContext.getBean(AdminServer.class);server.insert();}
}
4.结果我们发现缺少一个组件 PlatformTransactionManager
5. 给IOC容器中添加这个组件
package com.jsxs.tx;import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jca.cci.connection.CciLocalTransactionManager;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;import javax.sql.DataSource;
import java.beans.PropertyVetoException;/*** @Author Jsxs* @Date 2023/8/22 14:06* @PackageName:com.jsxs.tx* @ClassName: TxConfig* @Description: TODO 声明式事务* 1. 导入相关依赖 : 数据源、数据库驱动、Spring-JDBC->(提供了JDBCTemplate)* 2.配置数据源,JDBCTemplate(Spring提供的简化数据库操作的工具)操作数据库* 3.给方法上标注@Transactional 表示当前方法是一个事务方法.* 4.@EnableTransactionManagement 需要放在配置类进行开启注解的事务* 5.添加PlatformTransactionManager这个组件* @Version 1.0*/@Configuration
@ComponentScan(value = "com.jsxs")
@PropertySource(value = "classpath:db.properties")
@EnableTransactionManagement // ⭐
public class TxConfig {// 1.配置我们的数据源@Beanpublic DataSource dataSource() throws PropertyVetoException {// 1.1.c3p0数据源ComboPooledDataSource dataSource = new ComboPooledDataSource();dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/demo1");dataSource.setUser("root");dataSource.setPassword("121788");dataSource.setDriverClass("com.mysql.jdbc.Driver");return dataSource;}// 2.我们设置JDBC的模板,又因为这个模板需要数据源,所以我们要存放我们的数据源。又因为在配置类中的参数是先从IOC容器中拿取的,所以我们直接设置成参数即可。@Beanpublic JdbcTemplate jdbcTemplate(DataSource dataSource){return new JdbcTemplate(dataSource);}@Bean // 在配置类中的@Bean注解的方法参数,参数默认是从IOC容器中赋值的 ⭐⭐public PlatformTransactionManager platformTransactionManager(DataSource dataSource){return new DataSourceTransactionManager(dataSource);}
}
3.声明式事务原理
* 2.事务原理* (1).@EnableTransactionManagement* 利用 TransactionManagementConfigurationSelector 给容器中会导入批量组件* 导入两个组件* (1).AutoProxyRegistrar (2).ProxyTransactionManagementConfiguration* (2).细聊 AutoProxyRegistrar* 给容器中注入一个组件,利用后置处理器机制在对象创建以后,包装对象,返回一个代理对象(增强器),代理方法执行方法利用拦截器链进行调用** (3).细聊 ProxyTransactionManagementConfiguration* 1.给事务注册一个事务增强器->事务增强器要用事务注解的信息* 2.事务拦截器* (1).先获取事务相关的属性* (2).在获取PlatformTransactionManager,如果事务先没有添加指定任何* 最终会从容器中按照类型获取一个PlateFormTransactionManager;* (3).执行目标方法* 如果异常,获取到事务管理器,利用事务管理回滚操作* 如果正常,利用事务管理器,提交事务控制
(四)、扩展原理
1.BeanFactoryPostProcessor ->来定制和修改beanFactory的内容
在BeanFactory标准初始化之后调用,所有的bean定义已经保存加载到beanFactory,但是bean的实列还未创建
1.判断无参构造函数创建实列是否在BeanFactory之前还是之后?
1.BeanFactory的后置处理器MyBeanFactoryPostProcessor
package com.jsxs.etx;import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;import java.util.Arrays;/*** @Author Jsxs* @Date 2023/8/24 10:44* @PackageName:com.jsxs.etx* @ClassName: MyBeanFactoryPostProcessor* @Description: TODO* @Version 1.0*/
@Component ⭐注册组件
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { ⭐⭐ 继承接口@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {System.out.println("MyBeanFactoryPostProcessor->执行力postProcessBeanFactory方法");// 组件的数量int count = beanFactory.getBeanDefinitionCount();// 组件的名字String[] names = beanFactory.getBeanDefinitionNames();System.out.println("当前beanFactory中有"+count+"个Bean");System.out.println(Arrays.asList(names));}
}
2.实体类
package com.jsxs.bean;/*** @Author Jsxs* @Date 2023/8/13 21:11* @PackageName:com.jsxs.bean* @ClassName: Yellow* @Description: TODO* @Version 1.0*/
public class Yellow {public Yellow() { // ⭐构造函数System.out.println("Yellow无参构造创建实列");}
}
3.配置类
package com.jsxs.etx;import com.jsxs.bean.Yellow;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;/*** @Author Jsxs* @Date 2023/8/24 10:38* @PackageName:com.jsxs.etx* @ClassName: EtxConfig* @Description: TODO BeanFactoryPostProcessor : beanFactory的后置处理器 ⭐* 在BeanFactory标准初始化之后调用,所有的bean定义已经保存加载到beanFactory,但是bean的实列还未创建* 在初始化创建其他组件前面执行* @Version 1.0*/@ComponentScan("com.jsxs.etx") ⭐扫面组件
@Configuration
public class EtxConfig {@Bean ⭐⭐ 将组件注册进来public Yellow yellow(){return new Yellow();}
}
4.测试
package com.jsxs.Test;import com.jsxs.etx.EtxConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;/*** @Author Jsxs* @Date 2023/8/24 10:48* @PackageName:com.jsxs.Test* @ClassName: IOC_ext* @Description: TODO* @Version 1.0*/
public class IOC_ext {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(EtxConfig.class);applicationContext.close();}
}
所有的bean已经保存到beanFactory工厂中,但是实列依然还没有创建。在初始化创建其他组件前面执行
2.BeanDefinitionRegistryPostProcessor -> 额外添加组件
1.注册后置处理器
package com.jsxs.etx;import com.jsxs.bean.Yellow;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.stereotype.Component;/*** @Author Jsxs* @Date 2023/8/24 11:09* @PackageName:com.jsxs.etx* @ClassName: MyBeanDefinitionRegistryPostProcessor* @Description: TODO* @Version 1.0*/
@Component ⭐
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {// BeanDefinitionRegistry Bean定义信息的保存中心,以后BeanFactory就是按照BeanDefinitionRegistry里面保存的每一个bean定义信息创建bean实列 ⭐⭐@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {System.out.println("MyBeanDefinitionRegistryPostProcessor的数量是 "+registry.getBeanDefinitionCount()+"Registry ");// 创建一个组件 ⭐⭐⭐RootBeanDefinition beanDefinition = new RootBeanDefinition(Yellow.class);// 将刚才创建的组件注册进IOC中,且名字叫做hello ⭐⭐⭐⭐registry.registerBeanDefinition("hello",beanDefinition);}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {System.out.println("MyBeanDefinitionRegistryPostProcessor的数量是:"+beanFactory.getBeanDefinitionCount()+" BeanFactory");}
}
2. 配置类 EtxConfig.java MyBeanFactoryPostProcessor.java Yellow.java IOC_ext.java 和上面的类是一样的
package com.jsxs.etx;import com.jsxs.bean.Yellow;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;/*** @Author Jsxs* @Date 2023/8/24 10:38* @PackageName:com.jsxs.etx* @ClassName: EtxConfig* @Description: TODO 1. BeanFactoryPostProcessor : beanFactory的后置处理器* 在BeanFactory标准初始化之后调用,所有的bean定义已经保存加载到beanFactory,但是bean的实列还未创建* 在初始化创建其他组件之前运行* (1).原理* (1.1).创建IOC容器** 2.BeanDefinitionRegistryPostProcessor* 在所有的bean定义信息将要被加载,bean实列还未创建的时候执行。* 优先于BeanFactoryPostProcessor执行,利用BeanDefinitionRegistryPostProcessor给容器中再额外的添加一些组件* (2).原理* (2.1).创建IOC容器* (2.2).refresh() 刷新容器 ->invokeBeanFactoryPostProcessors(beanFactory);.* *** @Version 1.0*/@ComponentScan("com.jsxs.etx")
@Configuration
public class EtxConfig {@Beanpublic Yellow yellow(){return new Yellow();}
}
3.ApplicationListener -> 应用监听器
(1).ApplicationListener 的应用
1.事件-接口
package com.jsxs.etx;import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;/*** @Author Jsxs* @Date 2023/8/24 11:59* @PackageName:com.jsxs.etx* @ClassName: MyApplicationListener* @Description: TODO* @Version 1.0*/
@Component ⭐
public class MyApplicationListener implements ApplicationListener<ApplicationEvent> {// 当容器中发布此事以后,方法触发 ⭐⭐@Overridepublic void onApplicationEvent(ApplicationEvent event) {System.out.println("收到事件"+event);}
}
2.自定义事件
package com.jsxs.Test;import com.jsxs.etx.EtxConfig;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;/*** @Author Jsxs* @Date 2023/8/24 10:48* @PackageName:com.jsxs.Test* @ClassName: IOC_ext* @Description: TODO* @Version 1.0*/
public class IOC_ext {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(EtxConfig.class);// 1.自定义发布一个事件 ⭐⭐⭐applicationContext.publishEvent(new ApplicationEvent(new String("我发布了一个事件")) {});applicationContext.close();}
}
3.配置类等其他的都没有变化
package com.jsxs.etx;import com.jsxs.bean.Yellow;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;/*** @Author Jsxs* @Date 2023/8/24 10:38* @PackageName:com.jsxs.etx* @ClassName: EtxConfig* @Description: TODO 1. BeanFactoryPostProcessor : beanFactory的后置处理器* 在BeanFactory标准初始化之后调用,所有的bean定义已经保存加载到beanFactory,但是bean的实列还未创建* 在初始化创建其他组件之前运行* (1).原理* (1.1).创建IOC容器** 2.BeanDefinitionRegistryPostProcessor* 在所有的bean定义信息将要被加载,bean实列还未创建的时候执行。* 优先于BeanFactoryPostProcessor执行,利用BeanDefinitionRegistryPostProcessor给容器中再额外的添加一些组件* (2).原理* (2.1).创建IOC容器* (2.2).refresh() 刷新容器 ->invokeBeanFactoryPostProcessors(beanFactory);.* 3.ApplicationListener 监听容器中发布的事件。事件驱动模型开发* (1).监听ApplicationEvent及其下面的子事件** 步骤:* (1).写一个监听器来监听某个事件(ApplicationEvent及其子类)* (2).把监听器加入到容器中* (3).只要容器中有相关事件的发布,我们就能监听到这个事件* ContextRefreshedEvent ->刷新事件 (所有的bean都已经创建)* ContextClosedEvent -> 关闭事件* (4).自定义事件 ⭐⭐⭐⭐* applicationContext.publishEvent(new ApplicationEvent(new String("我发布了一个事件")) {});***** @Version 1.0*/@ComponentScan("com.jsxs.etx")
@Configuration
public class EtxConfig {@Beanpublic Yellow yellow(){return new Yellow();}
}
4.@EventListener 监听注解
(1).@EventListener 注解监听方法事件
@EventListener: 这个注解可以标注在方法上,然后包扫描到我们这个包就可以实现我们的注解监听。
1.UserService.java
package com.jsxs.service;import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;/*** @Author Jsxs* @Date 2023/8/27 9:02* @PackageName:com.jsxs.service* @ClassName: UserService* @Description: TODO* @Version 1.0*/
@Service
public class UserService {// 设置监听的注解,里面的类是被监听的类。这里我们监听 ApplicationEvent 这个类@EventListener(classes = {ApplicationEvent.class})public void Listener(ApplicationEvent applicationEvent){ //这里是拿到我们监听的事件,这个是固定的,System.out.println("UserService 监听事件为...."+applicationEvent);}
}
2.配置类什么的都和上面的一样
package com.jsxs.Test;import com.jsxs.etx.EtxConfig;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;/*** @Author Jsxs* @Date 2023/8/24 10:48* @PackageName:com.jsxs.Test* @ClassName: IOC_ext* @Description: TODO* @Version 1.0*/
public class IOC_ext {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(EtxConfig.class);// 1.自定义发布一个事件applicationContext.publishEvent(new ApplicationEvent(new String("我发布了一个事件")) {});applicationContext.close();}
}
(五)、Spring源码总结
1.Spring源码总结
1. Spring容器在启动的时候,先保存所有注册进来的Bean定义信息(1).xml注册bean <bean>(2).注解: @Service ....
2.Spring容器保存定义信息之后,将会根据保存的信息在合适的时机创建Bean的实列。(1).用到Bean的时候创建组件的实列,利用getBean的方法创建Bean。创建好的Bean实列将会保存在Spring容器中。(2).统一创建剩下所有Bean的时候。
3.后置处理器(1).每一个Bean创建完成,都会使用各种后置处理器进行处理,来增强我们的Bean功能,后置处理器在bean创建的各个环节中。比如 自动注入功能,Aop代理功能
4.事件驱动模型(1).ApplicationListener 事件监听(2).ApplicationEventMulticaster 事件派发
(六)、WEB 注解
注意事项: servlet3.0的版本要求要使用tomcat7.0+
1.简介与测试
(1).创建一个Maven的web项目
(2).创建页面支持
index.jsp
<html>
<body>
<a href="Hello">点击我跳转</a>
</body>
</html>
(3).编写后端
- 注解实现映射关系
使用@WebServlet注解之后,我们可以省略我们的web.xml的映射关系
HelloServlet .java
package com.jsxs.servlet;import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** @Author Jsxs* @Date 2023/8/27 10:01* @PackageName:com.jsxs.servlet* @ClassName: HelloServlet* @Description: TODO* @Version 1.0*/@WebServlet("/Hello") // 1.设置拦截的URL,请求过来就会调用这个类的方法 ⭐public class HelloServlet extends HttpServlet { // 2.继承Servlet 并重写两个方法 ⭐⭐@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.getWriter().write("hello..."); ⭐⭐⭐}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {super.doPost(req, resp);}
}
- 使用web.xml 映射关系
<!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app><display-name>Archetype Created Web Application</display-name>
<!-- 具体处理器: 具体的业务逻辑类--><servlet><servlet-name>hello</servlet-name><servlet-class>com.jsxs.servlet.HelloServlet</servlet-class></servlet>
<!-- 处理器映射器: 主要根据处理器名字和路径找具体的处理器--><servlet-mapping><servlet-name>hello</servlet-name><url-pattern>/hello</url-pattern></servlet-mapping>
</web-app>
2.ServletContainerInitalizer
容器在启动应用的时候,会扫描当前应用每一个jar包里面的src/main/webapp/META-INF/services/javax.servlet.ServletContainerInitializer指定实现的类,启动并运行这个实现类的方法。
(1).创建我们的指定扫描的文件并编辑
(2).编写被扫描的文件信息
package com.jsxs.servlet;import com.jsxs.services.HelloService;import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;
import java.util.Set;/*** @Author Jsxs* @Date 2023/8/27 10:41* @PackageName:com.jsxs.servlet* @ClassName: MyServletContainerInitalizer* @Description: TODO* @Version 1.0*/
// 容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类,子接口,抽象类)传递过来@HandlesTypes(value = {HelloService.class}) // 传入我们感兴趣的类型并赋值给set 🚹
public class MyServletContainerInitializer implements ServletContainerInitializer { // 1.继承接口并实现方法 ⭐// 2.应用启动的时候会调用这个方法/*** @param set : 负责接受我们感兴趣类型的所有子类型 即HelloService🚹* @param servletContext: 代表当前web应用的ServletContext,一个web应用相当于一个ServletContext* @throws ServletException*/@Overridepublic void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {System.out.println("感兴趣的类型"+set);for (Class<?> aClass : set) {System.out.println(aClass);}}
}
1.抽象类
package com.jsxs.services;/*** @Author Jsxs* @Date 2023/8/27 10:54* @PackageName:com.jsxs.services* @ClassName: HelloServiceAbart* @Description: TODO 父接口的抽象子类* @Version 1.0*/
public abstract class HelloServiceAbart implements HelloService{
}
2.子接口: 接口继承接口
package com.jsxs.services;/*** @Author Jsxs* @Date 2023/8/27 10:53* @PackageName:com.jsxs.services* @ClassName: HelloServiceExt* @Description: TODO 父接口的子接口* @Version 1.0*/
public interface HelloServiceExt extends HelloService{
}
3.实现类
package com.jsxs.services;/*** @Author Jsxs* @Date 2023/8/27 10:56* @PackageName:com.jsxs.services* @ClassName: HelloServiceImpi* @Description: TODO 普通类继承接口* @Version 1.0*/
public class HelloServiceImpi implements HelloService{
}
(3).将要被扫描的类全限定名写入文件中
com.jsxs.servlet.MyServletContainerInitializer
(4).运行结果
我们发现不管是子类 子接口 还是抽象类 实现类都被打印出全限定名了。
3.ServletContext 注册三大组件
过滤器 + 监听器 + Servlet
1.要注册的过滤器
package com.jsxs.servlet;import javax.servlet.*;
import java.io.IOException;/*** @Author Jsxs* @Date 2023/8/27 12:28* @PackageName:com.jsxs.servlet* @ClassName: UserFilter* @Description: TODO 过滤器: 使用的是Servlet的包* @Version 1.0*/
public class UserFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}// 过滤请求,假如为false那么就拦截,true就放行@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {// 过滤请求System.out.println("UserFilter...");// 放行filterChain.doFilter(servletRequest,servletResponse);}@Overridepublic void destroy() {}
}
2.要注册的监听器
package com.jsxs.servlet;import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;/*** @Author Jsxs* @Date 2023/8/27 12:34* @PackageName:com.jsxs.servlet* @ClassName: UserListener* @Description: TODO 监听项目的启动和停止* @Version 1.0*/
public class UserListener implements ServletContextListener {// 监听项目的开始和初始化@Overridepublic void contextInitialized(ServletContextEvent sce) {System.out.println("UserListener---项目初始化成功....");}// 监听销毁@Overridepublic void contextDestroyed(ServletContextEvent sce) {System.out.println("UserListener---项目关闭成功....");}
}
3. 要注册的Servlet
package com.jsxs.servlet;import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** @Author Jsxs* @Date 2023/8/27 12:39* @PackageName:com.jsxs.servlet* @ClassName: UserServlet* @Description: TODO* @Version 1.0*/
public class UserServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.getWriter().write("tomcate...");}
}
4.进行注册的操作
package com.jsxs.servlet;import com.jsxs.services.HelloService;import javax.servlet.*;
import javax.servlet.annotation.HandlesTypes;
import java.util.EnumSet;
import java.util.Set;/*** @Author Jsxs* @Date 2023/8/27 10:41* @PackageName:com.jsxs.servlet* @ClassName: MyServletContainerInitalizer* @Description: TODO* @Version 1.0*/
// 容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类,子接口)传递过来@HandlesTypes(value = {HelloService.class}) // 传入我们感兴趣的类型并赋值给set 🚹
public class MyServletContainerInitializer implements ServletContainerInitializer { // 1.继承接口并实现方法 ⭐// 2.应用启动的时候会调用这个方法/*** @param set : 负责接受我们感兴趣类型的所有子类型 即HelloService🚹* @param servletContext: 代表当前web应用的ServletContext,一个web应用相当于一个ServletContext* @throws ServletException*/@Overridepublic void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {System.out.println("感兴趣的类型"+set);for (Class<?> aClass : set) {System.out.println(aClass);}// 这里我们向容器中注册监听组件和过滤组件 ⭐⭐// (1).添加Servlet组件 -> 具体的映射请求 ⭐⭐⭐ServletRegistration.Dynamic userServlet = servletContext.addServlet("UserServlet", new UserServlet());// 配置要映射请求的路径userServlet.addMapping("/user");// (2).过滤器⭐⭐⭐FilterRegistration.Dynamic userFilter = servletContext.addFilter("UserFilter", UserFilter.class);// 配置映射的信息userFilter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST),true,"/*"); // 请求方式 是否为true 映射路径// (3).添加监听器⭐⭐⭐servletContext.addListener(UserListener.class);}
}
4.SpringMVC整合分析
1.web容器在启动的时候,会扫描每个jar包下的src/main/webapp/META-INF/services/javax.servlet.ServletContainerInitializer的全限定名
2.根据全限定名加载这个文件指定的类
3.Spring的应用一启动会加载感兴趣的WebApplicationInitializer下的所有组件
4.并且为WebApplicationInitializer组件创建对象(组件不是接口,不是抽象类)(1).AbstractContextLoaderInitializer 创建跟容器,createRootApplicationContext();(2).AbstractDispatcherServletInitializer 1.创建一个web的ioc容器 createServletApplicationContext()2.创建一个DispatchServlet createDispatchServlet()3.将创建的DispatchServlet添加到ServletContext中(3). AbstractAnnotationConfigDispatcherServletInitializer注解方式配置的DispatcherServlet初始化器 1.创建更容器: createRootApplicationContext()getRootConfigClasses(); 传入一个配置类2.创建web的ioc容器 createServletApplicationContext()
总结:以注解的方式启动SpringMvc,继承AbstractAnnotationConfigDispatcherServletInitializer实现抽象发发指定DipatcherServlet的配置信息
5.Servlet 异步
(1).AsyncContext 实现异步处理
有时候我们在处理一个数据的时候,会有相对应的时间限制。在实际的用户体验中可能会产生很糟糕的效应。所以我们要使用异步的方式来减少时间差。
package com.jsxs.servlet;import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** @Author Jsxs* @Date 2023/8/27 16:10* @PackageName:com.jsxs.servlet* @ClassName: HelloAsyncServlet* @Description: TODO* @Version 1.0*/@WebServlet(value = "/async",asyncSupported = true) // 设置映射的的路径和支持异步 ⭐
public class HelloAsyncServlet extends HttpServlet {@Overrideprotected void doGet(final HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {final AsyncContext asyncContext = req.startAsync(); // 2.开启异步模式 ⭐⭐System.out.println("主线程----------------------");// 3.通过使用线程的方式进行异步处理 ⭐⭐⭐asyncContext.start(new Runnable() {@Overridepublic void run() {System.out.println("hello-------------------------------------");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}// 4. 通知异步处理完毕 ⭐⭐⭐⭐asyncContext.complete();// 5.获取到异步的上下文 ⭐⭐⭐⭐⭐AsyncContext asyncContext1 = req.getAsyncContext();// 6.获取响应 ⭐⭐⭐⭐⭐⭐ServletResponse response = asyncContext1.getResponse();try {response.getWriter().write("hello async...");} catch (IOException e) {e.printStackTrace();}}});}
}
(2).Callable 实现异步处理
package com.jsxs.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;import java.util.concurrent.Callable;/*** @Author Jsxs* @Date 2023/8/27 16:37* @PackageName:com.jsxs.controller* @ClassName: AsyncController* @Description: TODO* @Version 1.0*/
@Controller
public class AsyncController {@RequestMapping("/async")@ResponseBodypublic Callable<String> async01(){System.out.println("主线程开始"+System.currentTimeMillis());Callable<String> callable=new Callable<String>(){@Overridepublic String call() throws Exception {System.out.println("副线程开始A"+System.currentTimeMillis());Thread.sleep(2000);System.out.println("副线程结束B"+System.currentTimeMillis());return "Callabled";}};System.out.println("主线程结束"+System.currentTimeMillis());return callable;}
}
(3).DeferredResult 实现异步
我们以创建订单为例,创建订单的请求一进来,应用1就要启动一个线程,来帮我们处理这个请求。如果假设应用1并不能创建订单,创建订单需要应用2来完成,那么此时应该怎么办呢?应用1可以把创建订单的消息存放在消息中间件中,比如RabbitMQ、Kafka等等,而应用2就来负责监听这些消息中间件里面的消息,一旦它发现有创建订单这个消息,那么它就进行相应处理,然后将处理完成后的结果,比如订单的订单号等等,再次存放在消息中间件中,接着应用1再启动一个线程,例如线程2,来监听消息中间件中的返回结果,只要订单创建完毕,它就会拿到返回的结果(即订单的订单号),最后将其响应给客户端。
1.创建订单
package com.meimeixia.controller;import java.util.concurrent.Callable;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;@Controller
public class AsyncController {@ResponseBody@RequestMapping("/createOrder")public DeferredResult<Object> createOrder() {/** 在创建DeferredResult对象时,可以像下面这样传入一些参数哟!* * 第一个参数(timeout): 超时时间。限定(请求?)必须在该时间内执行完,如果超出时间限制,那么就会返回一段错误的提示信息(timeoutResult)* 第二个参数(timeoutResult):超出时间限制之后,返回的错误提示信息*/DeferredResult<Object> deferredResult = new DeferredResult<>((long)3000, "create fail..."); ⭐return deferredResult;}@ResponseBody@RequestMapping("/async01")public Callable<String> async01() {System.out.println("主线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis());Callable<String> callable = new Callable<String>() {@Overridepublic String call() throws Exception {System.out.println("副线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis());Thread.sleep(2000); // 我们来睡上2秒 System.out.println("副线程结束..." + Thread.currentThread() + "==>" + System.currentTimeMillis());// 响应给客户端一串字符串,即"Callable<String> async01()"return "Callable<String> async01()";}};System.out.println("主线程结束..." + Thread.currentThread() + "==>" + System.currentTimeMillis());return callable;}}
很明显这是超出时间限制了,应该是相当于创建订单失败了吧!那接下来,我们应该怎么办呢?真令人头疼😭
OK,我们不妨来模拟一个队列。首先新建一个类,例如DeferredResultQueue,如下所示。
2.模拟队列
package com.meimeixia.service;import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;import org.springframework.web.context.request.async.DeferredResult;public class DeferredResultQueue {// DeferredResult对象临时保存的地方private static Queue<DeferredResult<Object>> queue = new ConcurrentLinkedQueue<DeferredResult<Object>>(); ⭐// 临时保存DeferredResult对象的方法 ⭐⭐public static void save(DeferredResult<Object> deferredResult) {queue.add(deferredResult); }// 获取DeferredResult对象的方法 ⭐⭐⭐public static DeferredResult<Object> get() {/** poll():检索并且移除,移除的是队列头部的元素*/return queue.poll();}}
然后,修改一下AsyncController中的createOrder方法。上面我也已经说过了,该方法并不能真正地来处理创建订单的请求。即使如此,那也没关系,因为我们可以在该方法中先把new出来的DeferredResult对象临时保存起来。
3.将正在创建中的订单临时保存起来
package com.meimeixia.controller;import java.util.concurrent.Callable;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;import com.meimeixia.service.DeferredResultQueue;@Controller
public class AsyncController {@ResponseBody@RequestMapping("/createOrder")public DeferredResult<Object> createOrder() {/** 在创建DeferredResult对象时,可以像下面这样传入一些参数哟!* * 第一个参数(timeout): 超时时间。限定(请求?)必须在该时间内执行完,如果超出时间限制,那么就会返回一段错误的提示信息(timeoutResult)* 第二个参数(timeoutResult):超出时间限制之后,返回的错误提示信息*/DeferredResult<Object> deferredResult = new DeferredResult<>((long)3000, "create fail...");DeferredResultQueue.save(deferredResult); ⭐// 保存起来return deferredResult;}@ResponseBody@RequestMapping("/async01")public Callable<String> async01() {System.out.println("主线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis());Callable<String> callable = new Callable<String>() {@Overridepublic String call() throws Exception {System.out.println("副线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis());Thread.sleep(2000); // 我们来睡上2秒 System.out.println("副线程结束..." + Thread.currentThread() + "==>" + System.currentTimeMillis());// 响应给客户端一串字符串,即"Callable<String> async01()"return "Callable<String> async01()";}};System.out.println("主线程结束..." + Thread.currentThread() + "==>" + System.currentTimeMillis());return callable;}}
当然了,实际开发中应该有一个别的线程来专门监听这个事,啥事?我猜应该是在其他的地方临时保存DeferredResult对象这件事吧!不过,我们在这儿并不会这样做,而是再在AsyncController中编写一个方法,例如create,该方法才是真正来处理创建订单的请求的。
4.真正创建订单号(监听)
@ResponseBody
@RequestMapping("/create")
public String create() {// 在这模拟创建订单String order = UUID.randomUUID().toString();/** 如果我们想在上一个请求(即createOrder)中使用订单,那么该怎么办呢?从临时保存DeferredResult对象的地方获取* 到刚才保存的DeferredResult对象,然后调用其setResult方法设置结果,例如设置订单的订单号*/DeferredResult<Object> deferredResult = DeferredResultQueue.get(); // ⭐deferredResult.setResult(order); // 设置结果// 这儿给客户端直接响应"success===>订单号"这样的字符串,不要再跳转页面了return "success===>" + order;
}
至此,可以看到,只要客户端发送一个createOrder
请求进来创建订单,那么服务端就会先将new出来的DeferredResult对象临时保存起来。等到create方法触发并把订单号设置进去之后,在createOrder方法中就会立即得到返回结果,即订单号。
最后,我们来重启项目进行测试。重启成功之后,先来访问createOrder请求,以便来创建订单,但是订单必须得在3秒内创建完,所以一旦访问了createOrder请求后,你必须立即访问create请求来真正创建订单,而且至少得在3秒内完成。
这时,你会看到什么结果呢?可以看到访问create请求之后,直接给浏览器页面响应了一个success===>4e7e3e4c-27d2-4989-87c1-00d545e05feb这样的字符串,其中4e7e3e4c-27d2-4989-87c1-00d545e05feb就是所创建订单的订单号,如下图所示。
切到访问createOrder请求的浏览器窗口之后,你也可以在浏览器页面中看到所创建订单的订单号,即4e7e3e4c-27d2-4989-87c1-00d545e05feb,如下图所示。