Spring Qualifier without if-else - java

I use Spring Framework. I want to make service for create an archive. I have two types of archive. I would like to know another way how to do it not using if-else statement.
Interface
public interface Archive {
public String makeArchive();
}
Implements 1
#Component
public class ArchiveRAR implements Archive {
#Override
public String makeArchive() {
return "I made archive RAR.";
}
}
Implements 2
#Component
public class ArchiveZIP implements Archive {
#Override
public String makeArchive() {
return "I made archive ZIP.";
}
}
Service
#Service
public class RunnerApp implements CommandLineRunner {
#Autowired
#Qualifier("archiveRAR")
Archive archiveRAR;
#Autowired
#Qualifier("archiveZIP")
Archive archiveZIP;
#Override
public void run(String... strings) throws Exception {
Report report = new Report("ZIP");
//HERE
if ("RAR".equals(report.getType())) {
System.out.println(archiveRAR.makeArchive());
} else if ("ZIP".equals(report.getType())) {
System.out.println(archiveZIP.makeArchive());
}
}
}
MainApp
#SpringBootApplication
public class MainApp {
public static void main(String[] args) throws Exception {
SpringApplication.run(MainApp.class, args);
}
}
Thank you.

I suggest you to use here factory pattern instead of if-else statement:
#Component
public class ArchiveFactory {
#Autowired
private Map<String, Archive> archives;
public Archive getArhive(String archiveType) {
return archives.get(archiveType);
}
}
By this way your main method will be:
#Service
public class RunnerApp implements CommandLineRunner {
#Autowired
private ArchiveFactory archiveFactory;
#Override
public void run(String... strings) throws Exception {
Report report = new Report("ZIP");
Archive archive = archiveFactory.get(report.getType());
System.out.println(archive.makeArchive());
}
}
Of course you need also to define bean of type Map<String, Archive>.

