随手记录第九话 -- Java框架整合篇
框架莫过于Spring了,那就以它为起点吧。
本文只为整理复习用,详细内容自行翻看以前文章。
1.Spring
有人说是Spring成就Java,其实也不是并无道理。
1.1 Spring之IOC控制反转
以XML注入bean的方式为入口,定位、加载、注册,最后将XML中bean标签解析成封装类BeanDefinitionHolder,将解析得到的封装类BeanDefinitionHold注册到IOC容器
//入口,通过ClassPathXmlApplicationContext的构造方法一直到此方法
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {super(parent);//存储我们传入的配置文件路径setConfigLocations(configLocations); if (refresh) {//整个IOC容器的入口refresh(); }
}//最终 beanName -> class类信息的封装BeanDefinition
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
将解析好的bean标签封装成BeanDefinition,以beanName作为key,存储在本地变量beanDefinitionMap中,但是到目前为止并没有实例化这些类,还不能直接使用。
1.2 Spring之DI依赖注入
AbstractApplicationContext##refresh()
下的finishBeanFactoryInitialization()#beanFactory.preInstantiateSingletons()
中通过遍历beanDefinitionNames
调用getBean()
实例化对象,让代码解耦统一走getBean()
更加的规范,完美解决循环依赖(程序初始化遍历一次,自己调用一次)- 从上面代码看到依赖注入就如同递归,A依赖B,B依赖C,加载A的时候会去实例化B,实例化B的时候会去实例化C,直到最后没有依赖才算完结
- 通过Filed反射调用的方式进行注入
1.3 Spring之AOP切面编程
在容器初始化bean之后,回调到后置通知方法,aop正好实现后置通知方法,经过判断如果满足当前的配置切点则生成代理类并返回到IOC容器中。
创建代理类时会把原始类、调用链路等信息通过构造方法保存下来,在创建动态代理时newInstance
时传入的回调handler
类也是this
,那么在调用该代理类时会到handler
类的invoke
方法
在invoke方法中通过递归以及增加下标的方法使调用链路执行反射调用,如果不能正常匹配到对应的Advice类,则在到最后一个时直接反射调用原始类并返回
如果能正常匹配到,例如上面的AspectJAfterAdvice则是先反射调用原始类,然后再调用后置切面方法,整个流程结束
1.4 SpringMvc
在依赖注入之后,在AOP之前实现,在spring-mvc包下找到RequestMappingHandlerMapping这个类,通过Springboot的自动装配机制初始化该类,该类实现了InitializingBean
接口,实现了afterPropertiesSet方法,以此为入口。
接下来开始初始化mapping的入口,判断只有加了@Controller || @RequestMapping
这两个注解的才符合条件,接下来获取方法上面的注解内容组成请求路径。最终存储位置:
//保存注册信息的MAP
//this.registry.put(mapping,
//new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
//this.pathLookup.add(path, mapping); 路径对应mapping
private final MultiValueMap<String, T> pathLookup = new LinkedMultiValueMap<>();
以上为Springboot中自动装配流程。
在MVC中直接开始进入运行阶段,通过Servlet的实现类DispatcherServlet
类里找init方法(在父类GenericServlet#init()
),然后创建web容器会同步配置和刷新容器(IOC的入口),在完成Spring的IOC,DI之后,才会走初始化Mapping。
也就是说MVC是从DispatcherServlet#Init开始的,而Springboot是从自动装配先初始化IOC容器开始的。
在init初始化容器之后,才会进入初始化MVC的组件,例如:handlerMapping、参数适配、视图预处理等等,在handlerMapping中取出对应类型的bean并存储在本地变量List<HandlerMapping> handlerMappings
。
开始调用阶段,Servlet的入口肯定是自己实现的doGet/doPost方法,最终在FrameworkServlet
找到,通过里面的getHandler() -> getHandlerInternal() -> initLookupPath
方法根据请求路径取出对应的handlerMethod beanName和Method
,最后在通过registry
循序获取mapping的注册信息,再通过注册信息里面的handler 调用bean工厂中的getBean获取到实例对象。
拿到对应路径的实例化对象后,通过invoke调用到原始类中的方法,回去返回值后通过ModelAndView写入到Response中返回到前端页面。
1.5 Spring扩展
1.5.1 首先看一下生命周期
扩展接口 | 作用 |
---|---|
BeanFactoryPostProcessor | 处理bean之前对beanFactory进行预先处理 |
BeanDefinitionRegistryPostProcessor | 自定义添加bean |
BeanPostProcessor | 在初始化Bean前后的回调 |
ApplicationContextAware | 获得上下文 |
InitializingBean | bean创建完成,所有属性注入完成后执行 |
DisposableBean | 在bean销毁前执行 |
ApplicationListener | 事件监听 |
1.5.2 Spring中的Aware接口实现
如果一个Bean实现了Aware接口,则能在bean中获取相应的Spring资源。
Aware接口 | set属性 | 作用 |
---|---|---|
BeanNameAware | setBeanName | 获得当前类在容器中的beanName |
BeanClassLoaderAware | setBeanClassLoader | 获得当前的类加载器 |
BeanFactoryAware | setBeanFactory | 获得bean工厂以获取其他的bean |
EnvironmentAware | setEnvironment | 获得当前运行的环境相关 |
EmbeddedValueResolverAware | setEmbeddedValueResolver | EL解析表达式 |
ResourceLoaderAware | setResourceLoader | 获得加载bean的资源加载器 |
ApplicatioinEventPublisherAware | setApplicatioinEventPublisher | 获得时间发布者 |
MessageSourceAware | setMessageSource | 获取国际化信息 |
ApplicationContextAware | setApplicationContext | 获取上下文 |
ServletContextAware | setServletContext | Servlet的上下文 |
1.5.3 构造方法初始化对象
Class.newInstance()
1.5.4 setter依赖注入
1.5.5 初始化类的回调 BeanNameAware.setBeanName
获取当前实现类的beanName,准备如下代码,在初始化当前类时,会将beanName回调到该方法来
@Component
public class TestBeanNameAware implements BeanNameAware {@Overridepublic void setBeanName(String name) {System.out.println("#获得TestBeanNameAware实现类的beanName:"+name);}
}
1.5.6 获取当前加载类的ClassLoader BeanClassLoaderAware.setBeanClassLoader
@Component
public class TestBeanClassLoaderAware implements BeanClassLoaderAware {@Overridepublic void setBeanClassLoader(ClassLoader classLoader) {System.out.println("#获得当前加载类的classLoader:"+classLoader);}
}
1.5.7 获取Bean工厂 BeanFactoryAware.setBeanFactory
可以通过该bean工厂获取指定名称的对象
@Component
public class TestBeanFactoryAware implements BeanFactoryAware {@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {System.out.println("#获得管理bean的容器:"+beanFactory.getClass().getName());//Object nameAware = beanFactory.getBean("testBeanNameAware");System.out.println(nameAware);}
}
1.5.8 初始化bean的回调BeanPostProcessor.postProcessBeforeInitialization 、postProcessAfterInitialization
aop就是典型的例子,在初始化bean前后回调,可以在回调中返回继承类或者代理类。
@Component
public class TestInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// System.out.println("初始化之前:"+bean.getClass().getName()+","+beanName);if(beanName.equals("com.example.demo.action.TestAction")){System.out.println("初始化之后:"+bean.getClass().getName()+","+beanName);return new Test1Action(); //设置成实现类或者继承类 动态修改bean的初始化}return null;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// System.out.println("初始化之后:"+bean.getClass().getName()+","+beanName);if(beanName.equals("com.example.demo.action.TestAction")){System.out.println("初始化之后:"+bean.getClass().getName()+","+beanName);}return null;}
}
1.5.9 初始完当前类的回调 InitializingBean.afterPropertiesSet
例如MVC的HandlerMapping回调
@Component
public class TestInitializingBean implements InitializingBean {@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("#初始化完毕当前类回调,后置回调之前");}
}
1.5.10 ApplicationContextAware 获得容器上下文,比较常用,不细说了
1.5.11 自定义注册bean或者属性BeanDefinitionRegistryPostProcessor
实例:Mybatis
@Component
public class TestBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {/*** 实现自定义bean并注册到BeanDefinitionRegistry* @param registry* @throws BeansException*/@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {//TestAction 无任何加载的标识 可以用这种方法向容器注册 mybatis里面用到BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(TestAction.class);registry.registerBeanDefinition(TestAction.class.getName(),builder.getBeanDefinition());System.out.println("注册自定义bean:"+builder.getBeanDefinition().getBeanClass());}/*** 主要是用来自定义修改持有的bean里面的属性值* @param beanFactory* @throws BeansException*/@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {for (String definitionName : beanFactory.getBeanDefinitionNames()) {if ("com.example.demo.action.TestAction".equals(definitionName)) {System.out.println("###beanName:" + definitionName);BeanDefinition definition = beanFactory.getBeanDefinition(definitionName);//获取bean的定义的值 动态修改值 需要有setter方法MutablePropertyValues pv = definition.getPropertyValues();pv.addPropertyValue("name", "王二麻子");System.out.println("###TestAction#name重新赋值");break;}}}
}
1.5.12 初始化调用工厂bean回调FactoryBean.getObject(), getObjectType()
实现了FactoryBean的类,容器初始化该类的时候会调用当前getObject()方法
//也可自己注册 初始该注册类时也会调用到该方法
BeanDefinitionBuilder builder1 = BeanDefinitionBuilder.genericBeanDefinition(TestFatoryBean.class);
registry.registerBeanDefinition("aaabbbccc",builder1.getBeanDefinition());
@Component
public class TestFatoryBean implements FactoryBean {/*** 初始化这个类的时候 TestFatoryBean的定义就已经被替换成了 TestFactoryBean1 这个类* 在其他类注解TestFactoryBean1 就可以直接用* @return 返回的对象实例* @throws Exception*/@Overridepublic Object getObject() throws Exception {System.out.println("#初始化TestFatoryBean.getObject()");return new TestFactoryBean1();}/*** @return 返回的对象类型*/@Overridepublic Class<?> getObjectType() {System.out.println("#初始化TestFatoryBean.getObjectType()");return TestFactoryBean1.class;}
}
1.5.13 容器相关事件回调AbstractApplicationContext
@Component
public class TestAbstractApplicationContext extends AbstractApplicationContext {@Overrideprotected void refreshBeanFactory() throws BeansException, IllegalStateException {System.out.println("#刷新容器事件");}@Overrideprotected void closeBeanFactory() {System.out.println("#关闭容器事件");}/*** 返回已有容器 || 自定义的容器* @return* @throws IllegalStateException*/@Overridepublic ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException {return null;}
}
1.5.14 自定义需要导入的类DeferredImportSelector extends ImportSelector
- 自定义需要导入的类,传入类名list,自动装配用
- 需要用到其他的类,注解@Import(TestDeferredImportSelector.class) 使用
第一种用法:
public class TestDeferredImportSelector implements ImportSelector {//直接实现 传入类的包名路径@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {System.out.println("#延时引入需要加载的importingClassMetadata");return new String[]{TestImportAction.class.getName()};}
}
第二种用法,可按分组实现:
public class TestDeferredImportSelector implements DeferredImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {return null;}/*** 如果实现了分组 优先按分组来 就是去分组的实现类* @return 返回实现分组的类 (实现DeferredImportSelector.Group)*/@Overridepublic Class<? extends Group> getImportGroup() {return Test123.class;}private static class Test123 implements DeferredImportSelector.Group{AnnotationMetadata annotationMetadata;@Overridepublic void process(AnnotationMetadata metadata, DeferredImportSelector selector) {System.out.println("#延时引入需要加载的process");this.annotationMetadata = metadata;//这里可以根据原数据做分组 让每个分组加载不同的类}@Overridepublic Iterable<Entry> selectImports() {System.out.println("#延时引入需要加载的selectImports");String[] strings = {TestImportAction.class.getName()};return Arrays.asList(strings).stream().map((className) -> new Entry(this.annotationMetadata,className)).collect(Collectors.toList());}}
}
2.Springboot
2.1 自动装配流程概括
- 入口肯定是run方法,参数传入的是当前的启动类
- 在容器初始化之前传入的启动类注册到容器中去,然后再刷新容器refresh()
- 在容器注册阶段完成后,就是将所有的需要加载类注册到BeanDefinitionMap了
- 然后在上下文中调用注册bean的处理器回调,在springboot包中解析bean
- 关注点在注册类,解析该类时得到扫描路径即当前路径 ‘.’ ,也就说明了springboot项目默认扫描到启动类那一层,扫描我们手写的代码后注册到容器,但是所需依赖还是没有向容器注册的
- 启动类上有@Import注解,解析时会一并处理,然后获取import的类的回调来获取需要自动装配的类
- 在回调类中解析需要自动装载的类,通过读取包内文件目录下META-INF/spring.factories的配置中获取对应key的值
- 最终想容器注册,自动装配流程到此完结
2.2 自动装配流程概括
- 通过SpringApplication构造方法时会手动初始化listeners,即
spring.factories
中可以为org.springframework.context.ApplicationListener
的值,最终保存到本地变量List<ApplicationListener<?>> listeners
中 - 初始化事件监听类
EventPublishingRunListener
- 在
EventPublishingRunListener
类中获取到构造方法保存的监听,然后进行回调 - 后面回调到文件处理的监听
ConfigFileApplicationListener
类,然后通过加载配置中PropertySourceLoader
类名的值得到PropertiesPropertySourceLoader,YamlPropertySourceLoader
两个配置解析类 - 最终交由这两个类分别解析properties后缀和yaml后缀的配置文件
2.3 自动装配扩展,手动实现一个starter
自动装配是根据Spring通过classloader获取指定key的值为需要自动装配的类,这里的key值为EnableAutoConfiguration
类的全路径
//实现代码
//AutoConfigurationImportSelector类
//通过classLoader获取指定key需要自动装配的类
List<String> configurations =
//getSpringFactoriesLoaderFactoryClass return EnableAutoConfiguration.class;
//这里的key也就是自动装配EnableAutoConfiguration类路径
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader());
也就是说需要自动转配的类,在META-INF/spring.factories
文件添加key为EnableAutoConfiguration全路径
,值为需要自动装配的类路径即可。
- 在新建项目中添加两个类,在
resources
目录下准备META-INF/spring.factories
,写入下面内容
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.start.test2.service.HelloService,\
com.start.test2.service.Hello1Service
然后将build打包,保证打包能够成功!
- 在其他Springboot项目引入该Jar包
//采用注解的方式使用star-test-2的类@AutowiredHelloService helloService;@AutowiredHello1Service hello1Service;
调用方法能够调用成功,这样就可以实现一个公用的star了。
3 Mybatis
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。
介绍一下作为orm框架使用的升级之路吧
- hibernate 全自动化,但复杂的查询时简直要程序员的命
- ibatis 半自动话,但是存在重复代码和硬编码,不易管理
- mybatis 半自动化,利用接口的动态代理实现,使用至今
3.1 Mybatis配置使用
项目结构
yml配置
server:port: 12233
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.168.0.100:3306/demo_test?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=falseusername: rootpassword: 123456#mybatis xml资源文件扫描
mybatis:mapperLocations: classpath:mapper/**.xml
TestMapper接口
//注解莫忘
@Mapper
public interface TestMapper {void insert(Map<String, Object> map);int update(Map<String, Object> map);List<Map<String, Object>> select(Long id);void delete(Long id);
}
TestMapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.TestMapper"><insert id="insert" parameterType="Map" useGeneratedKeys="true" keyProperty="id">insert into user_info(`name`,`age`,`crad`) values (#{name},#{age},#{crad})</insert>//其他的省略了 后面详细的有贴
</mapper>
调用直接和注解service一样使用
@Resource
TestMapper testMapper;
//testMapper.insert(map);
3.2 源码总结
3.2.1.初始化阶段
1.利用自动装配初始一个类MybatisAutoConfiguration,在静态类AutoConfiguredMapperScannerRegistrar实现了spring的回调,作为扫描mapper接口的入口MapperScannerConfigurer
2.找到的mapper接口注册到容器,将beanClass替换成了MapperFactoryBean类,也就是初始化Mapper接口时实际上在初始化MapperFactoryBean类
3.2.2.运行阶段
1.通过MybatisAutoConfiguration类中@Bean注解创建sqlsessionFactoryBean时扫描mapper文件,将Mapper文件中扫描出来的sql等信息保存到了Configuration#mappedStatements Map里面,将命名空间也就是接口信息保存到了Configuration#MapperRegistry类knownMappers Map中,key为接口全路径,value同时用MapperProxyFactory包装key
2.在MapperFactoryBean中实现了FactoryBean接口,实现了getObject方法,该方法的意思是实例化对象时由自己做主,这里返回的就是上面的MapperProxyFactory的包装类,调用类为MapperProxy。
3.在MybatisAutoConfiguration类中@Bean创建SqlSessionTemplate时,通过构造方法生成了一个代理的sqlSessionProxy,调用类为SqlSessionInterceptor
3.2.3.调用阶段
1.当容器初始化Mapper接口类时实际上得到的是MapperFactoryBean类对象,但该类实现了getObject方法,返回了最终的实例化对象,是一个代理类MapperProxy,也就是mapper接口实际上注入的是MapperProxy代理类
2.也就是会执行到MapperProxy#invoke类,然后在使用sqlSession时实际上是到代理类SqlSessionInterceptor,在该类完成了sqlSession的新建和复用以及事务提交和回滚
3.接下来才到真正的执行器executor,这里可能存在多层代理,插件的执行入口也在这里实现
4.最后在prepareStatement完成数据库连接getConnection,并执行sql,最终由handleResultSets处理结果集并返回
3.3 Mybatis插件
- TypeHandler入参出参实现使用
- Interceptor插件实现使用
3.3.1 TypeHandler类型处理器
表中的字段是字符串,但是Java数据类型是List
//定义处理的泛型为List<String>,这里就处理入参和出参之间的转换了
@Slf4j
@Component
public class MyTypeHandler implements TypeHandler<List<String>> {@Overridepublic void setParameter(PreparedStatement ps, int i, List<String> parameter, JdbcType jdbcType) throws SQLException {log.info("set ========= " + parameter);//这里采用偷懒的写法了ps.setString(i,parameter.toString().replaceAll("\\[|\\]",""));}@Overridepublic List<String> getResult(ResultSet rs, String columnName) throws SQLException {log.info("getResult == String");String string = rs.getString(columnName);//出参处理List<String> list = Arrays.asList(string.split(","));return list;}@Overridepublic List<String> getResult(ResultSet rs, int columnIndex) throws SQLException {log.info("getResult == columnIndex");return null;}@Overridepublic List<String> getResult(CallableStatement cs, int columnIndex) throws SQLException {log.info("getResult == columnIndex");return null;}
}
需要注意的点
xml中入参写法别忘记,#{bookNames,typeHandler=com.example.demo.plugin.MyTypeHandler}
xml中出参映射resultMap不能忘,<result column="book_names" property="bookNames" typeHandler="com.example.demo.plugin.MyTypeHandler"/>
3.3.2 Interceptor拦截插件
在很多业务中,我们可能需要去拦截执行的sql,达到不修改原有的代码业务去处理一些东西。例如:分页操作,数据权限,sql执行次数和时长等待,这时就可以用到这个Interceptor拦截器了。
回顾一下核心对象
Configuration 初始化基础配置,一些重要的类型对象,例如:插件,映射器,factory工厂,typeHandler对象等等。该类贯穿全局
SqlSessionFactory SqlSession工厂
SqlSession 顶层工作API,和数据库交互,完成CRUD功能
Executor 执行器,是mybatis调度的核心,负责SQL语句的生成和查询缓存的维护
StatementHandler 封装了JDBC Statement操作,负责对应操作,例如:设置参数,结果集转出
ParameterHandler 参数转换处理
MapperedStatement 对执行sql节点的封装
SqlSource 动态生成sql语句,并封装到BoundSql中
BoundSql 动态生成的sql和参数的封装
ResultSetHandler 结果集处理,将JDBC类型转换成Java数据类型
TypeHandler 类型转换器,可自定义实现
Mybatis支持对Executor、StatementHandler、ParmeterHandler和ResultSetHandler接口进行拦截,也就是会对这几个接口进行代理。例如实现查询拦截器
@Slf4j
@Component
@Intercepts(value = {@Signature(type = Executor.class, method = "query",//arg对应的参数数组args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class MyPagePlugin implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {log.info("进入插件 ================");Object target = invocation.getTarget();log.info("原始类来源:{}", target.getClass().getName());Object[] args = invocation.getArgs();MappedStatement st = (MappedStatement) args[0];BoundSql sql = st.getBoundSql(args[1]);log.info("执行SQL:" + sql.getSql());Object arg = args[1];log.info("传入参数:{}", arg);return invocation.proceed();}
}
那么以后查询统计就可以用插件实现了,而不是用切面来了!
3.4 Mybatis插件之pagehelper
用过就知道,最便捷最简单的物理分页插件了。
3.4.1 使用
Java查询代码
@GetMapping("/data/select")
public Object select() {//配置页码参数PageHelper.startPage(1,1);Map<String, Object> map = new HashMap<>();//查询List<Map<String, Object>> mapList = testMapper.select(map);PageHelper.clearPage();return mapList;
}
sql语句
select * from user_info
日志打印
==> Preparing: SELECT count(0) FROM user_info
<== Columns: count(0)
<== Row: 4
<== Total: 4
==> Preparing: select * from user_info LIMIT ?
==> Parameters: 1(Integer)
上述日志可以看到,先会执行一条count的sql,然后才会执行真正手写的sql,并且还带了limit查询。
这里需要注意常犯的错,PageHelper.startPage(0, 10);
/*** 计算起止行号*/
private void calculateStartAndEndRow() {//例如 1,10 -> (1-1)*10 =0this.startRow = this.pageNum > 0 ? (this.pageNum - 1) * this.pageSize : 0;//例如 1,10 -> 0+ 10 * 1=10 //但是如果是0 0+10*0= 0 最终执行的sql就是limit 0,0this.endRow = this.startRow + this.pageSize * (this.pageNum > 0 ? 1 : 0);}
这块谨记,PageHelper分页插件的页码是从1开始的!!!
3.4.2 Pagehelper代码织入
回顾一下mybatis的调用流程,从我们自己手写的xxxMapper接口开始
MapperProxy#invoke ->
内部类PlainMethodInvoker#invoke ->
MapperMethod#execute ->
SqlSessionTemplate#selectList->
内部代理类SqlSessionInterceptor#invoke(创建新的sqlseesion,事务的自动提交在这里控制,执行器插件织入均在这里初始化) ->
DefaultSqlSession#selectList ->
BaseExecutor#query(此处executor可被插件代理,甚至多层) ->
到对应的执行器例如SimpleExecutor#doQuery(预处理参数prepareStatement->
PreparedStatementHandler#parameterize中的parameterHandler可被多层代理) ->
SimpleStatementHandler#query(随执行器而定,可被多层代理) ->
DefaultResultSetHandler#handleResultSets(ResultSetHandler可被代理) ->
返回list
可被代理的 Executor,ParameterHandler,StatementHandler,ResultSetHandler均可被插件代理,本篇文章重点分析的是Executor
在pagehelper-spring-boot-autoconfigure-1.4.0.jar/META-INF/spring.factories
找到如下内容
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.github.pagehelper.autoconfigure.PageHelperAutoConfiguration
在PageHelperAutoConfiguration
这个类初始化interceptor插件并保存下来。
在Plugin#getSignatureMap
源码解析中有如下插件
//对应这个注解内容
@Intercepts(value = {@Signature(type = Executor.class, method = "query",args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
最终对boundSql的拦截处理,生成一个新的BoundSql即countBoundSql,执行count查询,如果查询结果为0,那就根据配置是否决定继续查询数据list。
这里记录一下值得注意的几个点:
page==0 || pageSize==0
只会执行count查询,分页查询不会查询。- 但是如果设置
pageSizeZero=true
那就继续查询。
基于springboot的自动装配机制,先扫描启动类目录下的class类,再是自动装配类EnableAutoConfiguration
配置下的类初始化,手写开发的查询插件先初始化PageInterceptor分页插件,在InterceptorChain#pluginAll
最后封装的是PageInterceptor分页插件,然后分页最终执行的返回并不是invocation.proceed()
,也就是手写的查询插件会失效掉。
4.Netty
Netty是一个基于异步、事件驱动的网络应用程序框架,用于快速开发高性能、高可靠性的网络IO程序,是目前最流行的NIO框架。Netty在互联网领域,大数据分布式计算,游戏行业,通信IM行业获得了广泛的应用。
Dubbo、RocketMQ、Tomcat等内部都采用了Netty。
4.1 Netty的简单使用
4.1.1 服务端
public static void main(String[] args) {NioEventLoopGroup b1 = new NioEventLoopGroup();NioEventLoopGroup b2 = new NioEventLoopGroup();ServerBootstrap bs = new ServerBootstrap().group(b1, b2).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel channel) throws Exception {ChannelPipeline pipeline = channel.pipeline();//使用自带的String编解码器pipeline.addLast(new StringEncoder());pipeline.addLast(new StringDecoder());//服务端业务处理的handlerpipeline.addLast(new MyChatServerHandler());}});ChannelFuture f = bs.bind(19900).addListener(future -> {if (future.isSuccess()) {System.out.println("已启动netty服务:" + 19900);}});try {f.channel().closeFuture().sync();} catch (InterruptedException e) {b1.shutdownGracefully();b2.shutdownGracefully();}}
4.1.2 服务端业务处理类
public class MyChatServerHandler extends SimpleChannelInboundHandler<String> {//保存长链接的channel通道static Map<ChannelId, Channel> channelMap = new ConcurrentHashMap<>();/*** 协议 name#msg*/@Overrideprotected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {System.out.println("服务端收到消息:" + msg);String[] split = msg.split("#");if (split.length == 1) {//输了昵称才算新加入的if (!channelMap.containsKey(ctx.channel().id())) {channelMap.put(ctx.channel().id(), ctx.channel());}}//消息群发SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String dateStr = sdf.format(new Date());for (Map.Entry<ChannelId, Channel> entry : channelMap.entrySet()) {if (split.length == 1) {entry.getValue().writeAndFlush(dateStr + ":欢迎" + msg + "加入群聊!");} else {entry.getValue().writeAndFlush(dateStr + ":" + msg.replace("#", " : "));}}}@Overridepublic void handlerAdded(ChannelHandlerContext ctx) throws Exception {System.out.println("新的客户端连接" + ctx.channel());}
}
4.1.3 客户端
public static void main(String[] args) {//客户端只需要一个事件分组NioEventLoopGroup group = new NioEventLoopGroup();//构建的类也不同Bootstrap bs = new Bootstrap().group(group)//选择的channel也不同.channel(NioSocketChannel.class).handler(new ChannelInitializer<Channel>() {@Overrideprotected void initChannel(Channel channel) throws Exception {ChannelPipeline pipeline = channel.pipeline();//handler处理链是一致的pipeline.addLast(new StringEncoder());pipeline.addLast(new StringDecoder());pipeline.addLast(new MyChatClientHandler());}});//连接到服务端的指定端口ChannelFuture future = bs.connect("127.0.0.1", 19900);try {future.channel().closeFuture().sync();} catch (InterruptedException e) {group.shutdownGracefully();}}
4.1.3 客户端业务处理
public class MyChatClientHandler extends SimpleChannelInboundHandler<String> {//保存第一次连接成功输入的昵称static String name;@Overrideprotected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {//打印消息System.out.println(msg);}/*** 协议 name#msg*/@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {System.out.println("与服务端连接成功:" + ctx.channel() + ",请输入你的昵称:");//启动一个发送消息线程 只要键盘输入过就发送到服务端new Thread(() -> {Scanner sc = new Scanner(System.in);while (true) {String next = sc.nextLine();if (name == null) {//第一次发送则初始化昵称 协议仅供理解,硬是要输昵称带#的忽略name = next;ctx.writeAndFlush(name);} else {//发送规定协议的报文ctx.writeAndFlush(name + "#" + next);}}}).start();}
}
接下来就可以启动一个服务端,多个客户端来任意聊天了。
上述内容仅供了解使用,正式开发下肯定是要考虑多台机器的,可能channel不在同一台机,这个时候就要使用到分布式中间件了。
4.1 RPC介绍
RPC一般指远程过程调用。 RPC是远程过程调用(Remote Procedure Call)的缩写形式。
首先看下服务的演变过程:
- 单一应用架构 -> MVC三层架构 -> PRC分布式服务 -> 弹性计算架构
接口请求也在慢慢演变:
- TCP/IP报文协议 -> RMI(仅JAVA可用) -> WebService ->HTTP -> GPRC(Thrift,Dubbo) ->SpringRestful(路径风格)
总体而言就是随着服务的增多,也伴随着服务之间的调用频繁和繁琐,这就有了PRC这代名词。
PRC普通应用在分布式架构中,先看下分布式服务派系
- 阿里系:dubbo zookeeper nginx
- spring生态:cloud eureka gateway
RPC的核心职能,以dubbo图解为例
这个机制现在用的很广泛了,例如cloud中的注册中心和配置中心。
大概了解一下理论后,接下来我们用代码来实操,以便更深入的认识PRC。
4.2 RPC实现原理
-
客户端
1.通过bean的初始化回调判断是否需要注入动态代理
2.在动态代理回调类中使用Netty调用远程服务,并发送约定协议的消息
3.使用回调机制返回服务端响应,并返回原始类 -
服务端
1.在bean的回调判断是否为发布的服务,是的话保存在公共map中,初始化时启动Rpc服务
2.调用服务解析消息后,通过请求的service获取指定的service,通过反射调用,并将结果返回 -
关于Rpc服务地址
正常的RPC服务,会先从注册中心获取这个服务发布的地址,也就是我们配置中的地址实际上是注册中心的地址
建立连接后,应该会保持心跳,第二次调用不再重新建立连接 -
关于阻塞异步回调
实际上还有熔断机制,应该处理掉一直等待的回调
5 SpringCloud
基于SpringBoot的全家桶,只了解过部分源码,基本上大同小异核心思想是一样的,没有文章记录了,使用可以看看以前的文章。
上一篇:随手记录第八话 – Java基础整合篇
下一篇:随手记录第十话 – xxx
非学无以广才,非志无以成学。
相关文章:

随手记录第九话 -- Java框架整合篇
框架莫过于Spring了,那就以它为起点吧。 本文只为整理复习用,详细内容自行翻看以前文章。 1.Spring 有人说是Spring成就Java,其实也不是并无道理。 1.1 Spring之IOC控制反转 以XML注入bean的方式为入口,定位、加载、注册&…...

电影《铃芽之旅》观后感
这周看了电影《铃芽之旅》,整部电影是新海诚的新作。电影讲述的是女主铃芽为了关闭往门,在日本旅行中,遭遇灾难的故事。 (1)往昔记忆-往昔之物 电影中,有很多的“往门”,换成中国的话说…...

Web自动化测试(二)(全网最给力自动化教程)
欢迎您来阅读和练手!您将会从本章的详细讲解中,获取很大的收获!开始学习吧! 2.4 CSS定位2.5 SeleniumBuilder辅助定位元素2.6 操作元素(键盘和鼠标事件) 正文 2.4 CSS定位 前言 大部分人在使用selenium定…...
【C语言经典例题!】逆序字符串
目录 一、题目要求 二、解题步骤 ①递归解法 思路 完整代码 ②循环解法 思路 完整代码 嗨大家好! 本篇博客中的这道例题,是我自己在一次考试中写错的一道题 这篇博客包含了这道题的几种解法,以及一些我自己对这道题的看法ÿ…...

21 - 二叉树(三)
文章目录1. 二叉树的镜像2. 判断是不是完全二叉树3. 完全二叉树的节点个数4. 判断是不是平衡二叉树1. 二叉树的镜像 #include <ctime> class Solution {public:TreeNode* Mirror(TreeNode* pRoot) {// write code hereif (pRoot nullptr) return pRoot;//这里记得要记得…...

【A-Star算法】【学习笔记】【附GitHub一个示例代码】
文章目录一、算法简介二、应用场景三、示例代码Reference本文暂学习四方向搜索,一、算法简介 一个比较经典的路径规划的算法 相关路径搜索算法: 广度优先遍历(BFC)深度优先遍历(DFC)Di jkstra算法&#…...
纽扣电池澳大利亚认证的更新要求
澳大利亚强制性安全和信息标准草案具体规定了对含有纽扣电池和纽扣电池以 及纽扣电池和纽扣电池本身的消费品的要求, 适用范围 1.本法规适用于: 纽扣锂电池(任何尺寸和类型); 直径为16毫米或以上的纽扣锂电池: 一起提供的纽扣电池(未预先安装在产品中)。 2.但是&…...

零代码零距离,明道云开放日北京站圆满结束
文/麦壁瑜 编辑/李雨珂 2023年3月17日,为期一天的明道云开放日北京站圆满结束。本次开放日迎来超过100名伙伴和客户现场参会,其中不乏安利、通用技术集团、民生银行、迈外迪、DELSK集团、中国人民养老保险、北京汽车等知名企业代表。北京大兴机场、作业…...

第五章Vue路由
文章目录相关理解vue-router的理解对SPA应用的理解路由的理解基本路由几个注意点嵌套路由——多级路由路由query参数命名路由路由的params参数路由的props配置路由跳转的replace方法编程式路由导航缓存路由组件路由组件独有的生命钩子activated和deactivated路由守卫全局路由守…...

Git常用指令
Git是什么: Git是分布式版本控制系统(Distributed Version Control System,简称 DVCS),分为两种类型的仓库: 本地仓库和远程仓库 第一步先新建仓库,本地 init ,然后提交分枝 链接仓库…...

Java每日一练(20230329)
目录 1. 环形链表 II 🌟🌟 2. 基础语句 ※ 3. 最小覆盖子串 🌟🌟🌟 🌟 每日一练刷题专栏 🌟 Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 1. 环形…...

【面试题】JS的一些优雅写法 reduce和map
大厂面试题分享 面试题库 前后端面试题库 (面试必备) 推荐:★★★★★ 地址:前端面试题库 web前端面试题库 VS java后端面试题库大全 JS的一些优雅写法 reduce 1、可以使用 reduce 方法来实现对象数组中根据某一key值求和 …...
【蓝桥杯真题】包子凑数(裴蜀定理、动态规划、背包问题)
题意 小明几乎每天早晨都会在一家包子铺吃早餐。他发现这家包子铺有N种蒸笼,其中第i种蒸笼恰好能放Ai个包子。每种蒸笼都有非常多笼,可以认为是无限笼。 每当有顾客想买X个包子,卖包子的大叔就会迅速选出若干笼包子来,使得这若干…...

一种免费将PDF转word的方式
pdf转word的需求对我来说很重要,我经常会有PDF转word的方式,但是网上搜索到的方式,要么收费、要么限制pdf大小或者限制转换次数。这里我分享一种免费转换的方式:用Acrobat Pro 来做转换。Adobe Acrobat Pro拥有强大的功能…...

MyBatis-面试题
文章目录1.什么是MyBatis?2.#{}和${}的区别是什么?3.MyBatis的一级、二级缓存4.MyBatis的优缺点5.当实体类中的属性名和表中的字段名不一样 ,怎么办 ?6.模糊查询like语句该怎么写?7.Mybatis是如何进行分页的?分页插件的原理是什…...

jQuery一些问题和ajax操作
jQuery语法: 文档就绪事件:文档加载之后运行jQuery代码,相当于jQuery的入口函数。 $(document).ready(function(){// 开始写 jQuery 代码...}); 简写: $(function(){// 开始写 jQuery 代码...}); jQuery选择器: …...
Pytorch构建自己的数据集
1.Pytorch内置的Dataset Pytorch中内置了许多数据集,我们可以从torchvision库中进行导入。比如,我们可以导入Fashion-MNIST数据集 import torch from torch.utils.data import Dataset from torchvision import datasets from torchvision.transforms …...
信息论小课堂:纠错码(海明码在信息传输编码时,通过巧妙的信道编码保证有了错误能够自动纠错。)
文章目录 引言I 纠错1.1 信息纠错的前提:信息冗余1.2 发现抄写错误的方法1.3 计算机的信息校验原理:奇偶校验1.4 有效的纠错编码II 案例2.1 例子1:自身DNA的编码2.2 例子2:海明码引言 预则立,不预则废:不确定性是我们这个世界自然的属性,在解决问题之前,要考虑到世界的不…...

MySQL执行计划(explain)
MySQL执行计划(explain) 1.什么是执行计划 2.如何分析执行计划 执行计划一共有12列,每一列都有着特殊的含义,接下来我们逐一分析 id select语句的查询顺序,包含一组数字,如果数字相同则从上到下,如果数字不同则从大到小。 select_type …...

思必驰回复第二轮审核问询,如何与科大讯飞、阿里巴巴“虎口夺食”?
数据智能产业创新服务媒体——聚焦数智 改变商业3月21日,思必驰科技股份有限公司(以下简称“思必驰”)更新上市申请审核动态,已回复上交所第二轮审核问询函,回复了涵盖关于实际控制人的认定、关于预计持续亏损及关于…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...

如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...

2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...

Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
AGain DB和倍数增益的关系
我在设置一款索尼CMOS芯片时,Again增益0db变化为6DB,画面的变化只有2倍DN的增益,比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析: 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...

淘宝扭蛋机小程序系统开发:打造互动性强的购物平台
淘宝扭蛋机小程序系统的开发,旨在打造一个互动性强的购物平台,让用户在购物的同时,能够享受到更多的乐趣和惊喜。 淘宝扭蛋机小程序系统拥有丰富的互动功能。用户可以通过虚拟摇杆操作扭蛋机,实现旋转、抽拉等动作,增…...
上位机开发过程中的设计模式体会(1):工厂方法模式、单例模式和生成器模式
简介 在我的 QT/C 开发工作中,合理运用设计模式极大地提高了代码的可维护性和可扩展性。本文将分享我在实际项目中应用的三种创造型模式:工厂方法模式、单例模式和生成器模式。 1. 工厂模式 (Factory Pattern) 应用场景 在我的 QT 项目中曾经有一个需…...

数据结构:递归的种类(Types of Recursion)
目录 尾递归(Tail Recursion) 什么是 Loop(循环)? 复杂度分析 头递归(Head Recursion) 树形递归(Tree Recursion) 线性递归(Linear Recursion)…...