简介: 项目的初衷是独立作出一个成熟的有特色的IOC容器,但由于过程参考Spring太多,而且也无法作出太多改进,于是目的变为以此项目作为理解Spring的一个跳板,与网上的一些模仿Spring的框架不同,本项目主要是针对注解形式

概述

项目的初衷是独立作出一个成熟的有特色的IOC容器,但由于过程参考Spring太多,而且也无法作出太多改进,于是目的变为以此项目作为理解Spring的一个跳板,与网上的一些模仿Spring的框架不同,本项目主要是针对注解形式

地址是Thales

流程

在Spring中,一个bean的形成分三个大的阶段,

  1. bean的定义阶段(包含BeanDefinition的加载,解析,与注册)
  2. bean的实例化阶段(包含对象的创建,属性的注入)
  3. bean的初始化阶段(包含一些资源的初始化,譬如打开文件建立连接等等)

这只是大概的划分,关于BeanPostProcessor等后置处理并没有显式的提及.

类的设计

如果只想了解一个bean是怎么从生到死的,只需要一步步debug就好了,如果看不懂,就多debug几遍.可是如果想实现一个类似的容器,类的设计,职责的分配,接口的实现继承必然是要了解的(除非你想几个类做完所有的事)

以下是DefaultListableBeanFactory的类图

是不是顶不住

我们再来看一张图

第一张是Spring5.0的,第二张图是Spring0.9的,所以并没有必要在一开始就引入过多的设计复杂度

我们再来看一套对比图

哪一个是0.9的,哪一个是5.0的一目了然.

说这么多的目的,是说明我们没必要一开始就奔着最完善的目标去写,可以一步步来,一步步加入功能

实现简易IOC

众所周知,SpringIoC中最基本的就是BeanFactory

我们先定义一个BeanFactory接口

//暂时就给这一个方法
public interface BeanFactory {
/**
* 根据名字获取Bean实例
* @param name
* @return
*/
Object getBean(String name);
}

beanDefinition

由于是注解形式,我们不能再像xml那样给定一个资源文件再去解析了,而应该去扫描classPath下所有带有@Component的类,

这时候我们需要给定的参数就从文件路径变成了包路径,我们只需要扫描这个包及其子包内符合条件的类,并且将其转化为BeanDefinition再注册就好.执行这个功能的是ClassPathBeanDefinitionScanner这个类.在这一步,就已经和传统的流程有所区别了,我们会传入一个ResourceLoader去实现具体的扫描功能(即定位),但不会再有专门的类去处理解析这一步

public interface Resource {
File getFile();
String getFilename();
String getFilePath();
}
//在最初设计的时候这个抽象类似乎没有用,但考虑到以后的扩展,还是先放在这
public abstract class AbstractResource implements Resource { @Override
public String getFilename() {
return getFile().getName();
} @Override
public String getFilePath() {
return getFile().getPath();
}
}
//这就是最终我们实例化bean时用到的Resource类,在Spring中并没有直接用,而是通过外观模式集成了一下成为RootBeanDefinition
public class ClassPathResource extends AbstractResource {
private final String path; private ClassLoader classLoader; private Class<?> clazz; public ClassPathResource(String path, ClassLoader classLoader, Class<?> clazz) {
this.path = path;
this.classLoader = classLoader;
this.clazz = clazz;
}
}

public interface ResourceLoader {
Resource getResource(String location);
}
//此类能够实现加载多个资源
public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
List<? extends Resource> getResources(String location);
} //这个类就是正式用于扫描的类了
public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {
private final ResourceLoader resourceLoader;
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader){
this.resourceLoader = resourceLoader;
} @Override
public Resource getResource(String location) {
return resourceLoader.getResource(location);
}
//在Spring中,是通过一层层方法的包装完成包名到路径的转换再到每个文件的扫描再转换为Resource,这里暂时就先一步到位,把具体实现放在工具类里
@Override
public List<? extends Resource> getResources(String location) {
Set<Class<?>> classes = ClassUtils.getClasses(location);
List<ClassPathResource> classPathResources = new ArrayList<>();
for (Class<?> clazz:classes) {
classPathResources.add(new ClassPathResource("",clazz.getClassLoader(),clazz));
}
return classPathResources;
}
}

