Ordering evaluation of Conditional annotations in spring

廉价感情. 提交于 2019-12-12 02:44:16

问题


I have three classes all of which needs to be enabled/disabled based on the @ConditionalOnExpression annotaion. All of these are in separate .java files.

EX:

@ConditionalOnExpression("#T(com.xxx.xxx.xxx.xxx.CxProperties).isAEnabled()}")
Class A{
}


@ConditionalOnExpression("#T(com.xxx.xxx.xxx.xxx.CxProperties).isBEnabled()}")
Class B{
}


@ConditionalOnExpression("#T(com.xxx.xxx.xxx.xxx.CxProperties).isCEnabled()}")
Class C{
}

Now i have an init funtion in a different class which gets executed first and i set the enable/disable value to the class CxProperties. Note: Assume all setters are static

class setvalues{

        public void init(){
             /*Read config values from a file*/
             CxProperties.setAEnabled(true/false);
             CxProperties.setBEnabled(true/false);
             CxProperties.setCEnabled(true/false);
        }
}

Now the evaluation of these conditions are happening at the beginning of the program (even before the execution of init) when the enable/disable is not set at all.

Is there any possible way in spring to order the evaluation of these conditions like say evaluate this after a certain point of execuation?

Any pointers are highly appreciated.


回答1:


I would suggest you not to use @ConditionalOnExpression annotation for this.

Consider using @PreAuthorize instead. Yes, it's from spring-security.

With that you can protect each service from usage if it isn't enabled and dynamically toggle enabled/disabled state for it:

@SpringBootApplication
public class So44462763Application {

    public static void main(String[] args) {
        SpringApplication.run(So44462763Application.class, args);
    }

    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)  // <-- this is required for PreAuthorize annotation to work
    public static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable();
        }
    }

    interface CxProperties {
        boolean isServiceAEnabled();
        boolean isServiceBEnabled();
        boolean isServiceCEnabled();

        boolean enableService(String service);
        boolean disableService(String service);
    }

    @Component("cx")
    public static class CxPropertiesImpl implements CxProperties {
        private static final ConcurrentHashMap<String, Boolean> services = new ConcurrentHashMap<>(); //could be database/redis/property file/etc

        @PostConstruct
        private void init() {
            //services.put("serviceA", true); //initial population from property file/network resource/whatever
        }

        public boolean isServiceAEnabled() {
            return services.getOrDefault("serviceA", false);
        }

        public boolean isServiceBEnabled() {
            return services.getOrDefault("serviceB", false);
        }

        public boolean isServiceCEnabled() {
            return services.getOrDefault("serviceC", false);
        }
        //just a sample how you can dynamically control availability for each service
        @Override
        public boolean enableService(String service) {
            services.put(service, true);
            return services.getOrDefault(service, false);
        }

        @Override
        public boolean disableService(String service) {
            services.put(service, false);
            return services.getOrDefault(service, false);
        }
    }

    interface BusinessService {
        String doSomething();
    }

    @Service("serviceA")
    @PreAuthorize("@cx.serviceAEnabled")
    public static class ServiceA implements BusinessService {

        @Override
        public String doSomething() {
            return this.getClass().getSimpleName() + " doing some work";
        }
    }

    @Service("serviceB")
    @PreAuthorize("@cx.serviceBEnabled")
    public static class ServiceB implements BusinessService {

        @Override
        public String doSomething() {
            return this.getClass().getSimpleName() + " doing some work";
        }
    }

    @Service("serviceC")
    @PreAuthorize("@cx.serviceCEnabled")
    public static class ServiceC implements BusinessService {

        @Override
        public String doSomething() {
            return this.getClass().getSimpleName() + " doing some work";
        }
    }

    @RestController
    @RequestMapping("/api/work")
    public static class WorkApi {
        private static final Logger log = LoggerFactory.getLogger(WorkApi.class);

        private final List<BusinessService> businessServices;

        @Autowired
        public WorkApi(final List<BusinessService> businessServices) {
            this.businessServices = businessServices;
        }

        @GetMapping
        public String doWork() {
            final StringJoiner joiner = new StringJoiner(",");
            for (BusinessService service : businessServices) {
                try {
                    joiner.add(service.doSomething());
                } catch (AccessDeniedException e) {
                    log.warn("Service {} is disabled.", service);
                }
            }
            return joiner.toString();
        }
    }

    @RestController
    @RequestMapping("/api/control")
    public static class ControlApi {

        private final CxProperties cxProperties;

        @Autowired
        public ControlApi(final CxProperties cxProperties) {
            this.cxProperties = cxProperties;
        }

        @PostMapping("{service}/enable")
        public boolean enable(@PathVariable("service") String serviceName) {
            return cxProperties.enableService(serviceName);
        }

        @PostMapping("{service}/disable")
        public boolean disable(@PathVariable("service") String serviceName) {
            return cxProperties.disableService(serviceName);
        }
    }
}

And here is a sample usage:

$ curl -u user:123 -XGET 'localhost:8080/api/work'
$ curl -u user:123 -XPOST 'localhost:8080/api/control/serviceC/enable'
true% 
$ curl -u user:123 -XGET 'localhost:8080/api/work'                    
ServiceC doing some work%
$ curl -u user:123 -XPOST 'localhost:8080/api/control/serviceA/enable'
true%
$ curl -u user:123 -XGET 'localhost:8080/api/work'                    
ServiceA doing some work,ServiceC doing some work%

Using this approach you can control accessibility of services even without restart.

And all of this can be done without spring-security as well but involves a little bit more of manual work and might slightly decrease overall readability of your code.



来源:https://stackoverflow.com/questions/44462763/ordering-evaluation-of-conditional-annotations-in-spring

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!