Android Dagger2 框架辅助工具模块深度剖析(六)
一、引言
在 Android 开发领域,依赖注入(Dependency Injection,简称 DI)作为一种至关重要的设计模式,能显著降低代码间的耦合度,提升代码的可测试性与可维护性。Dagger2 作为一款强大的依赖注入框架,凭借其在编译时生成依赖注入代码的特性,有效避免了运行时反射带来的性能损耗。而辅助工具模块在 Dagger2 中扮演着不可或缺的角色,它为开发者提供了一系列实用的工具和特性,助力开发者更高效地运用 Dagger2 进行开发。本文将全方位、深入地剖析 Dagger2 框架的辅助工具模块,从源码层面细致解读其实现原理与工作流程。
二、辅助工具模块概述
2.1 辅助工具模块的定义与作用
Dagger2 的辅助工具模块涵盖了一系列用于简化依赖注入过程、增强代码灵活性和可维护性的工具和特性。这些工具和特性并非直接参与依赖注入的核心流程,而是在开发过程中提供辅助支持,例如提供更便捷的依赖绑定方式、实现依赖替换、进行多绑定等。
2.2 辅助工具模块的主要组成部分
- 多绑定(Multibindings) :允许将多个依赖对象绑定到同一个类型上,实现依赖对象的集合注入。
- 限定符(Qualifiers) :为依赖对象添加额外的标识,在注入时可区分不同的依赖对象。
- 依赖替换(Dependency Substitution) :在测试环境中能够替换生产环境中的依赖对象,便于进行单元测试。
- 子组件(Subcomponents) :用于管理更细粒度的依赖关系,可继承父组件的依赖并定义自身的依赖和作用域。
三、多绑定(Multibindings)
3.1 多绑定的概念与用途
多绑定是 Dagger2 提供的一种强大特性,它允许将多个依赖对象绑定到同一个类型上。通过多绑定,开发者可以实现依赖对象的集合注入,这在需要处理多个相同类型依赖对象的场景中非常实用,例如插件系统、事件监听器集合等。
3.2 多绑定的类型
Dagger2 支持三种类型的多绑定:
- Set 绑定(Set Bindings) :将多个依赖对象绑定到一个
Set
集合中。 - Map 绑定(Map Bindings) :将多个依赖对象绑定到一个
Map
中,每个对象对应一个键。 - IntoSet 和 IntoMap 注解:用于将单个依赖对象添加到
Set
或Map
中。
3.3 Set 绑定的源码分析
3.3.1 Set 绑定的使用示例
java
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoSet;
import java.util.Set;
import javax.inject.Inject;// 定义一个依赖类
class Service {public void performService() {System.out.println("Performing service");}
}// 使用 @Module 注解标记模块类
@Module
class ServiceModule {// 使用 @IntoSet 注解将 Service 类的实例添加到集合中@Provides@IntoSetpublic Service provideService1() {return new Service();}// 使用 @IntoSet 注解将另一个 Service 类的实例添加到集合中@Provides@IntoSetpublic Service provideService2() {return new Service();}
}// 定义一个需要注入集合的类
class ServiceManager {private final Set<Service> services;// 使用 @Inject 注解标记构造函数@Injectpublic ServiceManager(Set<Service> services) {this.services = services;}public void startServices() {for (Service service : services) {service.performService();}}
}// 定义组件接口
import dagger.Component;@Component(modules = ServiceModule.class)
interface ServiceComponent {// 定义注入方法,用于将依赖对象注入到目标对象中void inject(ServiceManager manager);
}public class SetBindingExample {public static void main(String[] args) {// 创建组件实例ServiceComponent serviceComponent = DaggerServiceComponent.create();// 创建目标对象实例ServiceManager serviceManager = new ServiceManager(null);// 使用组件实例将依赖对象注入到目标对象中serviceComponent.inject(serviceManager);// 调用目标对象的方法serviceManager.startServices();}
}
3.3.2 Set 绑定的源码实现
在编译时,Dagger2 的注解处理器会根据 @IntoSet
注解生成相应的代码。以下是简化后的生成代码示例,用于说明 Set 绑定的实现原理:
java
// 生成的组件实现类
public final class DaggerServiceComponent implements ServiceComponent {private final ServiceModule serviceModule;private final java.util.Set<Service> serviceSet;private DaggerServiceComponent(ServiceModule serviceModule) {this.serviceModule = serviceModule;// 创建一个可变的 Set 集合java.util.Set<Service> mutableSet = new java.util.LinkedHashSet<>();// 添加第一个 Service 实例mutableSet.add(serviceModule.provideService1());// 添加第二个 Service 实例mutableSet.add(serviceModule.provideService2());// 将可变集合转换为不可变集合this.serviceSet = java.util.Collections.unmodifiableSet(mutableSet);}public static ServiceComponent create() {return new DaggerServiceComponent(new ServiceModule());}@Overridepublic void inject(ServiceManager manager) {// 将 Set 集合注入到目标对象中new ServiceManager(serviceSet);}
}
从上述代码可以看出,Dagger2 在生成的组件实现类中创建了一个 Set
集合,并将所有使用 @IntoSet
注解提供的依赖对象添加到该集合中。最后,将该集合注入到需要的目标对象中。
3.4 Map 绑定的源码分析
3.4.1 Map 绑定的使用示例
java
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import dagger.multibindings.StringKey;
import java.util.Map;
import javax.inject.Inject;// 定义一个依赖类
class Processor {public void process() {System.out.println("Processing");}
}// 使用 @Module 注解标记模块类
@Module
class ProcessorModule {// 使用 @IntoMap 注解将 Processor 类的实例添加到 Map 中,并使用 @StringKey 指定键@Provides@IntoMap@StringKey("processor1")public Processor provideProcessor1() {return new Processor();}// 使用 @IntoMap 注解将另一个 Processor 类的实例添加到 Map 中,并使用 @StringKey 指定键@Provides@IntoMap@StringKey("processor2")public Processor provideProcessor2() {return new Processor();}
}// 定义一个需要注入 Map 的类
class ProcessorManager {private final Map<String, Processor> processors;// 使用 @Inject 注解标记构造函数@Injectpublic ProcessorManager(Map<String, Processor> processors) {this.processors = processors;}public void processAll() {for (Map.Entry<String, Processor> entry : processors.entrySet()) {System.out.println("Processing with " + entry.getKey());entry.getValue().process();}}
}// 定义组件接口
import dagger.Component;@Component(modules = ProcessorModule.class)
interface ProcessorComponent {// 定义注入方法,用于将依赖对象注入到目标对象中void inject(ProcessorManager manager);
}public class MapBindingExample {public static void main(String[] args) {// 创建组件实例ProcessorComponent processorComponent = DaggerProcessorComponent.create();// 创建目标对象实例ProcessorManager processorManager = new ProcessorManager(null);// 使用组件实例将依赖对象注入到目标对象中processorComponent.inject(processorManager);// 调用目标对象的方法processorManager.processAll();}
}
3.4.2 Map 绑定的源码实现
在编译时,Dagger2 的注解处理器会根据 @IntoMap
和 @StringKey
注解生成相应的代码。以下是简化后的生成代码示例,用于说明 Map 绑定的实现原理:
java
// 生成的组件实现类
public final class DaggerProcessorComponent implements ProcessorComponent {private final ProcessorModule processorModule;private final java.util.Map<String, Processor> processorMap;private DaggerProcessorComponent(ProcessorModule processorModule) {this.processorModule = processorModule;// 创建一个可变的 Mapjava.util.Map<String, Processor> mutableMap = new java.util.LinkedHashMap<>();// 添加第一个 Processor 实例,并使用指定的键mutableMap.put("processor1", processorModule.provideProcessor1());// 添加第二个 Processor 实例,并使用指定的键mutableMap.put("processor2", processorModule.provideProcessor2());// 将可变 Map 转换为不可变 Mapthis.processorMap = java.util.Collections.unmodifiableMap(mutableMap);}public static ProcessorComponent create() {return new DaggerProcessorComponent(new ProcessorModule());}@Overridepublic void inject(ProcessorManager manager) {// 将 Map 注入到目标对象中new ProcessorManager(processorMap);}
}
从上述代码可以看出,Dagger2 在生成的组件实现类中创建了一个 Map
,并将所有使用 @IntoMap
注解提供的依赖对象添加到该 Map
中,同时使用 @StringKey
指定的键作为 Map
的键。最后,将该 Map
注入到需要的目标对象中。
四、限定符(Qualifiers)
4.1 限定符的概念与用途
限定符是 Dagger2 提供的一种机制,用于为依赖对象添加额外的标识。当存在多个相同类型的依赖对象时,通过使用限定符,开发者可以在注入时区分不同的依赖对象,确保注入正确的依赖。
4.2 限定符的定义与使用
4.2.1 定义限定符注解
java
import javax.inject.Qualifier;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;// 定义一个限定符注解
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@interface PrimaryEngine {}// 定义另一个限定符注解
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@interface SecondaryEngine {}
4.2.2 使用限定符注解
java
import javax.inject.Inject;
import dagger.Module;
import dagger.Provides;
import dagger.Component;// 定义一个依赖类
class Engine {public void start() {System.out.println("Engine started");}
}// 使用 @Module 注解标记模块类
@Module
class CarModule {// 使用 @PrimaryEngine 限定符注解标记提供依赖对象的方法@Provides@PrimaryEnginepublic Engine providePrimaryEngine() {return new Engine();}// 使用 @SecondaryEngine 限定符注解标记提供依赖对象的方法@Provides@SecondaryEnginepublic Engine provideSecondaryEngine() {return new Engine();}
}// 定义一个需要注入依赖的类
class Car {private final Engine primaryEngine;private final Engine secondaryEngine;// 使用 @Inject 注解标记构造函数,并使用限定符注解区分不同的依赖对象@Injectpublic Car(@PrimaryEngine Engine primaryEngine, @SecondaryEngine Engine secondaryEngine) {this.primaryEngine = primaryEngine;this.secondaryEngine = secondaryEngine;}public void startPrimaryEngine() {primaryEngine.start();}public void startSecondaryEngine() {secondaryEngine.start();}
}// 定义组件接口
@Component(modules = CarModule.class)
interface CarComponent {// 定义注入方法,用于将依赖对象注入到目标对象中void inject(Car car);
}public class QualifierExample {public static void main(String[] args) {// 创建组件实例CarComponent carComponent = DaggerCarComponent.create();// 创建目标对象实例Car car = new Car(null, null);// 使用组件实例将依赖对象注入到目标对象中carComponent.inject(car);// 调用目标对象的方法car.startPrimaryEngine();car.startSecondaryEngine();}
}
4.3 限定符的源码分析
在编译时,Dagger2 的注解处理器会识别限定符注解,并在生成的代码中使用这些限定符来区分不同的依赖对象。以下是简化后的生成代码示例,用于说明限定符的实现原理:
java
// 生成的组件实现类
public final class DaggerCarComponent implements CarComponent {private final CarModule carModule;private DaggerCarComponent(CarModule carModule) {this.carModule = carModule;}public static CarComponent create() {return new DaggerCarComponent(new CarModule());}@Overridepublic void inject(Car car) {// 根据 @PrimaryEngine 限定符获取对应的 Engine 实例Engine primaryEngine = carModule.providePrimaryEngine();// 根据 @SecondaryEngine 限定符获取对应的 Engine 实例Engine secondaryEngine = carModule.provideSecondaryEngine();// 将依赖对象注入到目标对象中new Car(primaryEngine, secondaryEngine);}
}
从上述代码可以看出,Dagger2 在生成的组件实现类中,根据限定符注解调用相应的提供依赖对象的方法,确保注入正确的依赖对象。
五、依赖替换(Dependency Substitution)
5.1 依赖替换的概念与用途
依赖替换允许开发者在测试环境中替换生产环境中的依赖对象,从而方便进行单元测试。通过依赖替换,开发者可以模拟不同的依赖对象行为,验证代码在不同情况下的正确性。
5.2 依赖替换的实现方式
5.2.1 使用不同的模块
在测试环境中,可以创建一个专门的测试模块,该模块提供与生产环境不同的依赖对象。通过在组件中使用测试模块替换生产模块,实现依赖对象的替换。
java
import javax.inject.Inject;
import dagger.Module;
import dagger.Provides;
import dagger.Component;// 生产环境的依赖类
class ProductionDependency {public void doSomething() {System.out.println("Production dependency doing something");}
}// 测试环境的依赖类
class TestDependency {public void doSomething() {System.out.println("Test dependency doing something");}
}// 生产环境的模块
@Module
class ProductionModule {@ProvidesProductionDependency provideProductionDependency() {return new ProductionDependency();}
}// 测试环境的模块
@Module
class TestModule {@ProvidesTestDependency provideTestDependency() {return new TestDependency();}
}// 组件
@Component(modules = ProductionModule.class)
interface MyComponent {void inject(MyClass myClass);
}// 需要注入依赖的类
class MyClass {@InjectProductionDependency dependency;public void performAction() {dependency.doSomething();}
}// 测试类
public class DependencySubstitutionTest {public static void main(String[] args) {// 生产环境的组件实例MyComponent productionComponent = DaggerMyComponent.create();MyClass productionMyClass = new MyClass();productionComponent.inject(productionMyClass);productionMyClass.performAction();// 测试环境的组件实例(假设通过某种方式替换了模块)// 这里只是示例,实际中可能需要更复杂的配置// 例如使用 Dagger 的 builder 模式来替换模块// 这里简单模拟TestModule testModule = new TestModule();// 假设存在一个 TestComponent 继承自 MyComponent 并使用 TestModule// TestComponent testComponent = DaggerTestComponent.builder().testModule(testModule).build();// MyClass testMyClass = new MyClass();// testComponent.inject(testMyClass);// testMyClass.performAction();}
}
5.2.2 源码分析
在编译时,Dagger2 会根据组件所使用的模块来生成相应的代码。当使用不同的模块时,生成的代码会调用不同模块中提供依赖对象的方法,从而实现依赖对象的替换。以下是简化后的生成代码示例,用于说明依赖替换的实现原理:
java
// 生产环境的组件实现类
public final class DaggerMyComponent implements MyComponent {private final ProductionModule productionModule;private DaggerMyComponent(ProductionModule productionModule) {this.productionModule = productionModule;}public static MyComponent create() {return new DaggerMyComponent(new ProductionModule());}@Overridepublic void inject(MyClass myClass) {// 从生产模块中获取依赖对象ProductionDependency dependency = productionModule.provideProductionDependency();// 将依赖对象注入到目标对象中myClass.dependency = dependency;}
}// 假设的测试环境的组件实现类
public final class DaggerTestComponent implements MyComponent {private final TestModule testModule;private DaggerTestComponent(TestModule testModule) {this.testModule = testModule;}public static MyComponent create() {return new DaggerTestComponent(new TestModule());}@Overridepublic void inject(MyClass myClass) {// 从测试模块中获取依赖对象TestDependency dependency = testModule.provideTestDependency();// 将依赖对象注入到目标对象中myClass.dependency = (ProductionDependency) dependency; // 这里只是示例,实际需要类型兼容}
}
从上述代码可以看出,生产环境的组件实现类从生产模块中获取依赖对象,而测试环境的组件实现类从测试模块中获取依赖对象,从而实现了依赖对象的替换。
六、子组件(Subcomponents)
6.1 子组件的概念与用途
子组件是 Dagger2 中用于管理更细粒度依赖关系的一种机制。子组件可以继承父组件的依赖,并且可以定义自己的依赖和作用域。通过使用子组件,开发者可以将依赖关系进行分层管理,提高代码的可维护性和灵活性。
6.2 子组件的定义与使用
6.2.1 定义父组件和子组件
java
import javax.inject.Inject;
import dagger.Module;
import dagger.Provides;
import dagger.Component;
import dagger.Subcomponent;// 父组件的模块
@Module
class ParentModule {@ProvidesString provideParentString() {return "Parent String";}
}// 父组件
@Component(modules = ParentModule.class)
interface ParentComponent {ChildComponent.Builder childComponentBuilder();
}// 子组件的模块
@Module
class ChildModule {@ProvidesString provideChildString() {return "Child String";}
}// 子组件
@Subcomponent(modules = ChildModule.class)
interface ChildComponent {void inject(Child child);@Subcomponent.Builderinterface Builder {ChildComponent build();Builder childModule(ChildModule module);}
}// 子组件注入的目标类
class Child {@InjectString parentString;@InjectString childString;public void printStrings() {System.out.println(parentString);System.out.println(childString);}
}public class SubcomponentExample {public static void main(String[] args) {// 创建父组件实例ParentComponent parentComponent = DaggerParentComponent.create();// 创建子组件实例ChildComponent childComponent = parentComponent.childComponentBuilder().childModule(new ChildModule()).build();// 创建目标对象实例Child child = new Child();// 使用子组件实例将依赖对象注入到目标对象中childComponent.inject(child);// 调用目标对象的方法child.printStrings();}
}
6.2.2 子组件的源码分析
在编译时,Dagger2 会为子组件生成相应的代码。子组件可以访问父组件的依赖,因为子组件的生成代码会持有父组件的引用。以下是简化后的生成代码示例,用于说明子组件的实现原理:
java
// 生成的父组件实现类
public final class DaggerParentComponent implements ParentComponent {private static final class ParentScopeHolder {private static final DaggerParentComponent INSTANCE = new DaggerParentComponent();}private final String parentString;private DaggerParentComponent() {parentString = new ParentModule().provideParentString();}public static ParentComponent create() {return ParentScopeHolder.INSTANCE;}@Overridepublic ChildComponent.Builder childComponentBuilder() {return new ChildComponentBuilder();}private final class ChildComponentBuilder implements ChildComponent.Builder {private ChildModule childModule;@Overridepublic ChildComponent build() {if (childModule == null) {throw new IllegalStateException("ChildModule must be set");}return new DaggerChildComponent(DaggerParentComponent.this, childModule);}@Overridepublic Builder childModule(ChildModule module) {this.childModule = module;return this;}}
}// 生成的子组件实现类
public final class DaggerChildComponent implements ChildComponent {private final DaggerParentComponent parentComponent;private final ChildModule childModule;private final String childString;private DaggerChildComponent(DaggerParentComponent parentComponent, ChildModule childModule) {this.parentComponent = parentComponent;this.childModule = childModule;this.childString = childModule.provideChildString();}@Overridepublic void inject(Child child) {// 从父组件获取依赖child.parentString = parentComponent.parentString;// 从子组件的模块获取依赖child.childString = childString;}
}
从上述代码可以看出,父组件 DaggerParentComponent
持有 parentString
依赖对象,子组件 DaggerChildComponent
持有父组件的引用,并可以从父组件中获取依赖对象。同时,子组件还可以从自己的模块中获取依赖对象,实现了依赖关系的分层管理。
七、辅助工具模块的性能优化
7.1 减少不必要的多绑定和限定符使用
过多的多绑定和限定符会增加代码的复杂度和编译时间。在使用多绑定和限定符时,应确保确实有必要,避免不必要的使用。例如,如果只需要一个依赖对象,就不需要使用多绑定;如果不存在多个相同类型的依赖对象,就不需要使用限定符。
7.2 优化子组件的使用
子组件的使用可以提高代码的可维护性,但过多的子组件会增加代码的复杂度和内存开销。在使用子组件时,应合理规划子组件的层次结构,避免创建过多的子组件。同时,应确保子组件的作用域合理,避免作用域冲突和内存泄漏。
7.3 利用编译时优化
Dagger2 在编译时生成依赖注入代码,利用编译时优化可以提高性能。可以通过以下方式利用编译时优化:
- 减少反射使用:Dagger2 避免了运行时反射,应尽量避免在代码中手动使用反射。
- 使用代码生成工具:Dagger2 的注解处理器会生成高效的依赖注入代码,应充分利用这些生成的代码。
八、辅助工具模块的调试和错误处理
8.1 调试技巧
在使用 Dagger2 的辅助工具模块时,可能会遇到一些问题,以下是一些调试技巧:
- 查看生成的代码:Dagger2 在编译时会生成大量的代码,可以查看这些生成的代码来了解依赖注入的具体实现。
- 使用日志输出:在关键的地方添加日志输出,查看依赖对象的创建和注入过程。
- 使用调试工具:可以使用 Android Studio 等开发工具的调试功能,逐步调试依赖注入的过程。
8.2 常见错误及解决方法
8.2.1 多绑定冲突错误
当多个模块提供相同类型的依赖对象并使用多绑定注解时,可能会导致多绑定冲突错误。解决方法如下:
- 检查多绑定注解的使用:确保多绑定注解使用正确,避免重复绑定相同类型的依赖对象。
- 使用限定符区分:如果需要绑定多个相同类型的依赖对象,可以使用限定符来区分它们。
8.2.2 限定符不匹配错误
当注入时使用的限定符与提供依赖对象时使用的限定符不匹配时,会导致限定符不匹配错误。解决方法如下:
- 检查限定符注解的使用:确保注入和提供依赖对象时使用的限定符注解一致。
- 检查组件和模块的配置:确保组件和模块中正确配置了限定符。
8.2.3 子组件作用域冲突错误
当子组件的作用域与父组件的作用域冲突时,会导致编译错误。解决方法如下:
- 检查作用域注解的使用:确保子组件和父组件的作用域注解使用正确,避免作用域冲突。
- 调整子组件和父组件的作用域:根据实际需求,调整子组件和父组件的作用域。
九、辅助工具模块在 Android 开发中的应用
9.1 在 Activity 和 Fragment 中的应用
在 Android 开发中,Activity 和 Fragment 有自己的生命周期。可以使用子组件和限定符来管理与 Activity 或 Fragment 相关的依赖对象,确保这些依赖
继续分析
9.1 在 Activity 和 Fragment 中的应用(续)
9.1.1 子组件在 Activity 中的使用
在 Android 应用里,Activity 有着自身独特的生命周期。借助子组件,我们能够精准管理与 Activity 相关的依赖对象,保证这些依赖对象的生命周期和 Activity 保持一致。
以下是一个在 Activity 中运用子组件的示例:
java
import android.app.Activity;
import android.os.Bundle;
import javax.inject.Inject;
import dagger.Module;
import dagger.Provides;
import dagger.Component;
import dagger.Subcomponent;// 定义一个依赖类
class ActivityDependency {public void doActivityWork() {System.out.println("Doing activity work");}
}// 父组件的模块
@Module
class AppModule {// 提供一个通用的依赖对象@ProvidesString provideAppString() {return "App String";}
}// 父组件
@Component(modules = AppModule.class)
interface AppComponent {// 定义创建子组件的方法ActivityComponent.Builder activityComponentBuilder();
}// 子组件的模块
@Module
class ActivityModule {private final Activity activity;public ActivityModule(Activity activity) {this.activity = activity;}// 提供与 Activity 相关的依赖对象@ProvidesActivityDependency provideActivityDependency() {return new ActivityDependency();}
}// 子组件
@Subcomponent(modules = ActivityModule.class)
interface ActivityComponent {// 定义注入方法,用于将依赖对象注入到目标对象中void inject(MainActivity activity);@Subcomponent.Builderinterface Builder {ActivityComponent build();Builder activityModule(ActivityModule module);}
}// 主 Activity 类
public class MainActivity extends Activity {// 使用 @Inject 注解标记需要注入的字段@InjectActivityDependency activityDependency;@InjectString appString;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 获取父组件实例AppComponent appComponent = ((MyApplication) getApplication()).getAppComponent();// 创建子组件实例ActivityComponent activityComponent = appComponent.activityComponentBuilder().activityModule(new ActivityModule(this)).build();// 使用子组件实例将依赖对象注入到目标对象中activityComponent.inject(this);// 调用依赖对象的方法activityDependency.doActivityWork();System.out.println(appString);}
}// 自定义 Application 类
class MyApplication extends android.app.Application {private AppComponent appComponent;@Overridepublic void onCreate() {super.onCreate();appComponent = DaggerAppComponent.create();}public AppComponent getAppComponent() {return appComponent;}
}
源码分析
- 父组件
DaggerAppComponent
:
java
public final class DaggerAppComponent implements AppComponent {private static final class AppScopeHolder {private static final DaggerAppComponent INSTANCE = new DaggerAppComponent();}private final String appString;private DaggerAppComponent() {appString = new AppModule().provideAppString();}public static AppComponent create() {return AppScopeHolder.INSTANCE;}@Overridepublic ActivityComponent.Builder activityComponentBuilder() {return new ActivityComponentBuilder();}private final class ActivityComponentBuilder implements ActivityComponent.Builder {private ActivityModule activityModule;@Overridepublic ActivityComponent build() {if (activityModule == null) {throw new IllegalStateException("ActivityModule must be set");}return new DaggerActivityComponent(DaggerAppComponent.this, activityModule);}@Overridepublic Builder activityModule(ActivityModule module) {this.activityModule = module;return this;}}
}
父组件负责创建通用的依赖对象(如 appString
),并且提供创建子组件的方法。子组件的构建器会持有父组件的引用,以便子组件能够访问父组件的依赖。
- 子组件
DaggerActivityComponent
:
java
public final class DaggerActivityComponent implements ActivityComponent {private final DaggerAppComponent parentComponent;private final ActivityModule activityModule;private final ActivityDependency activityDependency;private DaggerActivityComponent(DaggerAppComponent parentComponent, ActivityModule activityModule) {this.parentComponent = parentComponent;this.activityModule = activityModule;this.activityDependency = activityModule.provideActivityDependency();}@Overridepublic void inject(MainActivity activity) {activity.activityDependency = activityDependency;activity.appString = parentComponent.appString;}
}
子组件持有父组件的引用,能够获取父组件的依赖(如 appString
),同时从自身的模块中获取与 Activity 相关的依赖(如 activityDependency
),并将这些依赖注入到目标 Activity 中。
9.1.2 限定符在 Fragment 中的使用
在 Fragment 中,有时会存在多个相同类型的依赖对象,此时可以使用限定符来区分它们。
以下是一个在 Fragment 中使用限定符的示例:
java
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.fragment.app.Fragment;
import javax.inject.Inject;
import dagger.Module;
import dagger.Provides;
import dagger.Component;
import dagger.multibindings.IntoSet;
import java.util.Set;// 定义一个依赖类
class FragmentDependency {public void doFragmentWork() {System.out.println("Doing fragment work");}
}// 定义限定符注解
import javax.inject.Qualifier;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@interface PrimaryDependency {}@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@interface SecondaryDependency {}// 模块类
@Module
class FragmentModule {// 提供主依赖对象@Provides@PrimaryDependencypublic FragmentDependency providePrimaryDependency() {return new FragmentDependency();}// 提供次依赖对象@Provides@SecondaryDependencypublic FragmentDependency provideSecondaryDependency() {return new FragmentDependency();}
}// 组件接口
@Component(modules = FragmentModule.class)
interface FragmentComponent {// 定义注入方法,用于将依赖对象注入到目标对象中void inject(MyFragment fragment);
}// 自定义 Fragment 类
public class MyFragment extends Fragment {// 使用 @Inject 注解标记需要注入的字段@Inject@PrimaryDependencyFragmentDependency primaryDependency;@Inject@SecondaryDependencyFragmentDependency secondaryDependency;@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {// 获取组件实例FragmentComponent fragmentComponent = DaggerFragmentComponent.create();// 使用组件实例将依赖对象注入到目标对象中fragmentComponent.inject(this);// 调用依赖对象的方法primaryDependency.doFragmentWork();secondaryDependency.doFragmentWork();return inflater.inflate(R.layout.fragment_my, container, false);}
}
源码分析
- 组件
DaggerFragmentComponent
:
java
public final class DaggerFragmentComponent implements FragmentComponent {private final FragmentModule fragmentModule;private DaggerFragmentComponent(FragmentModule fragmentModule) {this.fragmentModule = fragmentModule;}public static FragmentComponent create() {return new DaggerFragmentComponent(new FragmentModule());}@Overridepublic void inject(MyFragment fragment) {// 根据限定符获取对应的依赖对象FragmentDependency primaryDependency = fragmentModule.providePrimaryDependency();FragmentDependency secondaryDependency = fragmentModule.provideSecondaryDependency();fragment.primaryDependency = primaryDependency;fragment.secondaryDependency = secondaryDependency;}
}
在组件的注入方法中,根据限定符调用相应的提供依赖对象的方法,确保将正确的依赖对象注入到 Fragment 中。
9.2 在 Service 中的应用
在 Android 开发里,Service 是一种在后台运行的组件,具备自身的生命周期。可以运用子组件和限定符来管理与 Service 相关的依赖对象,保证这些依赖对象的生命周期和 Service 一致。
以下是一个在 Service 中使用子组件和限定符的示例:
java
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import javax.inject.Inject;
import dagger.Module;
import dagger.Provides;
import dagger.Component;
import dagger.Subcomponent;// 定义一个依赖类
class ServiceDependency {public void doServiceWork() {System.out.println("Doing service work");}
}// 父组件的模块
@Module
class AppModule {// 提供一个通用的依赖对象@ProvidesString provideAppString() {return "App String";}
}// 父组件
@Component(modules = AppModule.class)
interface AppComponent {// 定义创建子组件的方法ServiceComponent.Builder serviceComponentBuilder();
}// 定义限定符注解
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@interface ServiceSpecificDependency {}// 子组件的模块
@Module
class ServiceModule {private final Service service;public ServiceModule(Service service) {this.service = service;}// 提供与 Service 相关的依赖对象@Provides@ServiceSpecificDependencyServiceDependency provideServiceDependency() {return new ServiceDependency();}
}// 子组件
@Subcomponent(modules = ServiceModule.class)
interface ServiceComponent {// 定义注入方法,用于将依赖对象注入到目标对象中void inject(MyService service);@Subcomponent.Builderinterface Builder {ServiceComponent build();Builder serviceModule(ServiceModule module);}
}// 自定义 Service 类
public class MyService extends Service {// 使用 @Inject 注解标记需要注入的字段@Inject@ServiceSpecificDependencyServiceDependency serviceDependency;@InjectString appString;@Overridepublic void onCreate() {super.onCreate();// 获取父组件实例AppComponent appComponent = ((MyApplication) getApplication()).getAppComponent();// 创建子组件实例ServiceComponent serviceComponent = appComponent.serviceComponentBuilder().serviceModule(new ServiceModule(this)).build();// 使用子组件实例将依赖对象注入到目标对象中serviceComponent.inject(this);// 调用依赖对象的方法serviceDependency.doServiceWork();System.out.println(appString);}@Overridepublic IBinder onBind(Intent intent) {return null;}
}// 自定义 Application 类
class MyApplication extends android.app.Application {private AppComponent appComponent;@Overridepublic void onCreate() {super.onCreate();appComponent = DaggerAppComponent.create();}public AppComponent getAppComponent() {return appComponent;}
}
源码分析
-
父组件
DaggerAppComponent
:和 Activity 中的父组件类似,负责创建通用的依赖对象(如appString
),并提供创建子组件的方法。 -
子组件
DaggerServiceComponent
:
java
public final class DaggerServiceComponent implements ServiceComponent {private final DaggerAppComponent parentComponent;private final ServiceModule serviceModule;private final ServiceDependency serviceDependency;private DaggerServiceComponent(DaggerAppComponent parentComponent, ServiceModule serviceModule) {this.parentComponent = parentComponent;this.serviceModule = serviceModule;this.serviceDependency = serviceModule.provideServiceDependency();}@Overridepublic void inject(MyService service) {service.serviceDependency = serviceDependency;service.appString = parentComponent.appString;}
}
子组件持有父组件的引用,能够获取父组件的依赖(如 appString
),同时从自身的模块中获取与 Service 相关的依赖(如 serviceDependency
),并将这些依赖注入到目标 Service 中。
9.3 在 Repository 模式中的应用
在 Android 开发的 MVVM、MVP 等架构模式里,Repository 模式常用于管理数据的获取和存储。Dagger2 的辅助工具模块能够助力管理 Repository 相关的依赖对象。
9.3.1 多绑定在 Repository 中的使用
假设我们有多种数据来源(如网络、本地数据库),可以使用多绑定将这些数据来源的 Repository 绑定到一个集合中。
以下是一个使用多绑定的 Repository 示例:
java
import javax.inject.Inject;
import dagger.Module;
import dagger.Provides;
import dagger.Component;
import dagger.multibindings.IntoSet;
import java.util.Set;// 定义一个 Repository 接口
interface DataRepository {void fetchData();
}// 网络数据 Repository 实现类
class NetworkDataRepository implements DataRepository {@Overridepublic void fetchData() {System.out.println("Fetching data from network");}
}// 本地数据 Repository 实现类
class LocalDataRepository implements DataRepository {@Overridepublic void fetchData() {System.out.println("Fetching data from local");}
}// 模块类
@Module
class RepositoryModule {// 将网络数据 Repository 添加到集合中@Provides@IntoSetpublic DataRepository provideNetworkDataRepository() {return new NetworkDataRepository();}// 将本地数据 Repository 添加到集合中@Provides@IntoSetpublic DataRepository provideLocalDataRepository() {return new LocalDataRepository();}
}// 组件接口
@Component(modules = RepositoryModule.class)
interface RepositoryComponent {// 定义注入方法,用于将依赖对象注入到目标对象中void inject(DataManager manager);
}// 数据管理类
class DataManager {private final Set<DataRepository> dataRepositories;@Injectpublic DataManager(Set<DataRepository> dataRepositories) {this.dataRepositories = dataRepositories;}public void fetchAllData() {for (DataRepository repository : dataRepositories) {repository.fetchData();}}
}public class RepositoryExample {public static void main(String[] args) {// 创建组件实例RepositoryComponent repositoryComponent = DaggerRepositoryComponent.create();// 创建目标对象实例DataManager dataManager = new DataManager(null);// 使用组件实例将依赖对象注入到目标对象中repositoryComponent.inject(dataManager);// 调用目标对象的方法dataManager.fetchAllData();}
}
源码分析
- 组件
DaggerRepositoryComponent
:
java
public final class DaggerRepositoryComponent implements RepositoryComponent {private final RepositoryModule repositoryModule;private final java.util.Set<DataRepository> dataRepositorySet;private DaggerRepositoryComponent(RepositoryModule repositoryModule) {this.repositoryModule = repositoryModule;// 创建一个可变的 Set 集合java.util.Set<DataRepository> mutableSet = new java.util.LinkedHashSet<>();// 添加网络数据 RepositorymutableSet.add(repositoryModule.provideNetworkDataRepository());// 添加本地数据 RepositorymutableSet.add(repositoryModule.provideLocalDataRepository());// 将可变集合转换为不可变集合this.dataRepositorySet = java.util.Collections.unmodifiableSet(mutableSet);}public static RepositoryComponent create() {return new DaggerRepositoryComponent(new RepositoryModule());}@Overridepublic void inject(DataManager manager) {// 将 Set 集合注入到目标对象中new DataManager(dataRepositorySet);}
}
在组件的构造函数中,创建一个 Set
集合,并将所有使用 @IntoSet
注解提供的 DataRepository
实例添加到该集合中,最后将集合注入到 DataManager
中。
9.3.2 限定符在 Repository 中的使用
当存在多个不同类型的 Repository 时,可以使用限定符来区分它们。
以下是一个使用限定符的 Repository 示例:
java
import javax.inject.Inject;
import dagger.Module;
import dagger.Provides;
import dagger.Component;// 定义一个 Repository 接口
interface UserRepository {void getUserData();
}// 定义限定符注解
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@interface RemoteUserRepository {}@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@interface LocalUserRepository {}// 远程用户数据 Repository 实现类
class RemoteUserDataRepository implements UserRepository {@Overridepublic void getUserData() {System.out.println("Getting user data from remote");}
}// 本地用户数据 Repository 实现类
class LocalUserDataRepository implements UserRepository {@Overridepublic void getUserData() {System.out.println("Getting user data from local");}
}// 模块类
@Module
class UserRepositoryModule {// 提供远程用户数据 Repository@Provides@RemoteUserRepositorypublic UserRepository provideRemoteUserRepository() {return new RemoteUserDataRepository();}// 提供本地用户数据 Repository@Provides@LocalUserRepositorypublic UserRepository provideLocalUserRepository() {return new LocalUserDataRepository();}
}// 组件接口
@Component(modules = UserRepositoryModule.class)
interface UserRepositoryComponent {// 定义注入方法,用于将依赖对象注入到目标对象中void inject(UserDataManager manager);
}// 用户数据管理类
class UserDataManager {private final UserRepository remoteUserRepository;private final UserRepository localUserRepository;@Injectpublic UserDataManager(@RemoteUserRepository UserRepository remoteUserRepository,@LocalUserRepository UserRepository localUserRepository) {this.remoteUserRepository = remoteUserRepository;this.localUserRepository = localUserRepository;}public void fetchAllUserData() {remoteUserRepository.getUserData();localUserRepository.getUserData();}
}public class UserRepositoryExample {public static void main(String[] args) {// 创建组件实例UserRepositoryComponent userRepositoryComponent = DaggerUserRepositoryComponent.create();// 创建目标对象实例UserDataManager userDataManager = new UserDataManager(null, null);// 使用组件实例将依赖对象注入到目标对象中userRepositoryComponent.inject(userDataManager);// 调用目标对象的方法userDataManager.fetchAllUserData();}
}
源码分析
- 组件
DaggerUserRepositoryComponent
:
java
public final class DaggerUserRepositoryComponent implements UserRepositoryComponent {private final UserRepositoryModule userRepositoryModule;private DaggerUserRepositoryComponent(UserRepositoryModule userRepositoryModule) {this.userRepositoryModule = userRepositoryModule;}public static UserRepositoryComponent create() {return new DaggerUserRepositoryComponent(new UserRepositoryModule());}@Overridepublic void inject(UserDataManager manager) {// 根据限定符获取对应的依赖对象UserRepository remoteUserRepository = userRepositoryModule.provideRemoteUserRepository();UserRepository localUserRepository = userRepositoryModule.provideLocalUserRepository();// 将依赖对象注入到目标对象中new UserDataManager(remoteUserRepository, localUserRepository);}
}
在组件的注入方法中,根据限定符调用相应的提供依赖对象的方法,确保将正确的依赖对象注入到 UserDataManager
中。
十、辅助工具模块的未来发展趋势
10.1 与 Kotlin 的深度融合
随着 Kotlin 在 Android 开发中占据越来越重要的地位,Dagger2 的辅助工具模块很可能会与 Kotlin 进行更深度的融合。例如,提供更符合 Kotlin 语言习惯的 API,利用 Kotlin 的协程、扩展函数等特性来简化依赖注入的代码。同时,借助 Kotlin 的编译时注解处理能力,进一步优化代码生成的效率和质量。
10.2 对 Android 架构组件的全面支持
随着 Android 架构组件(如 ViewModel、LiveData、Room 等)的不断发展和完善,Dagger2 的辅助工具模块将提供更全面的支持。例如,能够更方便地将 ViewModel 的依赖注入集成到 Dagger2 的体系中,通过多绑定和限定符管理不同类型的 LiveData 数据源,以及与 Room 数据库的 DAO 接口进行无缝集成,实现数据库操作的依赖注入。
10.3 性能和代码生成的持续优化
未来,Dagger2 的辅助工具模块将持续在性能和代码生成方面进行优化。一方面,通过改进注解处理器的算法,减少生成代码的体积和复杂度,提高编译速度;另一方面,利用最新的 Java 或 Kotlin 语言特性,生成更高效、更简洁的依赖注入代码,降低运行时的开销。
10.4 与其他框架的集成增强
Dagger2 可能会与更多的第三方框架进行集成,如 Retrofit、OkHttp、RxJava 等。通过辅助工具模块,开发者可以更轻松地将这些框架的依赖对象集成到 Dagger2 的依赖注入体系中,实现更高效的代码组织和管理。
十一、总结
Dagger2 的辅助工具模块为 Android 开发者提供了丰富而强大的功能,通过多绑定、限定符、依赖替换和子组件等特性,能够显著提高代码的可维护性、可测试性和灵活性。在实际开发中,开发者可以根据具体的需求,合理运用这些辅助工具,优化代码结构,降低代码的耦合度。
同时,在使用辅助工具模块时,需要注意一些问题,如避免不必要的多绑定和限定符使用,防止子组件作用域冲突等。通过掌握调试和错误处理技巧,能够快速定位和解决开发过程中遇到的问题。
随着 Android 开发技术的不断发展,Dagger2 的辅助工具模块也将不断演进和完善,为开发者带来更多的便利和惊喜。深入理解和掌握这些辅助工具模块的原理和使用方法,将有助于开发者编写出更加优秀的 Android 应用程序。
相关文章:
Android Dagger2 框架辅助工具模块深度剖析(六)
一、引言 在 Android 开发领域,依赖注入(Dependency Injection,简称 DI)作为一种至关重要的设计模式,能显著降低代码间的耦合度,提升代码的可测试性与可维护性。Dagger2 作为一款强大的依赖注入框架&#…...
LVGL第三方库的使用(中文库)
一、第三方库文档 3rd party libraries(第三方库) — LVGL 文档 FreeType 中文字库 SDL 模拟器使用freetype中文字库 1.开启字库 2.安装freetype 字库 sudo apt-get update sudo apt-get install libfreetype6-dev 3.修改makefile 添加字库 4.显示中…...
【愚公系列】《高效使用DeepSeek》009-PPT大纲自动生成
标题详情作者简介愚公搬代码头衔华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主,腾讯云内容共创官,掘金优秀博主,亚马逊技领云博主,51CTO博客专家等。近期荣誉2022年度…...
使用easyexcel实现单元格样式设置和下拉框设置
1.单元格样式设置 1.1实体类 public class DemoData {ExcelProperty("PK")private String name;ExcelProperty("年龄")private int age;// 必须提供无参构造方法public DemoData() {}public DemoData(String name, int age) {this.name name;this.age …...
ngx_conf_read_token
file_size ngx_file_size(&cf->conf_file->file.info); 此时 file_size2656 当然还是和上次一样 for ( ;; ) {if (b->pos > b->last) { 此时 b->pos 0x57759a8b77f4 b->last 0x57759a8b8230 b->start0x57759a8b77d0 条件不成立 ch *b->po…...
Certbot实现SSL免费证书自动续签(CentOS 7 + nginx/apache)
在 CentOS 上,你可以使用 Let’s Encrypt 提供的 Certbot 工具来申请和自动续约免费的 SSL 证书。 1. 安装 Certbot CentOS 7 安装 EPEL 和 Certbot yum install -y epel-release yum install -y certbot python3-certbot-nginx如果使用的是 Apache: …...
【使用 Java 调用命令行工具:完整指南】
在 Java 中调用命令行工具是一个常见的需求,尤其是在需要与外部程序交互或执行系统命令时。本文将详细介绍如何使用 Java 调用命令行工具,并提供一个完整的示例来演示如何下载视频。 1. 为什么需要调用命令行工具? 命令行工具通常提供了强大…...
pythonSTL---sys
sys 是 Python 标准库中的一个内置模块,它提供了许多与 Python 解释器和系统环境进行交互的功能。 sys方法 1. 导入 sys 模块 在使用 sys 库的功能之前,需要先导入它: import sys2. 命令行参数 (sys.argv) sys.argv 是一个包含命令行参数…...
数据分布偏移检测:保障模型在生产环境中的稳定性
数据分布偏移检测:保障模型在生产环境中的稳定性 引言 在机器学习系统从开发环境部署到生产环境的过程中,数据分布偏移问题是影响模型性能的主要挑战之一。当训练数据与生产环境中的数据分布不一致时,即使是经过精心调优的模型也可能表现出明显的性能下降。本文将深入探讨…...
redis删除与先判断再删除的区别
在Redis中,“先判断存在再删除”与“直接删除”的区别主要体现在操作效率、原子性保障、并发安全性三个方面,具体对比如下: 1. 操作效率 直接删除:仅需执行DEL命令一次,无论键是否存在均直接操作…...
3.6、数字签名
目录 数字签名数字签名与验证过程 数字签名 数字签名是签名者使用自己的私钥对待签名数据的哈希值做密码运算得到的一个结果 第一签名者用自己的私钥来对我们待签数据的哈希值进行签名,直接对数据进行签名其实也是可以的,只是对数据签名,这…...
华为手机助手输入连接码时光标乱跳
问题复现:输入12345678,光标自动跳转导致连接码出现乱序情况。 千万别试着找出规律,已试动态规律非大牛误轻试 问题原因: 想啥呢?华哥的软件又不是我开发我要Know Why干啥 我只需关心解决方案 (可能时输入…...
本地化部署Deepseek关于Ollama 安全加固方案(新手易学)
本地化部署Deepseek关于Ollama 安全加固方案(新手易学) 本方案针对使用ChatBox调用Ollama部署DeepSeek-R1:14b模型时的安全防护需求,提供四重防护措施。 🔒 一、关闭外网访问(关键步骤) 1. 修改监听地址 …...
C++ STL算法函数 —— 应用及其操作实现
一、STL算法函数分类概述 STL算法库提供了大量实用函数,按功能可分为以下五类: 1. 不修改序列的操作 定义:这些算法不会改变容器中的元素,仅对数据进行查询或统计。 典型函数: 函数功能示例find(first, last, value…...
AI数字人:口播与唇形同步的福音,支持本地部署/批量生成/口齿清晰
Heygem:开源前端界面的老六玩家 好消息!Heygem 在 GitHub 上开源了!不过,嘻嘻,只是前端界面开源,感觉更像是来 GitHub 刷一波知名度。不过这依然是个值得关注的工具。让我们先来看看它的官方介绍ÿ…...
【鸿蒙】封装日志工具类 ohos.hilog打印日志
封装一个ohos.hilog打印日志 首先要了解hilog四大日志类型: info、debug、warm、error 方法中四个参数的作用 domain: number tag: string format: string ...args: any[ ] 实例: //普通的info日志,使用info方法来打印 //第一个参数 : 0x0…...
附下载 | 2024 OWASP Top 10 基础设施安全风险.pdf
《2024 OWASP Top 10 基础设施安全风险》报告,由OWASP(开放网络应用安全项目)发布,旨在提升企业和组织对基础设施安全风险、威胁与漏洞的意识,并提供高质量的信息和最佳实践建议。报告列出了2024年最重要的10大基础设施…...
Chatbox通过百炼调用DeepSeek
解决方案链接:评测|零门槛,即刻拥有DeepSeek-R1满血版 方案概览 本方案以 DeepSeek-R1 满血版为例进行演示,通过百炼模型服务进行 DeepSeek 开源模型调用,可以根据实际需求选择其他参数规模的 DeepSeek 模型。百炼平台…...
Vue前端项目部署到宝塔面板的详细过程
目录 前言 一、项目的打包与上传 1、修改前端项目 2、关于test环境的补充修改 3、打包前端项目 二、添加站点,启动项目 三、总结 前言 书接上回 SpringBoot项目部署到宝塔面板的详细过程-CSDN博客 本次以SmartAdmin的项目为例,通过宝塔面板部署…...
免费高质量贴图(Textures) 网站推荐
以下是一些提供 免费或高质量贴图(Textures) 的网站,包括 PBR 贴图、HDRI 贴图、材质等,适用于 Three.js、Blender、Unity、Unreal Engine 等软件。 🌍 1. Poly Haven(https://polyhaven.com/)⭐…...
C++进阶——map和set的使用
目录 1、序列式容器和关联式容器 2、set系列的使用 2.1 set和multiset的参考文档 2.2 set类的介绍 2.3 set的构造和迭代器 2.4 set的增删查 2.5 set的insert和迭代器遍历 2.6 set的find和erase 2.7 set的lower_bound和upper_bound 2.8 multiset和set的差异 2.9 349.…...
AI机器学习---Anaconda
Anaconda指的是一个开源的Python发行版本,其包含了Conda、Python等180多个科学包及其依赖项。因为包含了大量的科学包,Anaconda 的下载文件比较大(约 531 MB),如果只需要某些包,或者需要节省带宽或存储空间…...
如何在Futter开发中做性能优化?
目录 1. 避免不必要的Widget重建 问题:频繁调用setState()导致整个Widget树重建。 优化策略: 2. 高效处理长列表 问题:ListView一次性加载所有子项导致内存暴涨。 优化策略: 3. 图片加载优化 问题:加载高分辨率…...
leetcode 75.颜色分类(荷兰国旗问题)
题目描述 题目分析 本题是经典的「荷兰国旗问题」,由计算机科学家 Edsger W. Dijkstra 首先提出。 要想单独解决这道题本身还是很简单的,统计0、1、2的数量然后按顺序赋值,或者手写一个冒泡排序,whatever。 但是在这一题中我们主…...
JVM--垃圾回收
垃圾回收的概念 垃圾回收主要针对的是堆中的对象,堆是一个共享的区域,创建的对象和数组都放在这个位置。但是我们不能一直的创建对象,也不是所有的对象能一直存放,如果不进行垃圾回收,内存迟早会耗尽,及时…...
Spring boot3-Http Interface: 声明式编程
来吧 1.首先引入pom.xml依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId> </dependency> 2.创建WebClientController控制器 import com.atguigu.boot3_07_http.serv…...
springboot EasyExcel 实现导入导出
1. 添加依赖 确保 Maven 依赖中包含 EasyExcel 3.0.5: <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.0.5</version></dependency><!-- excel工具 --><dep…...
基于RAGFlow本地部署DeepSpeek-R1大模型与知识库:从配置到应用的全流程解析
作者:后端小肥肠 🍊 有疑问可私信或评论区联系我。 🥑 创作不易未经允许严禁转载。 姊妹篇: DeepSpeek服务器繁忙?这几种替代方案帮你流畅使用!(附本地部署教程)-CSDN博客 10分钟上手…...
spring 创建单例 Bean 源码分析
一、创建单例Bean 1、创建单例 Bean 通过方法getBean()来创建单例bean。 代码入口: org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons spring boot version:2.6.13 org.springframework.beans.factory…...
GetCurrentTime
在实际编程中难免要获取当前时间并且进行格式化,本文给出了多种 GetCurrentTime() 方法以供选择。 C语言下使用strftime C 语言中可以使用 <time.h> 中的函数来获取和格式化时间 #include <stdio.h> #include <time.h>char* getCurrentTime() …...
HTB 学习笔记 【中/英】《Web 应用简介》P1
📌 这篇文章讲了什么? 介绍了 Web 应用 的概念、架构,以及与传统网站的区别。重点讲解了 Web 安全风险,包括 常见攻击方法(SQL 注入、文件包含、不安全的文件上传等)。介绍了 Web 渗透测试 的重要性&#…...
ROS catkin_make编译报错问题
对问题 CMake Error at graduation_design/CMakeLists.txt:226 (add_dependencies): The dependency target "graduation_design_generate_messages_cpp" of target "listener" does not exist 检查 generate_messages() 是否被注释 对 CMake Error at …...
【结构设计】3D打印创想三维Ender 3 v2
【结构设计】3D打印创想三维Ender 3 v2 文章目录 前言一、Creality Slicer1.2.3打印参数设置二、配件更换1.捆扎绑扎线2.气动接头3D打印机配件插头3.3D打印机配件Ender3pro/V2喷头套件4.读卡器 TF卡5.micro sd卡 三、调平四、参考文章总结 前言 使用工具: 1.创想三…...
并发编程2
接并发编程1 synchronized锁的实现 通过底层指令控制实现,Java提供的一种原子性内置锁,在进入synchronized后会从主内存复制一份共享变量到自己的工作内存,在工作内存中修改完成后,退出时会把工作内存的值写入到主内存ÿ…...
Linux 中 Git 使用指南:从零开始掌握版本控制
目录 1. 什么是 Git? Git 的核心功能: 2. Git 的安装 Ubuntu/Debian 系统: 验证安装: 3.gitee库 4. Git 的首次配置 配置用户名和邮箱: 查看配置: 5. Git 的基本使用 初始化仓库 添加文件到暂存区…...
C# Exe + Web 自动化 (BitComet 绿灯 自动化配置、设置)
BitComet GreenLight,内网黄灯转绿灯 (HighID), 增加p2p连接率提速下载-CSDN博客 前两天写个这个,每次开机关机后要重来一遍很麻烦的索性写个自动化。 先还是按照上面的教程自己制作一遍,留下Luck 以及 路由器相关的 端口记录信息。 (因为自…...
2024年12月CCF-GESP编程能力等级认证C++编程四级真题解析
四级真题的难度: 一、总体难度评价 CCF-GESP编程能力等级认证C++四级真题的难度通常被认为相对较高。它不仅要求考生具备扎实的C++编程基础,还需要考生掌握一定的算法和数据结构知识,以及良好的问题解决能力。 二、具体难度分析 理论知识考察: 单选题和判断题中,会涉…...
谷歌Chrome或微软Edge浏览器修改网页任意内容
在谷歌或微软浏览器按F12,打开开发者工具,切换到console选项卡: 在下面的输入行输入下面的命令回车: document.body.contentEditable"true"效果如下:...
《DeepSeek深度使用教程:开启智能交互新体验》Deepseek深度使用教程
《DeepSeek使用教程:开启智能交互新体验》 在当今数字化时代,人工智能技术正以前所未有的速度改变着我们的生活和工作方式。DeepSeek作为一款强大的人工智能工具,凭借其卓越的自然语言处理能力和多领域应用潜力,受到了众多开发者…...
Dijkstra算法
Dijkstra算法(迪杰斯特拉算法)是一种经典的单源最短路径算法,用于在加权图中找到从一个源点到所有其他顶点的最短路径。它要求图中不能有负权边,因为负权边可能会导致算法的贪心策略失效。 Dijkstra算法的基本思想 Dijkstra算法…...
Python中的静态方法如何使用?
在Python里,类当中的方法可以分为多种不同的类型,其中staticmethod是一个十分有趣而又实用的功能。我们来好好地聊一聊什么是静态方法,它的用途是什么,以及如何在实际应用中使用它们! 首先,定义一下静态方…...
【最后203篇系列】016 Q201架构思考
前言 Q200已经达到了我既定的目标,在最近的3个月,我需要进一步完善,达到可以试产的程度。 在这个过程当中,许多知识和体会一直在变。 qtv200到目前,虽然通过习惯(每晚运行离线策略和比对)方式维持了注意力的集中&…...
小脑萎缩会致命吗?
小脑萎缩,顾名思义,是指小脑的体积减小或结构发生异常,进而影响其正常功能。小脑作为人体重要的协调和运动控制中心,负责维持身体平衡、调节肌肉张力和协调运动等关键功能。当小脑出现萎缩时,患者可能会出现步态不稳、…...
pip install和conda install的区别
这里写目录标题 一、什么是 Python 依赖(Python Dependencies)?1. 依赖的作用2. 如何管理 Python 依赖3. 依赖管理问题4. 依赖锁定总结 二、使用pip安装包venv隔离环境方法 1:使用 venv(推荐)创建虚拟环境激…...
這是我第一次寫關於aapenal服務器管理控制面板的文章
首先我們來認識一下服務器管理面板的所有功能 網站管理功能: 支持創建和管理多個網站。配置虛擬主機(Vhost)和域名綁定。自動安裝常用應用(如WordPress、Joomla等)。 文件管理功能: 文件上傳、…...
requests库的request和response对象的属性和方法
Python requests库 request 参数信息 response 参数信息...
8664蛋糕的美味值
8664蛋糕的美味值 ⭐️难度:中等 🌟考点:枚举 📖 📚 import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in );int n sc.nextInt();int k s…...
【MySQL】数据库简要介绍和简单应用
目录 数据库简要介绍 SQL 的简单应用 需要注意的: 数据库简要介绍 数据库(database)是指长期存储在计算机内,有组织的、可共享的数据集合。它可视为一个电子化的文件柜,用来存储电子文件,用户可以对文件中的数据进行査询、新增、更新、删…...
yolo环境 pytorch环境配置 CUDA安装
我的成功案例:首先安装python 3.12.9的conda虚拟环境 (如果不安装3.12的会报错误ModuleNotFoundError:没有名为“numpy._core”的模块) 然后安装11.8cuda (其实我是可以最高安装12.6的cuda但我实测,太高版…...
camellia redis proxy v1.3.3对redis主从进行读写分离(非写死,自动识别故障转移)
1 概述 camellia-redis-proxy是一款高性能的redis代理(https://github.com/netease-im/camellia),使用netty4开发,主要特性如下: 支持代理到redis-standalone、redis-sentinel、redis-cluster。支持其他proxy作为后端…...