但最后直接使用的并不是PathMatchingResourcePatternResolver

而是把他作为ClassPathBeanDefinitionScanner的一个属性,在这个类里调用.

我们得到了Resource,如何获得对应的BeanDefinition?

先考虑这样一个问题,什么样的类可以被注册BeanDefinition?

  1. 添加了@Component注解或者满足其他注册的条件
  2. 不是接口或者抽象类

所以我们可以单独抽象出一个方法 boolean isCandidateComponent(Class<?> clazz)来判断是否被注册

现在到了注册阶段,依旧秉持面向接口编程的理念,同时考虑到单一职责,我们把注册Bean定义单独抽象出来

public interface BeanDefinitionRegistry {
void registerBeanDefinition(BeanDefinition beanDefinition);
}

上文说到Bean定义的定位,解析,注册都是在ClassPathBeanDefinitionScanner里完成的,于是BeanDefinitionRegistry自然也成为了ClassPathBeanDefinitionScanner的属性之一

于是ClassPathBeanDefinitionScanner构建完成了

public class ClassPathBeanDefinitionScanner {
//负责具体的Resource定位
private ResourcePatternResolver resourcePatternResolver;
//负责BeanDefinition解析
private BeanDefinitionRegistry registry; public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry,String...basePackage) {
this.registry = registry;
this.resourcePatternResolver = new PathMatchingResourcePatternResolver((ResourceLoader) registry);
this.scan(basePackage);
} public void scan(String...basePackages){
doScan(basePackages);
}
void doScan(String[] basePackages){
Set<BeanDefinition> beanDefinitions = new LinkedHashSet<>();
for (String basePackage:basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for(BeanDefinition candidate:candidates){
beanDefinitions.add(candidate);
registry.registerBeanDefinition(candidate);
}
} } //获取被注册的bean的集合
private Set<BeanDefinition> findCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
List<? extends Resource> resources = getResourcePatternResolver().getResources(basePackage);
for(Resource resource:resources){
if(resource instanceof ClassPathResource){
ClassPathResource classPathResource = (ClassPathResource)resource;
if(isCandidateComponent(classPathResource.getClazz())){
AnnotationBeanDefinition beanDefinition = new AnnotationBeanDefinition();
beanDefinition.setClazz(classPathResource.getClazz());
beanDefinition.setBeanName(BeanUtils.generateBeanName(classPathResource.getClazz().getName()));
candidates.add(beanDefinition);
}
}
}
return candidates; }
private ResourcePatternResolver getResourcePatternResolver() {
return this.resourcePatternResolver;
}
//判断是否被注册
boolean isCandidateComponent(Class<?> clazz){
Component declaredAnnotation = clazz.getDeclaredAnnotation(Component.class);
return declaredAnnotation!=null&&!clazz.isInterface();
} ;
}

实例化

在什么时候实例化?我们说,在调用getBean()而又没有现成的bean时进行实例化

public abstract class AbstractBeanFactory implements BeanFactory{
@Override
public Object getBean(String beanName) }

对象创建

有两种方式,通过Jdk默认的反射实现,或者用cglib代理实现.

默认自然是无参构造,但是如果传入了参数,则需要根据参数的类型和数量去匹配对应的构造函数,用其去实例化

于是我们抽象出InstantiationStrategy作为实例化接口,两种实例化方法都需要实现这个接口,我们真正去用的时候只需要去调该接口的方法就好

public interface InstantiationStrategy {
Object instantiate(BeanDefinition beanDefinition, String beanName, BeanFactory owner);
}
public class SimpleInstantiationStrategy implements InstantiationStrategy {
}

