当前位置: 首页 > news >正文

随手记录第九话 -- 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依赖注入

  1. AbstractApplicationContext##refresh()下的finishBeanFactoryInitialization()#beanFactory.preInstantiateSingletons()中通过遍历beanDefinitionNames调用getBean()实例化对象,让代码解耦统一走getBean()更加的规范,完美解决循环依赖(程序初始化遍历一次,自己调用一次)
  2. 从上面代码看到依赖注入就如同递归,A依赖B,B依赖C,加载A的时候会去实例化B,实例化B的时候会去实例化C,直到最后没有依赖才算完结
  3. 通过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获得上下文
InitializingBeanbean创建完成,所有属性注入完成后执行
DisposableBean在bean销毁前执行
ApplicationListener事件监听

1.5.2 Spring中的Aware接口实现

如果一个Bean实现了Aware接口,则能在bean中获取相应的Spring资源。

Aware接口set属性作用
BeanNameAwaresetBeanName获得当前类在容器中的beanName
BeanClassLoaderAwaresetBeanClassLoader获得当前的类加载器
BeanFactoryAwaresetBeanFactory获得bean工厂以获取其他的bean
EnvironmentAwaresetEnvironment获得当前运行的环境相关
EmbeddedValueResolverAwaresetEmbeddedValueResolverEL解析表达式
ResourceLoaderAwaresetResourceLoader获得加载bean的资源加载器
ApplicatioinEventPublisherAwaresetApplicatioinEventPublisher获得时间发布者
MessageSourceAwaresetMessageSource获取国际化信息
ApplicationContextAwaresetApplicationContext获取上下文
ServletContextAwaresetServletContextServlet的上下文

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 自动装配流程概括

  1. 入口肯定是run方法,参数传入的是当前的启动类
  2. 在容器初始化之前传入的启动类注册到容器中去,然后再刷新容器refresh()
  3. 在容器注册阶段完成后,就是将所有的需要加载类注册到BeanDefinitionMap了
  4. 然后在上下文中调用注册bean的处理器回调,在springboot包中解析bean
  5. 关注点在注册类,解析该类时得到扫描路径即当前路径 ‘.’ ,也就说明了springboot项目默认扫描到启动类那一层,扫描我们手写的代码后注册到容器,但是所需依赖还是没有向容器注册的
  6. 启动类上有@Import注解,解析时会一并处理,然后获取import的类的回调来获取需要自动装配的类
  7. 在回调类中解析需要自动装载的类,通过读取包内文件目录下META-INF/spring.factories的配置中获取对应key的值
  8. 最终想容器注册,自动装配流程到此完结

2.2 自动装配流程概括

  1. 通过SpringApplication构造方法时会手动初始化listeners,即spring.factories中可以为org.springframework.context.ApplicationListener的值,最终保存到本地变量List<ApplicationListener<?>> listeners
  2. 初始化事件监听类EventPublishingRunListener
  3. EventPublishingRunListener类中获取到构造方法保存的监听,然后进行回调
  4. 后面回调到文件处理的监听ConfigFileApplicationListener类,然后通过加载配置中PropertySourceLoader类名的值得到PropertiesPropertySourceLoader,YamlPropertySourceLoader两个配置解析类
  5. 最终交由这两个类分别解析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全路径,值为需要自动装配的类路径即可。

  1. 在新建项目中添加两个类,在resources目录下准备META-INF/spring.factories,写入下面内容
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.start.test2.service.HelloService,\
com.start.test2.service.Hello1Service

然后将build打包,保证打包能够成功!

  1. 在其他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插件

  1. TypeHandler入参出参实现使用
  2. 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了&#xff0c;那就以它为起点吧。 本文只为整理复习用&#xff0c;详细内容自行翻看以前文章。 1.Spring 有人说是Spring成就Java&#xff0c;其实也不是并无道理。 1.1 Spring之IOC控制反转 以XML注入bean的方式为入口&#xff0c;定位、加载、注册&…...

电影《铃芽之旅》观后感

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

Web自动化测试(二)(全网最给力自动化教程)

欢迎您来阅读和练手&#xff01;您将会从本章的详细讲解中&#xff0c;获取很大的收获&#xff01;开始学习吧&#xff01; 2.4 CSS定位2.5 SeleniumBuilder辅助定位元素2.6 操作元素&#xff08;键盘和鼠标事件&#xff09; 正文 2.4 CSS定位 前言 大部分人在使用selenium定…...

【C语言经典例题!】逆序字符串

