做推广适合哪些网站,广东石油化工建设集团公司网站,个人网站设计模板中文,如何制作网上商城文章目录 演示dagger2使用环境搭建前奏创建指令接口指令路由器创建Main 开始集成Dagger2注解自动生成CommandRouter实例使用自动生成的CommandRouterFactory获取CommandRouter对象 添加第一个指令实现HelloWorld 指令将HelloWorldCommand添加到CommandRouter 依赖接口而不依赖实… 文章目录 演示dagger2使用环境搭建前奏创建指令接口指令路由器创建Main 开始集成Dagger2注解自动生成CommandRouter实例使用自动生成的CommandRouterFactory获取CommandRouter对象 添加第一个指令实现HelloWorld 指令将HelloWorldCommand添加到CommandRouter 依赖接口而不依赖实现修改CommandRouter依赖接口不依赖具体的类提示Dagger获取Command接口CommandRouterFactory添加module获取Command的提示 提取输出创建输出接口修改HelloWorldCommand使用Outputter提供Outputter 添加登录指令创建单参数抽象类SingleArgCommand创建登录指令类LoginCommand创建LoginCommandModule绑定LoginCommand在Component使用LoginCommandModule 一个参数两个指令修改LoginCommandModule修改HelloWorldModuleCommandRouter直接注入多Command 特定类型的操作创建Database登录用户打印余额 保持状态创建存款指令DepositCommand创建DepositCommand对应Module添加Component 先登录后存款引入指令处理器 调整Command修改Component修改CommandLineAtm 创建内嵌指令创建Subcomponent在Component添加subcomponent在LoginCommand添加UserCommandsRouter添加BigDecimalCommand,简化存款指令修改DepositCommand 添加取款指令取款指令工具类Database.Account 添加取款方法在UserCommandsModule添加取款指令定义账户额度限制Provider添加到UserCommandsRouter修改取款指令注入额度限制定义区分数额的修饰符给AmoutModule 提供金额的函数添加注解提取指令构造函数参数添加注解 退出登录退出指令Database.Result 添加退出功能添加UserCommandsModule注册到登录后的指令中 设置取款最大限额WithdrawalLimiter跟踪最大限额和余额变化将WithdrawalLimiter添加到存款指令和取款指令定义SessionScope PerSession注解给WithdrawLimit添加注解给component添加注解 避免重复登录LoginCommand添加是否已登录判断LoginCommandModule声明如何创建OptionalAccount 参考地址: https://dagger.dev/tutorial/01-setup 代码仓库地址https://gitee.com/guchuanhang/dagger-tutorial
演示dagger2使用
完成指令行ATM应用演示dagger2使用。可以跟踪账户余额在控制台接收指令。 deposit 20withdraw 10环境搭建
这里使用的 Intellij IDEA社区版使用Maven进行构建。Maven内容如下
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersiongroupIddagger.example.atm/groupIdartifactIdDagger2ATMApp/artifactIdversion1.0-SNAPSHOT/versionpropertiesmaven.compiler.source8/maven.compiler.sourcemaven.compiler.target8/maven.compiler.targetproject.build.sourceEncodingUTF-8/project.build.sourceEncodingjava.version8/java.version/properties!-- 配置国内源--repositoriesrepositoryidalimaven/idurlhttps://maven.aliyun.com/repository/public/url/repository/repositoriespluginRepositoriespluginRepositoryidalimaven/idurlhttps://maven.aliyun.com/repository/public/url/pluginRepository/pluginRepositories!-- 配置dagger2依赖 --dependenciesdependencygroupIdcom.google.dagger/groupIdartifactIddagger/artifactIdversion2.41/version/dependencydependencygroupIdcom.google.dagger/groupIdartifactIddagger-compiler/artifactIdversion2.41/versionoptionaltrue/optional/dependency/dependencies!-- 配置支持dagger2 生成代码的框架--buildpluginsplugingroupIdorg.apache.maven.plugins/groupIdartifactIdmaven-compiler-plugin/artifactIdversion3.8.1/versionconfigurationsource11/sourcetarget11/target/configurationexecutionsexecutioniddefault-compile/idphasenone/phase/executionexecutioniddefault-testCompile/idphasenone/phase/executionexecutionidjava-compile/idphasecompile/phasegoalsgoalcompile/goal/goals/executionexecutionidjava-test-compile/idphasetest-compile/phasegoalsgoaltestCompile/goal/goals/execution/executions/plugin/plugins/build/project在pom.xml右键Maven-Reload Project. 下载依赖。
前奏
编写一些应用的基本框架代码。刚开始不使用Dagger当应用变得复杂时我们再使用Dagger凸显Dagger优越性。
创建指令接口
创建统一的ATM可以处理的指令接口.
/** Logic to process some user input. */
interface Command {/*** String token that signifies this command should be selected (e.g.:* deposit, withdraw)*/String key();/** Process the rest of the commands words and do something. */Result handleInput(ListString input);/*** This wrapper class is introduced to make a future change easier* even though it looks unnecessary right now.*/final class Result {private final Status status;private Result(Status status) {this.status status;}static Result invalid() {return new Result(Status.INVALID);}static Result handled() {return new Result(Status.HANDLED);}Status status() {return status;}}enum Status {INVALID,HANDLED}
}
指令路由器
创建CommandRouter记录ATM可以处理的所有指令并将指令交给具体的实现类进行处理。
final class CommandRouter {private final MapString, Command commands new HashMap();Result route(String input) {ListString splitInput split(input);if (splitInput.isEmpty()) {return invalidCommand(input);}String commandKey splitInput.get(0);Command command commands.get(commandKey);if (command null) {return invalidCommand(input);}ListString args splitInput.subList(1, splitInput.size());Result result command.handleInput(args);return result.status().equals(Status.INVALID) ?invalidCommand(input) : result;}private Result invalidCommand(String input) {System.out.println(String.format(couldnt understand \%s\. please try again., input));return Result.invalid();}// Split on whitespaceprivate static ListString split(String input) {return Arrays.asList(input.trim().split(\\s));}
}
创建Main
创建Main函数实现上述类的交互。
class CommandLineAtm {public static void main(String[] args) {Scanner scanner new Scanner(System.in);CommandRouter commandRouter new CommandRouter();while (scanner.hasNextLine()) {Command.Result unused commandRouter.route(scanner.nextLine());}}
}
至此完成了项目的准备工作。 feature/prepare
开始集成Dagger2注解
自动生成CommandRouter实例
创建接口添加Component,函数返回CommandRouter实例。
Component
interface CommandRouterFactory {CommandRouter router();
}对项目进行编译 Build-Rebuild Project
java: 警告: 源发行版 11 需要目标发行版 11 “java: 警告: 源发行版 11 需要目标发行版 11”错误解决
E:\IdeaProjects\DeaggerDemo\Dagger2ATMApp\src\main\java\dagger\example\atm\CommandRouterFactory.java:6
java: [Dagger/MissingBinding] dagger.example.atm.CommandRouter cannot be provided without an Inject constructor or an Provides-annotated method.dagger.example.atm.CommandRouter is requested atdagger.example.atm.CommandRouterFactory.router()毕竟有地方注入才能进行返回。
给CommandRouter添加构造函数并用Inject标注。类似声明需要CommandRouter的时候可以调用我进行创建。 Injectpublic CommandRouter(){}对项目进行编译 Build-Rebuild Project
生成的CommandRouterFactory实现类DaggerCommandRouterFactory内部调用new CommandRouter()
package dagger.example.atm;import dagger.internal.DaggerGenerated;DaggerGenerated
final class DaggerCommandRouterFactory implements CommandRouterFactory {
...public CommandRouter router() {return new CommandRouter();}
...
使用自动生成的CommandRouterFactory获取CommandRouter对象
class CommandLineAtm {public static void main(String[] args) {Scanner scanner new Scanner(System.in);CommandRouterFactory commandRouterFactory DaggerCommandRouterFactory.create();CommandRouter commandRouter commandRouterFactory.router();while (scanner.hasNextLine()) {Command.Result unused commandRouter.route(scanner.nextLine());}}
}
此致完成mini Dagger2使用。
概念Component 告诉Dagger实现一个接口或抽象类返回一个或多个对象。Dagger生成的Component实现类命名方式是DaggerYourType(DaggerYourType_NestedType对于内部类)Inject 修饰构造函数告诉Dagger如何去实例化类的对象。feature/init
添加第一个指令
让我们创建第一个指令改变例子不能处理任何指令的的状况。
实现HelloWorld 指令
final class HelloWorldCommand implements Command {InjectHelloWorldCommand() {}Overridepublic String key() {return hello;}Overridepublic Result handleInput(ListString input) {if (!input.isEmpty()) {return Result.invalid();}System.out.println(world!);return Result.handled();}
}将HelloWorldCommand添加到CommandRouter
final class CommandRouter {private final MapString, Command commands new HashMap();InjectCommandRouter(HelloWorldCommand helloWorldCommand) {commands.put(helloWorldCommand.key(), helloWorldCommand);}...
}
对项目进行编译 Build-Rebuild Project 在控制台输入hello就会返回world
hello
world!feature/first_command
依赖接口而不依赖实现
修改CommandRouter构造函数依赖Comand接口而不是具体的类方便以后进行扩展.eg. DepositCommand.
修改CommandRouter依赖接口不依赖具体的类
Inject
CommandRouter(Command command) {commands.put(command.key(), command);
}
...
对项目进行编译 Build-Rebuild Project
E:\IdeaProjects\DeaggerDemo\Dagger2ATMApp\src\main\java\dagger\example\atm\CommandRouterFactory.java:6
java: [Dagger/MissingBinding] dagger.example.atm.Command cannot be provided without an Provides-annotated method.dagger.example.atm.Command is injected atdagger.example.atm.CommandRouter(command)dagger.example.atm.CommandRouter is requested atdagger.example.atm.CommandRouterFactory.router()找不到Command实现类需要给Dagger更多的提示信息。
提示Dagger获取Command接口
创建Module提示Dagger如何创建Command
Module
abstract class HelloWorldModule {Bindsabstract Command helloWorldCommand(HelloWorldCommand command);
}
CommandRouterFactory添加module获取Command的提示
Component(modules HelloWorldModule.class)
interface CommandRouterFactory {CommandRouter router();
}
对项目进行编译 Build-Rebuild Project
ok,恢复正常了。 Dagger需要Command但不知道如何创建时发现Binds返回Command就会将helloWorldCommand()参数转化为Command进行注入。
概念
Binds 只能修饰抽象函数有些仅有一个参数与返回值是同一种类型。只能出现在Module修饰的接口或抽象类中
Module修饰的类是绑定方法的集合被Binds、Providers等
Inject修饰的构造函数不需要声明在Module修饰的类中
depending_interface
提取输出
现在HelloWorldCommand直接使用System.out.println()输出。为了方便扩展添加一层抽象Outputter避免直接调用System.out。这样以后更换输出函数避免修改HelloWorldCommand。
创建输出接口
interface Outputter {void output(String output);
}
修改HelloWorldCommand使用Outputter
private final Outputter outputter;Inject
HelloWorldCommand(Outputter outputter) {this.outputter outputter;
}...Override
public Result handleInput(ListString input) {if (!input.isEmpty()) {return Result.invalid();}outputter.output(world!);return Result.handled();
}
提供Outputter
通过前面我们可以知道对于接口的注入需要进行下面三个步骤:
创建被Inject修饰构造函数的实现类
public class SystemOutOutputter implements Outputter{Injectpublic SystemOutOutputter(){}Overridepublic void output(String output) {System.out.println(output);}
}
创建使用接口说明的Module、Binds函数
Module
public abstract class OutputterModule {Bindsabstract Outputter getOutputer(SystemOutOutputter outputter);
}
将Module添加到Component实现Component依赖的查找
Component(modules {HelloWorldModule.class, OutputterModule.class})
interface CommandRouterFactory {CommandRouter router();
}
由于SystemOutOutputter实现过于简单我们也可以直接在Module中进行实现
Module
public abstract class OutputterModule {Providesstatic Outputter getOutputer() {return System.out::println;}
}
正常的写法是下面的样式,上面是lambda简化后的效果。
Module
public abstract class OutputterModule {Providesstatic Outputter getOutputer() {return new Outputter() {Overridepublic void output(String output) {System.out.println(output);}};}
}
完成了Outputter抽象。加入以后需要进行记录什么的仅仅在一处修改就可以了。
概念
Provides 相对于BindsProviders修饰普通方法非抽象函数。函数返回值就是其提供的对象参数就是其依赖。
Providers 修饰的函数可以包含复杂的逻辑只要可以返回特定的对象。不一定需要每一次都返回新的实例。这正是Dagger依赖注入框架的优点
当请求一个对象时是否创建新实例是框架的实现细节。以后将使用提供而不是创建更为精确。feature/ouputter
添加登录指令
创建单参数抽象类SingleArgCommand
/** Abstract command that accepts a single argument. */
abstract class SingleArgCommand implements Command {Overridepublic final Result handleInput(ListString input) {return input.size() 1 ? handleArg(input.get(0)) : Result.invalid();}/** Handles the single argument to the command. */protected abstract Result handleArg(String arg);
}
创建登录指令类LoginCommand
final class LoginCommand extends SingleArgCommand {private final Outputter outputter;InjectLoginCommand(Outputter outputter) {this.outputter outputter;}Overridepublic String key() {return login;}Overridepublic Result handleArg(String username) {outputter.output(username is logged in.);return Result.handled();}
}创建LoginCommandModule绑定LoginCommand
Module
abstract class LoginCommandModule {Bindsabstract Command loginCommand(LoginCommand command);
}
在Component使用LoginCommandModule Component(modules {LoginCommandModule.class, SystemOutModule.class})interface CommandRouterFactory {CommandRouter router();}对项目进行编译 Build-Rebuild Project Run
login gch
gch is logged in.feature/login_command
一个参数两个指令
目前为止 CommandRouter一次仅仅支持一个指令.如何让他一次支持多个指令如果将LoginCommandModule.class、HelloWorldModule.class 同时加入Component
对项目进行编译 Build-Rebuild Project
E:\IdeaProjects\DeaggerDemo\Dagger2ATMApp\src\main\java\dagger\example\atm\CommandRouterFactory.java:6
java: [Dagger/DuplicateBindings] dagger.example.atm.Command is bound multiple times:Binds dagger.example.atm.Command dagger.example.atm.HelloWorldModule.helloWorldCommand(dagger.example.atm.HelloWorldCommand)Binds dagger.example.atm.Command dagger.example.atm.LoginCommandModule.loginCommand(dagger.example.atm.LoginCommand)dagger.example.atm.Command is injected atdagger.example.atm.CommandRouter(command)dagger.example.atm.CommandRouter is requested atdagger.example.atm.CommandRouterFactory.router()把Dagger整迷糊了仅仅需要一个Command却提供了两个。 下面使用IntoMap支持多Command注入。
修改LoginCommandModule
Module
abstract class LoginCommandModule {BindsIntoMapStringKey(login)abstract Command loginCommand(LoginCommand command);
}
修改HelloWorldModule
Module
abstract class HelloWorldModule {BindsIntoMapStringKey(hello)abstract Command helloWorldCommand(HelloWorldCommand command);
}
StringKey IntoMap,告诉Dagger如何创建MapString,Command.
注意: 现在StringKey指定了指令的keyCommand接口key()可以删掉了。
CommandRouter直接注入多Command
final class CommandRouter {private MapString, Command commands ;Injectpublic CommandRouter(MapString,Command map){this.commands map;}
...
}对项目进行编译 Build-Rebuild Project Run
login gch
gch is logged in.
hello
world!概念
IntoMap 创建特定类型的Map其key使用StringKey或IntKey指定dagger确保没有重复的key
IntoSet 创建Set集合.可以和Binds、Providers联合创建Set集合
IntoMap、IntoSet都是引入多绑定(集合包含多个元素并且元素来自不同的绑定方法)的方式.feature/multibinding
特定类型的操作
既然用户可以进行登录了让我们添加一些只有登录用户才能进行的操作。
创建Database
跟踪每一个用户和他们的账户余额。
class Database {private final MapString, Account accounts new HashMap();InjectDatabase() {}Account getAccount(String username) {return accounts.computeIfAbsent(username, Account::new);}static final class Account {private final String username;private BigDecimal balance BigDecimal.ZERO;Account(String username) {this.username username;}String username() {return username;}BigDecimal balance() {return balance;}void deposit(BigDecimal amount) {balance balance.add(amount);}}
}
登录用户打印余额
将database注入LoginCommand, 用户登录后打印账户余额。
final class LoginCommand extends SingleArgCommand {private final Database database;private final Outputter outputter;InjectLoginCommand(Database database, Outputter outputter) {this.database database;this.outputter outputter;}Overridepublic String key() {return login;}Overridepublic Result handleArg(String username) {Database.Account account database.getAccount(username);outputter.output(username is logged in with balance: account.balance());return Result.handled();}
}
对项目进行编译 Build-Rebuild Project Run
login gch
gch is logged in with balance: 0
login gch
gch is logged in with balance: 0现在登录可以打印余额了不过可以重复登录。这个问题我们接下来会进行解决。 feature/login_print
保持状态
代码里面有点小问题你发现了吗 为了方便找到他我们添加一些代码。
创建存款指令DepositCommand
final class DepositCommand implements Command {private final Database database;private final Outputter outputter;InjectDepositCommand(Database database, Outputter outputter) {this.outputter outputter;this.database database;}Overridepublic Result handleInput(ListString input) {if (input.size() ! 2) {return Result.invalid();}Database.Account account database.getAccount(input.get(0));account.deposit(new BigDecimal(input.get(1)));outputter.output(account.username() now has: account.balance());return Result.handled();}
}
创建DepositCommand对应Module
Module
abstract class UserCommandsModule {BindsIntoMapStringKey(deposit)abstract Command depositCommand(DepositCommand command);
}
添加Component
Component(modules {LoginCommandModule.class, HelloWorldModule.class,UserCommandsModule.class, OutputterModule.class})
interface CommandRouterFactory {CommandRouter router();
}对项目进行编译 Build-Rebuild Project Run
deposit gch 2
gch now has: 2
login gch
gch is logged in with balance: 0明明刚存入2怎么登录显示余额0 为了让情况更明显一些在Database、LoginCommand、DepositCommand构造函数添加System.out.println(Creating a new this);,为了让打印更清晰在LoginCommand、DepositCommand构造函数再添加System.out.println(database: database);
对项目进行编译 Build-Rebuild Project Run
Creating a new org.Database71e7a66b
Creating a new org.LoginCommand246ae04d
database:org.Database71e7a66b
Creating a new org.Database2ef9b8bc
Creating a new org.DepositCommand5d624da6
database:org.Database2ef9b8bcDagger为LoinCommand、DepositCommand分别创建了Database对象。为了告诉Dagger这两个使用同一个Database对象我们需要添加Singleton注解。
给Database添加Singleton注解给Component添加Singleton注解声明Singleton修饰的Database实例在其他依赖中进行共享
Singleton
final class Database { ... }Singleton
Component
interface CommandRouterFactory {...
}对项目进行编译 Build-Rebuild Project Run
Creating a new org.Database591f989e
Creating a new org.LoginCommand4cb2c100
database:org.Database591f989e
Creating a new org.DepositCommand77b52d12
database:org.Database591f989e现在是同一个Database实例啦。
概念
Singleton 对于每一个component仅仅创建一个对象实例适用于Inject修饰构造函数的类、Binds修饰的方法、Providers修饰的方法为什么将compoment添加Singleton修饰,现在还不是挺清楚接下来就更清晰啦。 feature/singleton
先登录后存款
为了实现先登录后存款的宏大目标让我们先对代码进行重构。
引入指令处理器
引入CommandProcessor,包含若干CommandRouters将CommandRouter添加到CommandProcessor上就支持里面的指令集删除就返回到之前支持的指令集。
Singleton
final class CommandProcessor {private final DequeCommandRouter commandRouterStack new ArrayDeque();InjectCommandProcessor(CommandRouter firstCommandRouter) {commandRouterStack.push(firstCommandRouter);}Command.Status process(String input) {Command.Result result commandRouterStack.peek().route(input);if (result.status().equals(Command.Status.INPUT_COMPLETED)) {commandRouterStack.pop();return commandRouterStack.isEmpty()? Command.Status.INPUT_COMPLETED : Command.Status.HANDLED;}result.nestedCommandRouter().ifPresent(commandRouterStack::push);return result.status();}
}
调整Command
interface Command {/*** Process the rest of the commands words and do something.*/Result handleInput(ListString input);/*** This wrapper class is introduced to make a future change easier* even though it looks unnecessary right now.*/final class Result {private final Status status;private final OptionalCommandRouter nestedCommandRouter;private Result(Status status, OptionalCommandRouter nestedCommandRouter) {this.status status;this.nestedCommandRouter nestedCommandRouter;}static Result invalid() {return new Result(Status.INVALID, Optional.empty());}static Result handled() {return new Result(Status.HANDLED, Optional.empty());}Status status() {return status;}public OptionalCommandRouter nestedCommandRouter() {return nestedCommandRouter;}static Result enterNestedCommandSet(CommandRouter nestedCommandRouter) {return new Result(Status.HANDLED, Optional.of(nestedCommandRouter));}}enum Status {INVALID,HANDLED,INPUT_COMPLETED}
}修改Component
删除CommandRouterFactory,创建CommandProcessorFactory其内容如下
Singleton
Component(modules {LoginCommandModule.class, HelloWorldModule.class, UserCommandsModule.class, OutputterModule.class})
interface CommandProcessorFactory {CommandProcessor commandProcessor();
}
修改CommandLineAtm
class CommandLineAtm {public static void main(String[] args) {Scanner scanner new Scanner(System.in);CommandProcessorFactory commandProcessorFactory DaggerCommandProcessorFactory.create();CommandProcessor commandProcessor commandProcessorFactory.commandProcessor();while (scanner.hasNextLine()) {Command.Status unused commandProcessor.process(scanner.nextLine());}}
}
feature/refactor
创建内嵌指令
Dagger 已经可以做到从 MapString, Command创建CommandRouter但是这些指令任何用户都可用。并且每个指令都要指定用户名必须每次都输入用户名。下面让我们添加一个用户session和指令组只对完成登录的用户可用。但是怎么创建两个拥有不同Map的CommandRouters一个已登录用户使用一个未登录用户使用Subcomponent
Subcomponent和Component相似他有dagger实现的抽象函数可以添加Modules.此外有一个parent Component他可以访问parent Component所有类型但是parent 不能访问他的。类似于类的继承。
接下来创建 Subcomponent添加仅供登录用户使用的指令。共享CommandProcessorFactory Component中的Database实例方便实现一个特定用户的存款和取钱。
创建Subcomponent
Subcomponent(modules UserCommandsModule.class)
interface UserCommandsRouter {CommandRouter router();Subcomponent.Factoryinterface Factory {UserCommandsRouter create(BindsInstance Account account);}Module(subcomponents UserCommandsRouter.class)interface InstallationModule {}
} Subcomponent modules指定创建实例时Dagger可以查找依赖的类Subcomponent独有和Component一样modules可以指定多个;Subcomponent内函数返回值指定希望Dagger创建的类的实例Subcomponent.Factory注解标注subcomponent工厂接口其只有一个方法并且要返回subcomponent对象。BindsInstance 注解参数Account在subcomponent 中Inject标注的构造函数、Binds标注的方法、Providers标注的方法都可以获取该Account。Module(subcomponents UserCommandsRouter.class)定义的Module引入Component就将subcomponent和Component建立了关系。在Component添加subcomponent
Singleton
Component(modules {...UserCommandsRouter.InstallationModule.class,})
interface CommandProcessorFactory {CommandProcessor commandProcessor();
}
在LoginCommand添加UserCommandsRouter
final class LoginCommand extends SingleArgCommand {private final Database database;private final Outputter outputter;private final UserCommandsRouter.Factory userCommandsRouterFactory;InjectLoginCommand(Database database, Outputter outputter,UserCommandsRouter.Factory userCommandsRouterFactory) {this.database database;this.outputter outputter;this.userCommandsRouterFactory userCommandsRouterFactory;}Overridepublic Result handleArg(String username) {Database.Account account database.getAccount(username);outputter.output(username is logged in with balance: account.balance());return Result.enterNestedCommandSet(userCommandsRouterFactory.create(account).router());}
}
现在只有登录之后才能进行存款我们可以从指令中移除username因为BindsInstance已经提供账号。
添加BigDecimalCommand,简化存款指令
/*** Abstract {link Command} that expects a single argument that can be converted to {link* BigDecimal}.*/
abstract class BigDecimalCommand extends SingleArgCommand {private final Outputter outputter;protected BigDecimalCommand(Outputter outputter) {this.outputter outputter;}Overrideprotected final Result handleArg(String arg) {BigDecimal amount tryParse(arg);if (amount null) {outputter.output(arg is not a valid number);} else if (amount.signum() 0) {outputter.output(amount must be positive);} else {handleAmount(amount);}return Result.handled();}private static BigDecimal tryParse(String arg) {try {return new BigDecimal(arg);} catch (NumberFormatException e) {return null;}}/** Handles the given (positive) {code amount} of money. */protected abstract void handleAmount(BigDecimal amount);
}
修改DepositCommand
final class DepositCommand extends BigDecimalCommand {private final Database.Account account;private final Outputter outputter;InjectDepositCommand(Database.Account account, Outputter outputter) {super(outputter);this.account account;this.outputter outputter;}Overrideprotected void handleAmount(BigDecimal amount) {account.deposit(amount);outputter.output(account.username() now has: account.balance());}
}
Subcomponent修饰的类和Component修饰的类一样有一个工厂方法返回对象
subcomponent可以使用modules提供依赖
submomponent总是有一个 parent component或者parent subcomponent
subcomponent可以借助parent component资源获取依赖但是parent 不能借助于subcomponent资源
Subcomponent.Factory在parent component中调用创建subcomponent实例
类似的Component.Factory适用于Component修饰的类BindsInstance同样实现运行时参数注入。对项目进行编译 Build-Rebuild Project Run
deposit 1000
couldnt understand deposit 1000. please try again.
login guchuanhang
guchuanhang is logged in with balance: 0
deposit 100
guchuanhang now has: 100feature/subcomponent
添加取款指令
取款指令工具类
final class WithdrawCommand extends BigDecimalCommand {private final Database.Account account;private final Outputter outputter;InjectWithdrawCommand(Database.Account account, Outputter outputter) {super(outputter);this.account account;this.outputter outputter;}Overrideprotected void handleAmount(BigDecimal amount) {account.withdraw(amount);outputter.output(account.username() now has: account.balance());}
}Database.Account 添加取款方法
Singleton
class Database {...static final class Account {...void withdraw(BigDecimal amount) {balance balance.subtract(amount);}}
}在UserCommandsModule添加取款指令
这个是登录之后才能执行的指令将其添加到UserCommandsModule
Module
abstract class UserCommandsModule {...BindsIntoMapStringKey(withdraw)abstract Command withdrawCommand(WithdrawCommand command);
}
现在我们做一些配置
限制用户取款账户额度不能小于某个值设置单次交易可以取款最大值
有很多方式可以实现这里我们通过WithdrawCommand构造函数注入的方式进行实现。
定义账户额度限制Provider
Module
interface AmountsModule {Providesstatic BigDecimal minimumBalance() {return BigDecimal.ZERO;}Providesstatic BigDecimal maximumWithdrawal() {return new BigDecimal(1000);}
}
添加到UserCommandsRouter
Subcomponent(modules {UserCommandsModule.class, AmountsModule.class})
interface UserCommandsRouter {... 修改取款指令注入额度限制 final class WithdrawCommand extends BigDecimalCommand {private final Database.Account account;private final Outputter outputter;private final BigDecimal minimumBalance;private final BigDecimal maximumWithdrawal;InjectWithdrawCommand(Database.Account account, Outputter outputter, BigDecimal minimumBalance,BigDecimal maximumWithdrawal) {super(outputter);this.account account;this.outputter outputter;this.minimumBalance minimumBalance;this.maximumWithdrawal maximumWithdrawal;}Overrideprotected void handleAmount(BigDecimal amount) {if (amount.compareTo(maximumWithdrawal) 0) {outputter.output(超过单次最大提取额度);return;}BigDecimal newBalance account.balance().subtract(amount);if (newBalance.compareTo(minimumBalance) 0) {outputter.output(超过最低余额限制);return;}account.withdraw(amount);outputter.output(account.username() new balance is: account.balance());}
}
对项目进行编译 Build-Rebuild Project
E:\IdeaProjects\DeaggerDemo\Dagger2ATMApp\src\main\java\dagger\example\atm\CommandProcessorFactory.java:10
java: [Dagger/DuplicateBindings] java.math.BigDecimal is bound multiple times:Provides java.math.BigDecimal dagger.example.atm.AmountsModule.maximumWithdrawal()Provides java.math.BigDecimal dagger.example.atm.AmountsModule.minimumBalance()java.math.BigDecimal is injected atdagger.example.atm.WithdrawCommand(…, maximumWithdrawal)dagger.example.atm.WithdrawCommand is injected atdagger.example.atm.UserCommandsModule.withdrawCommand(command)java.util.Mapjava.lang.String,dagger.example.atm.Command is injected atdagger.example.atm.CommandRouter(map)dagger.example.atm.CommandRouter is requested atdagger.example.atm.UserCommandsRouter.router() [dagger.example.atm.CommandProcessorFactory → dagger.example.atm.UserCommandsRouter]It is also requested at:dagger.example.atm.WithdrawCommand(…, minimumBalance, …)提示BigDecimal绑定多次, Dagger蒙圈了不知道用哪一个。
定义区分数额的修饰符
为了处理这种有多个相同类型返回值使用修饰符。修饰符是被Qualifier注释的注解。
Qualifier
Retention(RUNTIME)
interface MinimumBalance {}Qualifier
Retention(RUNTIME)
interface MaximumWithdrawal {}给AmoutModule 提供金额的函数添加注解
Module
interface AmountsModule {ProvidesMinimumBalancestatic BigDecimal minimumBalance() {return BigDecimal.ZERO;}ProvidesMaximumWithdrawalstatic BigDecimal maximumWithdrawal() {return new BigDecimal(1000);}
}提取指令构造函数参数添加注解
毕竟要区分出来使用到的是Module里面的哪一个函数提供的返回值嘛
final class WithdrawCommand extends BigDecimalCommand {InjectWithdrawCommand(Database.Account account, Outputter outputter,MinimumBalance BigDecimal minimumBalance,MaximumWithdrawal BigDecimal maximumWithdrawal) {...
对项目进行编译 Build-Rebuild Project现在就可以了。 稍微进行测试一下
login gch
gch is logged in with balance: 0
withdraw 100
超过最低余额限制
deposit 10000
gch now has: 10000
withdraw 2000
超过单次最大提取额度
withdraw 1000
gch new balance is: 9000概念
Qualifier 用于区分无关但类型相同的实例
Qualifier 常用于区分很多地方都会用到的基本类型int、String等feature/withdraw
退出登录
现在ATM只能登录不能退出现在让我们添加一下退出功能。
退出指令
final class LogoutCommand implements Command {private final Outputter outputter;InjectLogoutCommand(Outputter outputter) {this.outputter outputter;}Overridepublic Result handleInput(ListString input) {if (input.isEmpty()) {outputter.output(退出登录成功);return Result.inputCompleted();} else {return Result.invalid();}}}
Database.Result 添加退出功能
interface Command {final class Result {public static Result inputCompleted() {return new Result(Status.INPUT_COMPLETED,Optional.empty());}添加UserCommandsModule注册到登录后的指令中
Module
abstract class UserCommandsModule {...BindsIntoMapStringKey(logout)abstract Command logout(LogoutCommand command);对项目进行编译 Build-Rebuild Project Run 测试效果.
login gch
gch is logged in with balance: 0
deposit 10
gch now has: 10
logout
退出登录成功
deposit 10
couldnt understand deposit 10. please try again.feature/logout
设置取款最大限额
上面我们已经配置单次取款最大额度。但是通过多次取款完全可以超过取款最大限额。如果要设置最大限额我们要怎么做呢并且存款后增加取款限额呢在同一个session
WithdrawalLimiter跟踪最大限额和余额变化 final class WithdrawalLimiter {private BigDecimal remainingWithdrawalLimit;InjectWithdrawalLimiter(MaximumWithdrawal BigDecimal maximumWithdrawal) {this.remainingWithdrawalLimit maximumWithdrawal;}void recordDeposit(BigDecimal amount) {remainingWithdrawalLimit this.remainingWithdrawalLimit.add(amount);}void recordWithdrawal(BigDecimal amount) {remainingWithdrawalLimit this.remainingWithdrawalLimit.subtract(amount);}BigDecimal getRemainingWithdrawalLimit() {return remainingWithdrawalLimit;}
}
将WithdrawalLimiter添加到存款指令和取款指令
final class DepositCommand extends BigDecimalCommand {private final Database.Account account;private final Outputter outputter;private final WithdrawalLimiter withdrawalLimiter;InjectDepositCommand(Database.Account account, Outputter outputter, WithdrawalLimiter withdrawalLimiter) {super(outputter);this.account account;this.outputter outputter;this.withdrawalLimiter withdrawalLimiter;}Overrideprotected void handleAmount(BigDecimal amount) {account.deposit(amount);withdrawalLimiter.recordDeposit(amount);outputter.output(account.username() now has: account.balance());}
}
final class WithdrawCommand extends BigDecimalCommand {private final Database.Account account;private final Outputter outputter;private final BigDecimal minimumBalance;private final WithdrawalLimiter withdrawalLimiter;InjectWithdrawCommand(Database.Account account, Outputter outputter,MinimumBalance BigDecimal minimumBalance,WithdrawalLimiter withdrawalLimiter) {super(outputter);this.account account;this.outputter outputter;this.minimumBalance minimumBalance;this.withdrawalLimiter withdrawalLimiter;}Overrideprotected void handleAmount(BigDecimal amount) {if (amount.compareTo(withdrawalLimiter.getRemainingWithdrawalLimit()) 0) {outputter.output(超过Session最大提取额度);return;}BigDecimal newBalance account.balance().subtract(amount);if (newBalance.compareTo(minimumBalance) 0) {outputter.output(超过最低余额限制);return;}account.withdraw(amount);withdrawalLimiter.recordWithdrawal(amount);outputter.output(account.username() new balance is: account.balance());}
}
对项目进行编译 Build-Rebuild Project Run 测试程序
login gch
gch is logged in with balance: 0
deposit 10000
gch now has: 10000
withdraw 2000
超过Session最大提取额度出错了存入10000我最大可以取出100010000才对怎么2000就超过最大额度了 相信你也有感觉会不会是WithdrawalLimiter创建了多个实例的问题 英雄所见略同我也这么认为呢! 然后再WithdrawCommand和DepositCommand构造函数都打印一下WithdrawalLimiter对象地址
对项目进行编译 Build-Rebuild Project Run 进行测试。
login gch
gch is logged in with balance: 0
DepositCommand.withdrawalLimiter:org.WithdrawalLimiter5056dfcb
WithdrawCommand.withdrawalLimiter:org.WithdrawalLimiter2344fc66还真不是同一个对象。 我们之前通过Singleton注解实现了Database的全局单例抛开使用Singleton是否有意义不说如果给WithdrawLimit添加Singleton也需要给UserCommandsRouter添加Singleton。
对项目进行编译 Build-Rebuild Project
E:\IdeaProjects\DeaggerDemo\Dagger2ATMApp\src\main\java\dagger\example\atm\CommandProcessorFactory.java:9
java: [dagger.example.atm.UserCommandsRouter] dagger.example.atm.UserCommandsRouter has conflicting scopes:dagger.example.atm.CommandProcessorFactory also has SingletonSingleton的源码如下
Scope
Documented
Retention(RetentionPolicy.RUNTIME)
public interface Singleton {
}也就是作为subcomponent其scope不能与component相同。起码换个名字嘛。
定义SessionScope PerSession注解
Scope
Documented
Retention(RUNTIME)
interface PerSession {}
给WithdrawLimit添加注解
PerSession
final class WithdrawalLimiter {...给component添加注解
PerSession
Subcomponent(modules {UserCommandsModule.class, AmountsModule.class})
interface UserCommandsRouter {...对项目进行编译 Build-Rebuild Project Run 进行测试。
login gch
gch is logged in with balance: 0
deposit 10000
gch now has: 10000
withdraw 2000
gch new balance is: 8000
withdraw 2000
gch new balance is: 6000
logout
退出登录成功
login gch
gch is logged in with balance: 6000
withdraw 600
gch new balance is: 5400
withdraw 500
超过Session最大提取额度概念
Scope注解声明对于同一个component(或subcomponent)实例提供一个共享的对象也就是该范围内单例
Singleton 只是框架提供的Scope 注解
Scope 限定范围的对象的声明周期是和component对象进行绑定的
注解的名字没有任何意义Singleton和PerSession源码 除了名字完全一样
在一个JVM中多Component创建时Scope修饰对象也会创建多个。feature/session
避免重复登录
对项目进行编译 Build-Rebuild Project Run进行测试.
login gch
gch is logged in with balance: 0
login gch
gch is logged in with balance: 0问题出在哪里呢 Subcomponent UserCommandsRouter集成了Component CommandProcessorFactory [HelloWorldCommand, LoginCommand],支持的指令集合为[HelloWorldCommand, LoginCommand, DepositCommand, WithdrawCommand]。 虽然不能从Map中删除LoginCommand但是有另外一个方法。 在LoginCommand添加OptionalAccount,指示当前是否为登录状态。
LoginCommand添加是否已登录判断
final class LoginCommand extends SingleArgCommand {private final Database database;private final Outputter outputter;private final UserCommandsRouter.Factory userCommandsRouterFactory;private final OptionalDatabase.Account account;InjectLoginCommand(Database database, Outputter outputter, UserCommandsRouter.Factory userCommandsRouterFactory,OptionalDatabase.Account account) {this.database database;this.outputter outputter;this.userCommandsRouterFactory userCommandsRouterFactory;this.account account;}Overridepublic Result handleArg(String username) {if (this.account.isPresent()) {outputter.output(当前已处于登录状态退出登录前不能进行登录);return Result.invalid();}Database.Account account database.getAccount(username);outputter.output(username is logged in with balance: account.balance());return Result.enterNestedCommandSet(userCommandsRouterFactory.create(account).router());}
}
LoginCommandModule声明如何创建OptionalAccount
Module
abstract class LoginCommandModule {BindsOptionalOfabstract Database.Account optionalAccount();BindsOptionalOf告诉Dagger当发现Account后使用Optional.of()去创建他如果他一直不出现Optional.empty()使用Guava版本Optional是Optional.absent()就会返回true。
回顾一下会发现CommandProcessorFactory没有AccountUserCommandsRouter有。每一次创建UserCommandsRouter都会调用LoginCommand每一次调用optional account不同有登录的和无登录的我们可以据此进行区分是否为登录状态。
概念
IntoMap、IntoSet等多绑定指令出现在subcomponent时除包含自身出现的集合外同时包含parent Component中出现的内容。
BindsOptionalOf OptionalReturnType告诉Dagger当ReturnType出现时构建OptionalReturnType对象。
找到仅仅在subcomponent中出现的内容进行subcomponent和component的区分。对项目进行编译 Build-Rebuild Project Run测试程序.
login gch
gch is logged in with balance: 0
login abc
当前已处于登录状态退出登录前不能进行登录
couldnt understand login abc. please try again.
logout
退出登录成功
login abc
abc is logged in with balance: 0feature/repeat_login 官方版本