属性注入

字段值获取

有两种方式可以实现字段值获取

  1. 直接注解Autowired或者Value
  2. Value里面填的不是值而是占位符,那么就需要解析占位符去获取

我们通过Class对象获取所有字段,再通过遍历所有字段查找加在字段上的注解来获取(这仅仅只是Spring的一种注入方式)

 //处理@Autowired注解
for(Field field:declaredFields){
Autowired autowired = field.getDeclaredAnnotation(Autowired.class);
if(autowired != null){
pvs.add(new PropertyValue(field.getName(),new BeanReference(BeanUtils.generateBeanName(field.getType().getName()),field.getType())));
}
}
//处理@Value注解
for(Field field:declaredFields){
Value value = field.getDeclaredAnnotation(Value.class);
if(value != null){
String value1 = value.value();
pvs.add(new PropertyValue(field.getName(),value1));
}
}

字段值填充

获取字段值后通过反射填入相应的字段中

for(Field field:mbd.getBeanClass().getDeclaredFields()){
field.setAccessible(true);
if (field.getName().equals(propertiesValue.getName())&&field.getType().isAssignableFrom(newValue.getClass())) {
field.set(bean,newValue);
}
}

初始化

调用指定的初始化方法,进行资源的初始化.,如何获取初始化方法?在xml模式中,只要加个标签即可,如果是注解模式,加个注解标识一下或者在某个注解上加个参数,代表初始化方法,这个还没有实现

功能填充

后置处理器添加

上面我们已经实现了一个可以进行依赖查找,依赖注入的Bean容器,让我们再回顾一下Spring的流程,我们少了些什么,最容易想到的应该就是后置处理器了,包括BeanFactoryPostProcessorBeanPostProcessor两种,前者对于beanFactory进行修改操作,后者对于bean进行修改操作,同样是面向接口编程

首先建立BeanPostProcessor

public interface BeanPostProcessor {

    Object postProcessBeforeInitialization(Object bean, String beanName);

    Object postProcessAfterInitialization(Object bean, String beanName) ;
}

就目前来看,有什么是需要BeanPostProcessor来做的呢?我们可以把之前对注解进行处理,获取注入属性的代码分离出来,专门用一个BeanPostProcessor去处理

所有自定义实现的BeanPostProcessor都需要继承这个接口,由于BeanPostProcessor的作用是处理其他的Bean,所以必须要在其他被处理的Bean实例化之前被创建出来.于是我们在finishBeanFactoryInitialization(beanFactory);之前添加registerBeanPostProcessors(beanFactory);用于实例化所有的BeanPostProcessor

而这些beanPostProcessor的重要程度是不同的,例如处理注解注入的BeanPostProcessor优先级就要比一般的BeanPostProcessor优先级要高,所以需要先实例化

Aware接口添加

其实现在我们已经可以完全的把一个对象交由IOC容器了,但此时这个对象与容器之间的关系是单向的,容器能够操作bean,但bean不能借助容器,为了解决此类问题,我们添加一个Aware接口作为标志接口,由各个更具体的Aware去继承他,并在实例化属性之后,初始化方法执行之完成相关容器属性的注入

事件监听器添加

监听器是观察者模式的一种实现

我们先定义以下几个基本接口

public interface ApplicationEventPublisher {
/**
* 发布事件
* @param event
*/
void publishEvent(ApplicationEvent event);
} public interface ApplicationEventMulticaster {
/**
* 添加广播事件
* @param event
*/
void multicastEvent(ApplicationEvent event); /**
* 添加对于某个事件的监听器
* @param listener
*/
void addApplicationListener(ApplicationListener listener); /**
* 移除指定监听器
* @param listener
*/
void removeApplicationListener(ApplicationListener listener);
}
public interface ApplicationListener <E extends ApplicationEvent> extends EventListener {
/**
* 监听特定事件
* @param event
*/
void onApplicationEvent(E event);
}

