简体   繁体   中英

Dynamically choose bean without Qualifier annotation

I have a service that gets an URL from the user input, extracts the body content of this URL, apply CSS, and finally returns the result as a stream.

The tricky part is that I have different implementations depending on the URL, if the URL is not recognized, then a "default" implementation is used. To do so, I used a BeanFactory to choose the correct implementation on Runtime.

@Service
@Qualifier("defaultHtmlToHtmlService")
public class DefaultHtmlToHtmlImpl implements HtmlToHtmlService {

    @Override
    public InputStream htmlToHtml(String url) throws IOException {
        Document document = Jsoup.connect(url).get();
        Element content = document.body();

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        outputStream.write(content.outerHtml().getBytes());
        outputStream.close();

        return new ByteArrayInputStream(outputStream.toByteArray());
    }
}

@Component
public class BeanFactory {


private final HtmlToHtmlService defaultHtmlToHtmlService;

private final HtmlToHtmlService impl1;

private final HtmlToHtmlService impl2;

@Autowired
public BeanFactory(@Qualifier("defaultHtmlToHtmlImpl") HtmlToHtmlService defaultHtmlToHtmlService,
                   @Qualifier("impl1") HtmlToHtmlService impl1,
                   @Qualifier("impl2") HtmlToHtmlService impl2) {
    this.defaultHtmlToHtmlService = defaultHtmlToHtmlService;
    this.impl1 = impl1;
    this.impl2 = impl2;
}

public HtmlToHtmlService getHtmlToHtmlImpl(String url) {
    if (url.contains("some pattern")) {
        return impl1;
    } else if (url.contains("some other pattern")) {
        return impl2;
    } else {
        return defaultHtmlToHtmlService;
    }
}

This is working just fine. However, my issue is that I don't want my BeanFactory to share any information with the rest, but I do not know how to decouple it. Basically, I want to remove the @Qualifier annotation, and not to have to manually enter the new URL patterns in the Bean Factory in the future when I will get more implementations.

I saw that maybe @PostConstruct annotation or using static blocks could be the solution, but I am really not sure how to apply it in this case.

Something like a strategy pattern can help in this case. If you define a List<HtmlToHtmlService> in the constructor of the component in question, Spring can populate it with all implementations available in the context (you may want to rename the class BeanFactory to avoid ambiguity with Spring's own BeanFactory ):

@Component
public class HtmlToHtmlServiceFactory {

    private final List<HtmlToHtmlService> services;

    @Autowired
    public HtmlToHtmlServiceFactory(List<HtmlToHtmlService> services) {
        this.services = services
    }

    public HtmlToHtmlService getHtmlToHtmlImpl(String url) {
        return services.stream()
            .filter(service -> service.supportsUrl(url))
            .findFirst()
            .orElseThrow(() -> new IllegalArgumentException(String.format("No service found that supports [%s]", url)));
    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM