No JavaFX, FXMLLoader
cria automaticamente o controlador especificado em um arquivo .fxml usando o construtor 0-arg. Ele também fornece métodos para definir/criar o controlador manualmente usando FXMLLoader#setController
ou FXMLoader#setControllerFactory
.
Minha pergunta agora é qual é a diferença entre eles? Existem situações em que um é melhor que o outro?
A documentação de ambos os métodos realmente não responde à minha pergunta.
Quando você carrega um arquivo FXML com um dos
load(...)
métodos definidos emFXMLLoader
, na configuração padrão dois objetos importantes são criados peloFXMLLoader
:root
, que corresponde ao elemento raiz da estrutura FXMLcontroller
, que é um objeto (uma instância específica) conectado aoroot
de várias maneiras peloFXMLLoader
.Como você não pode especificar um objeto real com um valor de string de um atributo XML (ou seja, o valor fornecido para
fx:controller="..."
), a maneira padrão como isso funciona é especificar o nome da classe do controlador nofx:controller
atributo. Por padrão, oFXMLLoader
cria a instância do controlador invocando reflexivamente o construtor de argumento zero da classe especificada.Ele
FXMLLoader
injeta@FXML
elementos anotados do arquivo FXML em campos correspondentes na instância do controlador que ele criou. Feito isso, ele chama oinitialize()
método, se houver, permitindo uma inicialização bastante intuitiva do controlador e das propriedades da visualização associada.Algumas consequências disso são dignas de nota (nem todas são relevantes para esta questão):
FXMLLoader.load(...)
suas variantes várias vezes, várias instâncias da classe do controlador serão criadas. Isso é (quase?) sempre o que você deseja, mas é importante notar que o estado não persistirá de uma instância do controlador para outra.É comum querer fornecer dados de alguma forma ao controlador. Por exemplo, em arquiteturas do tipo MVC/MVP/MVVM, o controlador (ou equivalente) geralmente precisa de acesso a um objeto de modelo. Você pode conseguir isso com a configuração padrão recuperando uma referência ao controlador após o FXML ser carregado e passando-o:
Embora esta abordagem seja viável, ela tem várias desvantagens:
initialize()
método seja invocado. Isso significa que qualquer inicialização que deva ser realizada e que dependa do modelo deve ser feita como resultado dasetModel()
chamada, e não nos locais mais intuitivos (como oinitialize()
método).final
na classe do controlador (o que geralmente é natural e desejável) e não existe uma maneira elegante de impor a configuração do modelo apenas uma vez.As duas primeiras desvantagens podem ser corrigidas chamando
setController(...)
oFXMLLoader
antes de carregar o FXML. No entanto, isso introduz novas desvantagens:Observe que, neste caso, podemos passar uma instância do modelo para o construtor do controlador. Isso significa que o modelo pode ser final e estará disponível assim que o controlador for instanciado. Portanto, o controlador pode se parecer com:
O que acontece quando chamamos
setController(...)
é que substituímos completamente o mecanismo padrão para criar um controlador, em vez disso, fornecemos uma instância de controlador de nossa escolha. Por esta razão, agora não podemos especificar umfx:controller
atributo no arquivo FXML; seria, na melhor das hipóteses, redundante e possivelmente contraditório, e uma exceção será lançada se o fizermos.As duas últimas desvantagens acima (verbosidade e conexão explícita no código entre a visão e a classe do controlador) ainda existem, embora tenhamos resolvido as outras duas que são provavelmente mais importantes. No entanto, não poder especificar a classe do controlador no arquivo FXML significa que introduzimos uma nova desvantagem:
Por outro lado, a fábrica de controladores nos permite usar o mecanismo padrão de criação de uma instância de controlador (ou seja, especificar a classe no
fx:controller
atributo e permitir que eleFXMLLoader
forneça a instância), mas nos permite configurar como a instância é criada.A fábrica do controlador é um
Callback<Class<?>, Object>
, que é essencialmente uma função que mapeia aClass<?>
(um tipo) para um objeto que será usado como controlador. Deve ser uma instância da classe fornecida, mas não há verificação real para isso.O exemplo anterior pode ser reescrito
Neste caso podemos (e devemos) ter
fx:controller="com.example.MyController"
no arquivo FXML. Nosso controlador pode ser exatamente igual à versão anterior, com afinal
instância do modelo que está acessível noinitialize()
método. E nossas ferramentas de desenvolvedor "sabem" qual classe está sendo usada para o controlador, para que possam verificar nomes de métodos e campos, etc.Observe que ainda codificamos o nome da classe do controlador em nosso código de carregamento. Podemos fazer uma fábrica geral de controladores usando reflexão. Esta implementação analisa a classe de controlador fornecida, tenta encontrar um construtor usando uma instância de modelo e usa esse construtor, se houver. Se não encontrar tal construtor, ele tentará usar o construtor padrão sem argumentos. Se nenhum deles existir, uma exceção será lançada. Obviamente, a lógica aqui é arbitrária e pode ser qualquer coisa que você escolher.
Isso é um pouco mais detalhado, mas poderíamos escrevê-lo uma vez e reutilizá-lo em todo o aplicativo. Os arquivos FXML são "naturais" e têm exatamente a mesma aparência da configuração padrão, e o controlador pode se parecer com o postado aqui (com um construtor tomando uma instância de modelo, etc.). Existe sem dúvida uma desvantagem geral de usar reflexão em termos de desempenho e falta de verificação de tipo em tempo de compilação, mas
FXMLLoader
já está usando tanta reflexão que o custo adicional aqui provavelmente será mínimo.Um ponto digno de nota é para arquivos FXML aninhados, usados
<fx:include>
no FXML. A mesma fábrica do controlador será usada quando o arquivo FXML incluído for carregado como para o arquivo FXML carregado explicitamente que o contém. Portanto, se você usar uma fábrica de controladores, ela deverá ser flexível o suficiente para fornecer o controlador correto tanto para o FXML "principal" quanto para quaisquer FXMLs incluídos.Se bem me lembro, a fábrica de controladores foi introduzida na versão 2.1 em resposta a uma solicitação de alguém que deseja usar uma estrutura de injeção de dependência para gerenciar controladores JavaFX. (Pode ter sido Adam Bien, na época em que ele estava escrevendo a estrutura do pós-combustão , agora sem manutenção , mas minha memória não é tão boa.)
As fábricas de controladores funcionam bem neste cenário. Existem vários exemplos de Spring-JavaFX neste site, mas a ideia geral é:
Essencialmente, apenas informamos
FXMLLoader
para obter os controladores do contexto do aplicativo Spring.O controlador baseado em Spring se parece com
Possível duplicata aqui: JavaFX: Precisa de ajuda para entender setControllerFactory
Conforme explicado na duplicata,
setControllerFactory
é útil quando você usa uma estrutura de injeção de dependência e deseja usar sua fábrica personalizada para injetar dependências. Se você não usa nenhum framework para injeção de dependência, é mais fácil usarsetController
.