具体调用流程为具体的listener被添加到广播器中,事件通过publisher统一发布,而publishEvent最后会调用 multicastEvent(ApplicationEvent event)方法,经过相应判断后由对应监听器做出相应操作.

如何判断这个监听器是否对该事件感兴趣?

我们事先实现的listener是有泛型的,我们可以通过这个泛型与传入的事件类型的关系来判断

public boolean supportEvent(ApplicationListener<ApplicationEvent> listener,ApplicationEvent event){
//先获取Class对象
Class<? extends ApplicationListener> listenerClass = listener.getClass(); //获取其实现的所有接口(包括泛型信息)
Type[] genericInterfaces = listenerClass.getGenericInterfaces();
for (Type genericInterface:genericInterfaces){
//判断是否为泛型接口
if(genericInterface instanceof ParameterizedType){
ParameterizedType parameterizedType = (ParameterizedType) genericInterface;
//得到所有泛型参数
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for(Type actualTypeArgument:actualTypeArguments){
try {
Class<?> aClass = Class.forName(actualTypeArgument.getTypeName());
//判断感兴趣的事件类型是否与传入事件相同,或者是其父类
if(aClass.isAssignableFrom(event.getClass())){
return true;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
return false;
}

FactoryBean添加

目前的Bean都是由BeanFactory来产生的,

我们用FactoryBean接口来标识这个产生Bean的特殊的Bean

循环依赖的解决

循环依赖是指A依赖于B的同时B依赖于A,解决方法为实例化与初始化分离,如果只考虑一般情况的话用两级缓存实际上就够了,

代码优化

实现简易AOP

如果从正统的AOP开始的话,随之而来的就是一堆概念,包括切点,通知一类

我们先看AOP要做什么

所以说AOP的核心就是动态代理,我们以Cglib为例来看看动态代理要怎么用

 Enhancer enhancer = new Enhancer();
//1. 为哪个类进行代理
enhancer.setSuperclass(Buy.class);
enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
//2. 为该类的哪个方法进行代理
if(method.getName().equals("buyOne")){
//3. 代理究竟要做什么
System.out.println("hello");
}
//4. 调用原有的对象
methodProxy.invokeSuper(o,objects); return o;
});
//5. 产生代理后的对象
Buy o = (Buy)enhancer.create();

这就是动态代理最核心的功能,也是AOP的核心功能,AOP的最终目的是代码5,即产生一个代理对象,把这个代理对象交给IOC去管理

而为了达成这个目的,AOP框架需要做好代码1-4所需要做的事,一和二组合起来,成了JoinPoint,3叫做Advice,这两个组合起来就叫做Advisor,可不可以不分这些种类,就全写在一个或几个类里,当然可以,Spring0.9就是这么做的,但发展到如今,早已采用了这种划分方式.本项目也采用这种分类.

先从连接点说起,如何确定到底在哪里实现功能增强,无非是类与方法两个层次;

我们先定义ClassFilterMethodMacther两个接口

public interface ClassFilter {
/**
* 给定类型是否匹配
* @param clazz
* @return
*/
boolean matches(Class< ? > clazz);
}
public interface MethodMatcher {
/**
* 对应类的对应方法是否匹配
* @param method
* @param targetClass
* @return
*/
boolean matches(Method method,Class< ? > targetClass);
}

这两个接口必然是组合起来使用的,于是我们用PointCut将其组合起来

public interface Pointcut {
/**
* 获取ClassFilter
* @return
*/
ClassFilter getClassFilter(); /**
* 获取MethodMatcher
* @return
*/
MethodMatcher getMethodMatcher();
}

接口只是定义了抽象功能,这些功能还要有具体的实现

我们默认用Java的正则去匹配方法名,以此构建出JdkRegexMethodMatcher

public class JdkRegexMethodPointcut implements MethodMatcher, Pointcut{
private Pattern[] compiledPatterns = new Pattern[0];
@Override
public ClassFilter getClassFilter() {
return null;
} @Override
public MethodMatcher getMethodMatcher() {
return this;
} @Override
public boolean matches(Method method, Class<?> targetClass) {
String name = method.getName();
for (Pattern pattern :compiledPatterns) {
Matcher matcher = pattern.matcher(name);
if(matcher.matches()){
return true;
}
}
return false;
}
//预编译
private Pattern[] compilePatterns(String[] source) throws PatternSyntaxException {
Pattern[] destination = new Pattern[source.length];
for (int i = 0; i < source.length; i++) {
destination[i] = Pattern.compile(source[i]);
}
return destination;
}
public void initPatternRepresentation(String[] patterns) throws PatternSyntaxException {
this.compiledPatterns = compilePatterns(patterns);
}
}

Spring中,并不是直接继承的MethodMatcher,考虑到正则的语法不同,额外做了一层抽象,但在此处省略掉了

JdkRegexMethodMatcher同时也实现了PointCut类,也就是说,现在切点已经准备好了

再来看Advice

由于考虑的可扩展点比较多,于是继承的层次也变的多了

public interface Advice {
}
public interface BeforeAdvice extends Advice{
}
public interface MethodBeforeAdvice extends BeforeAdvice{
void before(Method method, Object[] args, Object target) throws Throwable;
}

现在Advice也定义完了,具体的实现我们交由用户去做

接下来就是整合成Advisor

public interface Advisor {
Advice getAdvice();
}
public interface PointcutAdvisor extends Advisor{ Pointcut getPointcut();
}
public abstract class AbstractPointcutAdvisor implements PointcutAdvisor{
private Advice advice;
@Override
public Advice getAdvice() {
return advice;
} public void setAdvice(Advice advice) {
this.advice = advice;
}
}

目前已经定义好了Advisor的功能

我们再实现这个接口

public class RegexMethodPointcutAdvisor extends AbstractPointcutAdvisor {
JdkRegexMethodPointcut pointcut = new JdkRegexMethodPointcut();
private String[] patterns; public RegexMethodPointcutAdvisor() {
}
public RegexMethodPointcutAdvisor(Advice advice) {
setAdvice(advice);
}
public void setPattern(String pattern) {
setPatterns(pattern);
} public void setPatterns(String... patterns) {
this.patterns = patterns;
pointcut.initPatternRepresentation(patterns); }
@Override
public Pointcut getPointcut() {
return pointcut;
}
}

RegexMethodPointcutAdvisor就整合了PointCut以及Advice,通过他,我们就可以确定在何处做何种增强.

现在的advisor可以完成检验一个类是否要被代理的功能,但是如果这个类需要被代理,advisor却无法保存这个类的对应信息

于是我们需要一个类将advisor与对应的代理类结合起来,这就是AdvisedSupport

public class AdvisedSupport {
private TargetSource targetSource;
private List<MethodInterceptor> methodInterceptors = new ArrayList<>();
private List<PointcutAdvisor> advisors = new ArrayList<>(); public TargetSource getTargetSource() {
return targetSource;
} public void setTargetSource(TargetSource targetSource) {
this.targetSource = targetSource;
} public List<MethodInterceptor> getMethodInterceptor() {
return methodInterceptors;
} public void addMethodInterceptor(MethodInterceptor methodInterceptor) {
this.methodInterceptors.add(methodInterceptor);
} public List<PointcutAdvisor> getAdvisor() {
return advisors;
} public void addAdvisor(PointcutAdvisor advisor) {
MethodBeforeAdviceInterceptor methodBeforeAdviceInterceptor = new MethodBeforeAdviceInterceptor();
methodBeforeAdviceInterceptor.setAdvice((MethodBeforeAdvice) advisor.getAdvice());
addMethodInterceptor(methodBeforeAdviceInterceptor);
this.advisors.add(advisor);
}
}

上类属性中的TargetSource便是真正持有代理对象信息的类

现在万事具备,只需要用Cglib去使用我们已经持有的信息就可以创建出新的类了

public class CglibAopProxy implements AopProxy{
private final AdvisedSupport advised; public CglibAopProxy(AdvisedSupport advised) {
this.advised = advised;
} @Override
public Object getProxy() {
Enhancer enhancer = new Enhancer();
//1. 为哪个类进行代理
enhancer.setSuperclass(advised.getTargetSource().getTargetClass());
enhancer.setCallback(new DynamicAdvisedInterceptor(advised));
//5. 产生代理后的对象
return enhancer.create();
}
private static class DynamicAdvisedInterceptor implements MethodInterceptor { private final AdvisedSupport advised; public DynamicAdvisedInterceptor(AdvisedSupport advised) {
this.advised = advised;
} @Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
CglibInvocation cglibInvocation = new CglibInvocation(method,objects,o,methodProxy);
//2. 为该类的哪个方法进行代理
for(PointcutAdvisor advisor: advised.getAdvisor()){
if(advisor.getPointcut().getMethodMatcher().matches(method,advised.getTargetSource().getTargetClass())){
//3. 代理究竟要做什么
return advised.getMethodInterceptor().get(0).invoke(cglibInvocation);
}
} //4. 调用源方法
return cglibInvocation.proceed();
}
}
}

将这份代码与最初使用cglib的代码比较,会发现过程几乎是一模一样.但是作为一个框架,应该尽可能的给用户以方便

于是我们需要一个Creator去把这一切都做好,他需要负责将AdvicePointCut组合成Advisor,再将AdvisorTargetSource组装成AdvisedSupport,再将AdvisedSupport交给Cglib动态代理,产生代理对象,而用户只需要编写Advice以及切入点表达式即可

功能演示

  1. 属性注入

    1. 基本类型
    2. 引用类型
    3. 循环依赖
  2. 容器感知
  3. FactoryBean生成对象
  4. AOP切面增强
  5. 自定义BeanPostProcessor

困难及解决

  1. 首先是设计上的问题
  2. FactoryBean的实现
  3. AOP与IOC的结合
  4. 字段的注入

原文链接

本文为阿里云原创内容,未经允许不得转载。

模仿Spring实现一个类管理容器的更多相关文章

  1. TP5.1:依赖注入、绑定一个类到容器里、绑定一个闭包到容器中

    依赖注入 1.在application中创建一个文件夹,名字为commom,commom文件夹中创建被注入文件夹,在被注入文件夹中创建一个名为demo.php的文件 2.在demo.php中输入: 3 ...

  2. spring 给一个类 生成test

    右击一个类,然后:GoTo==>test ,就可以新建这个类的测试用例了,简单粗暴, 如果想要不污染数据库,就在一个测试用例 的标签@Test 下面在加上@RollBack 这样: @Test ...

  3. Spring中一个类的注入和引用是不一样的

    1.在Spring管理下的bean需要以下面这种方式引入(一种注入方式): private MgrService mgrService; public MgrService getMgrService ...

  4. 2.spring源码-BeanPostProcessor后置处理之ApplicationContextAwareProcessor,实现spring容器中某一个类的bean对象在初始化时需要得到Spring容器内容。

    需求:我们的需求是,在spring初始化完毕时,使我们自定义一个类Bird类可以得到spring容器内容. 实现步骤: 1.首先我们来看一下ApplicationContextAwareProcess ...

  5. laravel学习:php写一个简单的ioc服务管理容器

    php写一个简单的ioc服务管理容器 原创: 陈晨 CoderStory 2018-01-14 最近学习laravel框架,了解到laravel核心是一个大容器,这个容器负责几乎所有服务组件的实例化以 ...

  6. Android Toast:是一个类,主要管理消息的提示

    Toast:是一个类,主要管理消息的提示.makeText(),是Toast的一个方法,用来显示信息,分别有三个参数.第一个参数:this,是上下文参数,指当前页面显示第二个参数:“string st ...

  7. Spring Boot+Quartz实现一个实时管理的定时任务

    转载 https://www.cnblogs.com/wujiwen/p/9615120.html 项目实践过程中碰到一个动态管理定时任务的需求:针对每个人员进行信息的定时更新,具体更新时间可随时调整 ...

  8. 证明spring中<property name="">这个双引号的内容只与setter方法有关,与一个类定义的字段和getter方法无关

    证明如下: 思路定义两个实体类每个实体类的成员变量(字段)名和setter 和getter的名字都不一样: 原因是:bean的声明周期的原因:有一步是:注入属性. 其中一个类引用了另一个类. 被引用类 ...

  9. Spring学习8-Spring事务管理(编程式事务管理)

    一.Spring事务的相关知识   1.事务是指一系列独立的操作,但在概念上具有原子性. 比如转账:A账号-100, B账号+100,完成.这两个操作独立是没问题的. 但在逻辑上,要么全部完成,要么一 ...

  10. Spring学习8-Spring事务管理

      http://blog.sina.com.cn/s/blog_7ffb8dd501014e0f.html   Spring学习8-Spring事务管理(注解式声明事务管理) 标签: spring注 ...

随机推荐

  1. SQL奇遇记:解锁 SQL 的秘密

    数据库基础 在我们探究SQL语言之旅的起点,首先要对数据库的核心理念有所了解.数据库在现代生活中无处不在,每次网购.网页浏览.即时通讯,都在产生数据.简单来说,数据库就是按一定数据结构组织.存储.管理 ...

  2. 记录--uniapp上如何实现安卓app微信登录功能(操作流程总结)

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 uniapp上如何实现安卓app微信登录功能?下面本篇文章给大家分享一下uniapp上实现安卓app微信登录的权限申请.开发的具体操作流程 ...

  3. Kafka之Producer网络传输

    一.背景 在Kafka的组成部分(Broker.Consumer.Producer)中,设计理念迥异,每个部分都有自己独特的思考.而把这些部分有机地组织起来,使其成为一个整体的便是「网络传输」.区别于 ...

  4. HTML/ CSS 入门

    前言 我们在之前的学习中,对于网络有了一定的了解.现在我们来学习一些基础的 HTML/ CSS 知识.希望阅读完这篇文章能达到编写简单页面的程度. 目录: HTML/ CSS 的发明: HTML 基础 ...

  5. C# 二维码生成、识别,去除白边、任意颜色

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  6. vue项目中添加水印效果

    新建js文件:例如warterMark.js 'use strict' let watermark = {} let setWatermark = (str) => { let id = '1. ...

  7. SQL优化篇之-如何减少耗时查询的调用次数

    函数调用次数与性能 在查询语句中,如果 Select 子句调用了较为耗时的函数或子查询,需要特别考虑函数调用次数对于SQL整体执行时间的影响. 一.数据准备,SQL 语句 模拟较耗时的用户函数 确保执 ...

  8. 学习 Tensorflow 的困境与解药

    我构建的预测模型 在过去的一段时间里我抓去了小宇宙内上万条播客节目的首日播放量的数据,并利用这些数据构建了一个用于预测播客节目播放量的模型.包含以下六个输入参数: 节目发布于一周中的哪一天 节目发布于 ...

  9. 【译】如何在 Visual Studio 中安装 GitHub Copilot

    GitHub Copilot 简介 GitHub Copilot 是一个新工具,可以帮助您在人工智能的帮助下更快,更智能地编写代码.它可以建议代码补全,生成代码片段,甚至为您编写整个函数.GitHub ...

  10. 使用pillow制作长图

    这是来自一个妹子的需求,需要将多张图片拼接成一张长图 我是使用pillow这个库来实现的,下面的简单的代码,操作比较简单,代码还有优化的空间 def test(dirpath): ims = [Ima ...