One other way..
#Component("RAR")
public class ArchiveRAR implements Archive{...}
#Component("ZIP")
public class ArchiveZIP implements Archive {...}
And the service class to inject BeanFactory
#Service
public class RunnerApp implements CommandLineRunner {
#Autowired
BeanFactory beans;
#Override
public void run(String... strings) throws Exception {
String archiveType = "ZIP/RAR";
Archive archive = beans.getBean(archiveType,Archive.class);
archive.makeArchive();
}

There is a cleaner way to do it without basically if-else and String comparison if you can decide beforehand which one you would like to use: just pass the appropriate Archive reference to the Report bean when you create it and also define a method that will generate your archive:
#Component
class Report {
private Archive archive;
// declare that Archive dependency is required
#Autowired
public Report(#Qualifier("archiveRar") Archive archive) {
// or public Report(#Qualifier("archiveZip") Archive archive) {
// depending on which one you would like to inject and use
this.archive = archive;
}
public void generateArchive() {
archive.makeArchive();
}
}
Also annotate your 2 Archive implementations with meaningful qualifier names:
#Component
#Qualifier("archiveRar")
public class ArchiveRAR implements Archive {
#Override
public String makeArchive() {
return "I made archive RAR.";
}
}
and
#Component
#Qualifier("archiveZip")
public class ArchiveZIP implements Archive {
#Override
public String makeArchive() {
return "I made archive ZIP.";
}
}
The Service will now look simpler:
#Service
public class RunnerApp implements CommandLineRunner {
#Autowired
private Report report;
#Override
public void run(String... strings) throws Exception {
report.generateArchive();
}
}
If you need run-time injection, you can get rid of the if-else but not String value qualifier for the bean, for example by a accessing the Spring bean container using BeanFactory.
Keep the above code for Archive and its implementations and delete the Report class which will no longer be used. In the Service use this:
#Service
public class RunnerApp implements CommandLineRunner {
#Autowired
private BeanFactory beanFactory;
#Override
public void run(String... strings) throws Exception {
String qualifierValue = "archiveRar";
// or String qualifierValue = "archiveZip";
Archive archive = beanFactory.getBean(qualifierValue, Archive.class);
System.out.println(archive.makeArchive());
}
}

Related

How to get one instance of dependency while it injected several times in different classes?

I need to get one instance of OwnerService, because in Dataloader class I load some data to that instance and in OwnerController class I have to get loaded data. But in OwnerController there was no data. I printed out the instances and receive different ID of instances
Dataloader class
public class DataLoader implements CommandLineRunner {
private final OwnerService ownerService;
public DataLoader() {
ownerService = new OwnerServiceMap();
}
#Override
public void run(String... args) throws Exception {
System.out.println(ownerService);
}
}
#RequestMapping("/owners")
#Controller
public class OwnerController {
private final OwnerService ownerService;
public OwnerController(OwnerService ownerService) {
this.ownerService = ownerService;
}
#GetMapping({"", "/", "/index"})
public String ownersIndex(Model model) {
System.out.println(ownerService);
model.addAttribute("owners", ownerService.findAll());
return "owners/index";
}
}
I need one instance of Bean in several injected classes.
In your class DataLoader, you are not injecting OwnerService. Instead, the constructor directly creates an instance of class OwnerServiceMap (which is presumably a class that implements interface OwnerService):
public DataLoader() {
ownerService = new OwnerServiceMap();
}
Instead, inject it into DataLoader, in exactly the same way as you are doing in OwnerController:
public DataLoader(OwnerService ownerService) {
this.ownerService = ownerService;
}

how to use '#Qualifier' dynamically specifying parameters?

I want to use #Qualifier to dynamically specifying parameters? how to do it ?
#Qualifier("two") 'two' as a parameter ,can be 'one' 'three' or other.
Can i use aop dynamically design 'two'?
means I want to change the name of service with a #Qualifier by parameters.
the parameter from the url 'Token'.
case: url: http://localhost:8080/insert/order, token has a parameter: companyId = one
#RestController
public class ApiWebService {
#Autowired
#Qualifier("two")
//#Qualifier("one")
private BaseService baseService;
#GetMapping("insert/order")
public void test() {
baseService.insertOrder();
}
}
#Service("one")
public class CompanyOneService extends BaseService {
#Override
public void insertOrder() {
System.out.println("conpanyOne");
System.out.println("baseInsertOrder");
}
}
#Service("two")
public class CompanyTwoService extends BaseService {
#Override
public void insertOrder(){
System.out.println("companyTwo");
System.out.println("baseInsertOrder");
}
}
three
four
...
#Service
public class BaseService {
public void insertOrder(){
System.out.println("baseInsertOrder");
}
}
你好 !
No you cannot , mostly because the attribute in Java annotation does not allow to assign with variables.
Actually you want to choose an implementation to use based on some runtime conditions(i.e.companyId in your case). You can achieve it using factory pattern with #Configuration and #Bean which is much more elegant and easier to understand than your ugly AOP solution:
First define a factory:
#Configuration
public class ServiceFactory{
#Bean
public BaseService companyOneService(){
return new CompanyOneService();
}
#Bean
public BaseService companyTwoService(){
return new CompanyTwoService();
}
public BaseService getService(Integer companyId){
if(companyId == 1){
return companyOneService();
}else if(company==2){
return companyTwoService();
}else{
//blablablab
}
}
}
In the controller , inject the ServiceFactory to get the related Service based on the the company Id
#RestController
public class ApiWebService {
#Autowired
private ServiceFactory serviceFactory;
#GetMapping("insert/order")
public void test() {
Integer companyId = getCompanyIdFromToken(httpServletRequest);
BaseService service = serviceFactory.getService(companyId);
service.blablabla();
}
}
Inject (autowire) ApplicationContext into your class and use one of getBeans* method to find the exact bean you need.
aspect
#Aspect
#Component
public class ApiAspect {
#Pointcut("execution(* com.example.demo.control.ApiWebService.*(..))")
public void apiInputWebService() {
}
#Before("apiInputWebService()")
public void apiInputAuth(JoinPoint joinPoint) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes())
.getRequest();
String token = request.getHeader("Authorization");
//compangId can be from token
String compangId = "one";
Object target = joinPoint.getTarget();
Method method = target.getClass().getMethod("before", String.class);
method.invoke(target, compangId);
}
}
control
#RestController
public class ApiWebService {
private ApiService baseService;
#Autowired
private ApplicationContext applicationContext;
public void before(String company) {
baseService = (ApiService) applicationContext.getBean(company);
}
#GetMapping("insert/order")
public void test() {
baseService.insertOrder();
}
}
service
#Service
public class ApiService {
public void insertOrder(){
System.out.println("baseInsertOrder");
}
}
#Service("one")
public class CompanyOneService extends ApiService {
#Override
public void insertOrder() {
System.out.println("conpanyOne");
System.out.println("baseInsertOrder");
}
}
#Service("two")
public class CompanyTwoService extends ApiService {
#Override
public void insertOrder(){
System.out.println("companyTwo");
System.out.println("baseInsertOrder");
}
}