目录 一、题目要求 二、解题步骤 ①递归解法 思路 完整代码 ②循环解法 思路 完整代码 嗨大家好&#xff01; 本篇博客中的这道例题&#xff0c;是我自己在一次考试中写错的一道题 这篇博客包含了这道题的几种解法&#xff0c;以及一些我自己对这道题的看法&#xff…...

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本文暂学习四方向搜索&#xff0c;一、算法简介 一个比较经典的路径规划的算法 相关路径搜索算法&#xff1a; 广度优先遍历&#xff08;BFC&#xff09;深度优先遍历&#xff08;DFC&#xff09;Di jkstra算法&#…...

纽扣电池澳大利亚认证的更新要求

澳大利亚强制性安全和信息标准草案具体规定了对含有纽扣电池和纽扣电池以 及纽扣电池和纽扣电池本身的消费品的要求&#xff0c; 适用范围 1.本法规适用于: 纽扣锂电池(任何尺寸和类型); 直径为16毫米或以上的纽扣锂电池: 一起提供的纽扣电池(未预先安装在产品中)。 2.但是&…...

零代码零距离,明道云开放日北京站圆满结束

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

第五章Vue路由

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

Git常用指令

Git是什么&#xff1a; Git是分布式版本控制系统&#xff08;Distributed Version Control System&#xff0c;简称 DVCS&#xff09;&#xff0c;分为两种类型的仓库&#xff1a; 本地仓库和远程仓库 第一步先新建仓库&#xff0c;本地 init ,然后提交分枝 链接仓库&#xf…...

Java每日一练(20230329)

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

【面试题】JS的一些优雅写法 reduce和map

大厂面试题分享 面试题库 前后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 web前端面试题库 VS java后端面试题库大全 JS的一些优雅写法 reduce 1、可以使用 reduce 方法来实现对象数组中根据某一key值求和 …...

【蓝桥杯真题】包子凑数(裴蜀定理、动态规划、背包问题)

题意 小明几乎每天早晨都会在一家包子铺吃早餐。他发现这家包子铺有N种蒸笼&#xff0c;其中第i种蒸笼恰好能放Ai个包子。每种蒸笼都有非常多笼&#xff0c;可以认为是无限笼。 每当有顾客想买X个包子&#xff0c;卖包子的大叔就会迅速选出若干笼包子来&#xff0c;使得这若干…...

一种免费将PDF转word的方式

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

MyBatis-面试题

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

jQuery一些问题和ajax操作

jQuery语法&#xff1a; 文档就绪事件&#xff1a;文档加载之后运行jQuery代码&#xff0c;相当于jQuery的入口函数。 $(document).ready(function(){// 开始写 jQuery 代码...}); 简写&#xff1a; $(function(){// 开始写 jQuery 代码...}); jQuery选择器&#xff1a; …...

Pytorch构建自己的数据集

1.Pytorch内置的Dataset Pytorch中内置了许多数据集&#xff0c;我们可以从torchvision库中进行导入。比如&#xff0c;我们可以导入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列,每一列都有着特殊的含义&#xff0c;接下来我们逐一分析 id select语句的查询顺序,包含一组数字&#xff0c;如果数字相同则从上到下&#xff0c;如果数字不同则从大到小。 select_type …...

思必驰回复第二轮审核问询,如何与科大讯飞、阿里巴巴“虎口夺食”?

‍数据智能产业创新服务媒体——聚焦数智 改变商业3月21日&#xff0c;思必驰科技股份有限公司&#xff08;以下简称“思必驰”&#xff09;更新上市申请审核动态&#xff0c;已回复上交所第二轮审核问询函&#xff0c;回复了涵盖关于实际控制人的认定、关于预计持续亏损及关于…...

基于Spring、SpringMVC、MyBatis的汽车租赁系统设计

文章目录 项目介绍主要功能截图:前台首页汽车信息列表汽车租赁留言反馈个人信息管理后台汽车类型管理汽车信息管理租赁信息管理用户管理续租信息管理归还信息管理保险信息管理违章登记管理部分代码展示设计总结项目获取方式🍅 作者主页:Java韩立 🍅 简介:Java领域优质创…...

读《刻意练习》后感,与原文好句摘抄

第一章&#xff0c;有目的的练习 所谓“天真的练习”&#xff0c;基本上只是反复的做某件事情&#xff0c;并指望只靠这种反复的练习&#xff0c;就能够提高表现和水平。 有目的练习的四个特点 有目的的练习具有定义明确的特定目标有目的的练习是专注的有目的的练习包含反馈…...

华为OD机试用java实现 -【选座位】

最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧本篇题解:选座位 题目 疫情期间需要大…...

国产蓝牙耳机怎么挑选?口碑最好的国产蓝牙耳机

蓝牙耳机已经成为现代人生活中必不可少的设备之一&#xff0c;因此市场上涌现出了众多的品牌和型号。但是&#xff0c;在这个竞争激烈的市场中&#xff0c;哪些品牌的蓝牙耳机更受欢迎呢&#xff1f;以下是几款口碑不错的蓝牙耳机品牌。 一、南卡小音舱蓝牙耳机 推荐系数&…...

seaborn从入门到精通03-绘图功能实现02-分类绘图Categorical plots

seaborn从入门到精通03-绘图功能实现02-分类绘图Categorical plots总结参考关系-分布-分类分类绘图-Visualizing categorical data图形级接口catplot--figure-level interface导入库与查看tips和diamonds 数据分类散点图参考分布散点图stripplot分布密度散点图-swarmplot&#…...

❤️独特的算法❤️:一文解决编辑距离问题

编辑距离问题 题目关键点115. 不同的子序列 - 力扣&#xff08;LeetCode&#xff09;*dp数组定义&#xff0c;情况讨论583. 两个字符串的删除操作 - 力扣&#xff08;LeetCode&#xff09;两个字符串删除&#xff0c;情况讨论多加一种72. 编辑距离 - 力扣&#xff08;LeetCode…...

三次样条样条:Bézier样条和Hermite样条

总结 What is the Difference Between Natural Cubic Spline, Hermite Spline, Bzier Spline and B-spline? 1.多项式拟合中的 Runge Phenomenon 找到一条通过N1个点的多项式曲线 &#xff0c;需要N次曲线。通过两个点的多项式曲线为一次&#xff0c;三个点的多项式曲线为二…...

Redis面试题 (2023最新版)

文章目录一、Redis为什么快&#xff1f;1、纯内存访问2、单线程&#xff0c;避免上下文切换3、渐进式ReHash、缓存时间戳&#xff08;1&#xff09;渐进式ReHash&#xff1a;&#xff08;2&#xff09;缓存时间戳&#xff1a;二、Redis合适的应用场景常用基本数据类型&#xff…...

基于springboot实现家乡特色食品景点推荐系统【源码+论文】分享

基于springboot实现家乡特色推荐系统演示开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&…...

Spring MVC 启动之 HandlerMapping

在上一篇文章中&#xff0c;我们介绍了 Spring MVC 的启动流程&#xff0c;接下来我们将发分多个篇章详细介绍流程中的重点步骤 今天我们从 HandlerMapping 开始分析&#xff0c;HandlerMapping 是框架中的一个非常重要的组件。它的作用是将URL请求映射到合适的处理程序&#x…...

建站合同模板/成都黑帽seo

Print也许是Python中使用频率最高的一个函数。很多小白都是从Hello World程序开始认识Python&#xff0c;而Python的Hello World程序只有一行&#xff0c;那就是调用内置的Print函数&#xff0c;向控制台输出字符串“Hello World”。 不仅小白&#xff0c;哪怕是Python开发者&a…...

h5网站源代码/自己如何做网站

本文转载自一位做前端开发的朋友的博客【岁月如歌】&#xff0c;他向学习JavaScript的朋友推荐了很多非常不错的书籍及在线教程&#xff0c;适合英语能力不错的朋友参阅&#xff0c;转载如下&#xff1a; 最近 reddit 有讨论&#xff1a;References for JavaScript Mastery. 去…...

专题页是什么/泰州seo外包公司

本系列笔记主要是回顾了一下PHP中和日期操作相关的函数在PHP中设置时区有三种方法&#xff1a;1、直接在php的配置文件中修改以mac系统为例&#xff0c;我使用的是mamp pro 集成环境&#xff0c;所以我就说明了一下如何修改这种环境下的配置文件。a) 打开终端&#xff0c;输入 …...

wordpress中文下载/外链生成

CSS 背景(background) 目标 - 理解 - 背景的作用 - css背景图片和插入图片的区别 - 应用 - 通过css背景属性&#xff0c;给页面元素添加背景样式 - 能设置不同的背景图片位置 4.1 背景颜色(color) - 语法&#xff1a; background-color:颜色值; 默认的值是 trans…...

php网站开发要学什么/搜索引擎优化工具

IntelliJ IDEA&#xff0c;初次接触&#xff0c;被赞许的收费版IDE环境。 IntelliJ IDEA中文使用说明文档&#xff1a;https://github.com/tengj/IntelliJ-IDEA-Tutorial 极客学院提供的中文使用文档&#xff1a;http://wiki.jikexueyuan.com/project/intellij-idea-tutorial/…...

wordpress营销模板下载/百度信息流广告投放

在今日头条上面&#xff0c;有很多创作者并不擅长拍视频&#xff0c;因为口才不是太好&#xff0c;或者不想抛头露面&#xff0c;但是却有一个非常了得的文字功底&#xff0c;内容方面简直可以跟作家水平相媲美&#xff0c;但是却难倒在标题上面。大约半个月前&#xff0c;有个…...