How to inject bean via generic variable in spring

I have problem with injecting bean with generic types. Look at the example. I will inject to the service a repository which types takes from App class. Now i have exception:
No qualifying bean of type 'asd.IRepository' available: expected single matching bean but found 2: a,b
asd here is package, just for tests.
What can I do in this situation? Is any way to makes it?
public interface IRepository<T, V> {
void print();
}
#Component
public class A implements IRepository<String,String> {
#Override
public void print() {
System.out.println("A");
}
}
#Component
public class B implements IRepository<Double,String> {
#Override
public void print() {
System.out.println("A");
}
}
#Service
public class ServiceABC<V, T> {
#Autowired
private IRepository<V,T> repo;
public void print(){
repo.print();
}
}
#Controller
public class App {
#Autowired
private ServiceABC<String, String> serviceABC;
public static void main(String[] args) {
ApplicationContext ctx =
new AnnotationConfigApplicationContext("asd");
App app = ctx.getBean(App.class);
app.serviceABC.print();
}
It looks like you don't know in advance which implementation of your IRepository interface you will need. And you will know that at runtime. In this case it is a typical case for Factory pattern where you will have a IRepositoryFactory that will have a method thhat retrieves specific implementation by type (for example IRepositoryFactory.getInstance(String type); So in your ServiceABC you may use the IRepository to get specific bean at runtime. So Factory pattern may be an answer to your question. I also wrote an article that deals with this type of problem and proposes the idea of self-populating Factory (using Open source library that provides such utility). Here is the link to the article: Non-intrusive access to "Orphaned" Beans in Spring framework
You have to name your components and autowire by name:
#Component("A")
public class A implements IRepository<String,String> {...}
#Component("B")
public class B implements IRepository<Double,String> {...}
[...]
#Autowired
#Qualifier("B")
private IRepository repo;
Something like that?
#Controller
public class RepositoryFactory {
#Autowired
private IRepository<String, String> a;
#Autowired
private IRepository<Double, String> b;
public IRepository getRepository(String className) {
if(className.equalsIgnoreCase("a")) {
return a;
} else if(className.equalsIgnoreCase("b")) {
return b;
}
return null;
}
}
#Service
public class ServiceABC {
#Autowired
private RepositoryFactory repositoryFactory;
public void print(String className){
repositoryFactory.getRepository(className).print();
}
}
#Controller
public class App {
#Autowired
private ServiceABC serviceABC;
public static void main(String[] args) {
ApplicationContext ctx =
new AnnotationConfigApplicationContext("asd");
App app = ctx.getBean(App.class);
app.serviceABC.print(A.class.getSimpleName());
}
}s

Spring Boot run controller without request

Is there a way to run a controller for initializing some data before Spring Boot starts the Tomcat?
My current code looks like that:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
AbstractApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Controller controller = (Controller) context.getBean("controller");
controller.start();
context.close();
SpringApplication.run(Application.class, args);
}
}
#Controller
#Component("controller")
public class Controller {
#Autowired
private Runner runner;
public void start() {
runner.test();
}
}
#Configuration
#PropertySource("classpath:config.properties")
#Component("runner")
public class Runner {
#Value("${name}")
private String name;
public void test() {
System.out.println("hello " + name)
}
public String getName() {
return name;
}
}
#Controller
public class HelloController {
#Autowired
private Runner runner;
#RequestMapping("/hello")
public CalenderCollection data(#PathVariable("name")String name, Model model) {
model.addAttribute("name", runner.getName());
return "hello";
}
}
#Configuration
#ComponentScan(basePackages = "com.test")
public class AppConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
This prints the correct name into the console. But when I visit the url then runner is null. Then I thought about to change the Application and Controller class to this:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
#Controller
#Component("controller")
public class Controller {
#Autowired
private Runner runner;
public Controller() {
runner.test();
}
}
But now I have the problem that runner is null right in the beginning. What would be a right way to collect some data in the beginning and then continue with the process?
Usually you use ApplicationRunner or CommandLineRunner to run some code before application started.
From documentation
If you need to run some specific code once the SpringApplication has
started, you can implement the ApplicationRunner or CommandLineRunner
interfaces. Both interfaces work in the same way and offer a single
run method which will be called just before SpringApplication.run(…​)
completes.
The CommandLineRunner interfaces provides access to application
arguments as a simple string array, whereas the ApplicationRunner uses
the ApplicationArguments interface discussed above.
import org.springframework.boot.*
import org.springframework.stereotype.*
#Component
public class MyBean implements CommandLineRunner {
public void run(String... args) {
// Do something...
}
}
If you are just trying to run some code when the application has started, then there is no need to use Controller. Spring provides various application lifecycle hooks for such use cases.
The code would most likely look like this
#Component
public class MyListener
implements ApplicationListener<ContextRefreshedEvent> {
#Autowired
private Runner runner;
public void onApplicationEvent(ContextRefreshedEvent event) {
runner.test();
}
}
Check out this blog post and this part of the documentation for more information
If you want to initialize some values you can do something like this :
#SpringBootApplication
public class Application implements CommandLineRunner {
#Override
public void run( String... args ) throws Exception {
//initialise your value here
}
}
If this is something that affects only that class you can use
#PostConstruct
on a method of your controller class.
If this is something that is linked to the whole application, you should consider
creating an application listener on ApplicationReadyEvent

Inject bean into enum

I have the DataPrepareService that prepare data for reports and I have an Enum with report types, and I need to inject ReportService into Enum or have access to ReportService from enum.
my service:
#Service
public class DataPrepareService {
// my service
}
my enum:
public enum ReportType {
REPORT_1("name", "filename"),
REPORT_2("name", "filename"),
REPORT_3("name", "filename")
public abstract Map<String, Object> getSpecificParams();
public Map<String, Object> getCommonParams(){
// some code that requires service
}
}
I tried to use
#Autowired
DataPrepareService dataPrepareService;
, but it didn't work
How can I inject my service into enum?
public enum ReportType {
REPORT_1("name", "filename"),
REPORT_2("name", "filename");
#Component
public static class ReportTypeServiceInjector {
#Autowired
private DataPrepareService dataPrepareService;
#PostConstruct
public void postConstruct() {
for (ReportType rt : EnumSet.allOf(ReportType.class))
rt.setDataPrepareService(dataPrepareService);
}
}
[...]
}
weekens' answer works if you change inner class to static so spring can see it
Maybe something like this:
public enum ReportType {
#Component
public class ReportTypeServiceInjector {
#Autowired
private DataPrepareService dataPrepareService;
#PostConstruct
public void postConstruct() {
for (ReportType rt : EnumSet.allOf(ReportType.class))
rt.setDataPrepareService(dataPrepareService);
}
}
REPORT_1("name", "filename"),
REPORT_2("name", "filename"),
...
}
There is one another approach you may like to explore. However instead of injecting a bean into enum it associates a bean with an enum
Say you have an enum WidgetType and Widget class
public enum WidgetType {
FOO, BAR;
}
public class Widget {
WidgetType widgetType;
String message;
public Widget(WidgetType widgetType, String message) {
this.widgetType = widgetType;
this.message = message;
}
}
And you want to create Widgets of this type using a Factory BarFactory or FooFactory
public interface AbstractWidgetFactory {
Widget createWidget();
WidgetType factoryFor();
}
#Component
public class BarFactory implements AbstractWidgetFactory {
#Override
public Widget createWidget() {
return new Widget(BAR, "A Foo Widget");
}
#Override
public WidgetType factoryFor() {
return BAR;
}
}
#Component
public class FooFactory implements AbstractWidgetFactory {
#Override
public Widget createWidget() {
return new Widget(FOO, "A Foo Widget");
}
#Override
public WidgetType factoryFor() {
return FOO;
}
}
The WidgetService is where most of the work happens. Here I have a simple AutoWired field which keeps tracks of all the registered WidgetFactories. As a postConstruct operation we create a map of the enum and the associated factory.
Now clients could inject the WidgetService class and get the factory for the given enum type
#Service
public class WidgetService {
#Autowired
List<AbstractWidgetFactory> widgetFactories;
Map<WidgetType, AbstractWidgetFactory> factoryMap = new HashMap<>();
#PostConstruct
public void init() {
widgetFactories.forEach(w -> {
factoryMap.put(w.factoryFor(), w);
});
}
public Widget getWidgetOfType(WidgetType widgetType) {
return factoryMap.get(widgetType).createWidget();
}
}
Enums are static, so you have to figure out a way to access to the beans from a static context.
You can create a class named ApplicationContextProvider that implements the ApplicationContextAware interface.
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class ApplicationContextProvider implements ApplicationContextAware{
private static ApplicationContext appContext = null;
public static ApplicationContext getApplicationContext() {
return appContext;
}
public void setApplicationContext(ApplicationContext appContext) throws BeansException {
this.appContext = appContext;
}
}
then add this your application context file:
<bean id="applicationContextProvider" class="xxx.xxx.ApplicationContextProvider"></bean>
after that you could access to the application context in a static way like this:
ApplicationContext appContext = ApplicationContextProvider.getApplicationContext();
it will be hard to control that the spring container is already up and running at the time the enum is instantiated (if you had a variable with this type in a test-case, your container will usually not be there, even aspectj autowiring won't help there). i would recommend to just let the dataprepare-service or something give you the specific-params with a lookup-method with the enum-parameter.
I think this what you need
public enum MyEnum {
ONE,TWO,THREE;
}
Autowire the enum as per usual
#Configurable
public class MySpringConfiguredClass {
#Autowired
#Qualifier("mine")
private MyEnum myEnum;
}
Here is the trick, use the factory-method="valueOf" and also make sure
lazy-init="false"
so the container creates the bean upfront
<bean id="mine" class="foo.bar.MyEnum" factory-method="valueOf" lazy-init="false">
<constructor-arg value="ONE" />
</bean>
and you are done!
Just pass it to the method manually
public enum ReportType {
REPORT_1("name", "filename"),
REPORT_2("name", "filename"),
REPORT_3("name", "filename")
public abstract Map<String, Object> getSpecificParams();
public Map<String, Object> getCommonParams(DataPrepareService dataPrepareService){
// some code that requires service
}
}
As long as you call the method only from managed beans, you can inject it in these beans and pass the reference to the enum on each call.
Maybe you can use this solution ;
public enum ChartTypes {
AREA_CHART("Area Chart", XYAreaChart.class),
BAR_CHART("Bar Chart", XYBarChart.class),
private String name;
private String serviceName;
ChartTypes(String name, Class clazz) {
this.name = name;
this.serviceName = clazz.getSimpleName();
}
public String getServiceName() {
return serviceName;
}
#Override
public String toString() {
return name;
}
}
And in another class which you need the bean of the Enum :
ChartTypes plotType = ChartTypes.AreaChart
Object areaChartService = applicationContext.getBean(chartType.getServiceName());

Categories