An actuator that can automatically match the implementation class according to the interface (noodles)

Posted by abda53 on Tue, 22 Feb 2022 14:05:20 +0100

Demand background

Take a scenario. For example, at this time, we wrote a restaurant class to make different faces for users according to their needs.

So how to achieve it at this time?

The conventional writing method may be to take the user input as a variable, and then make if for this variable

If there are n kinds of faces, there are n if

But this way of writing is obviously too low, and it is not easy to maintain and read.

Give a new scheme

A new scheme is given here, which is actually a reference to the strategic mode in the design mode,

The bottom layer is written thanks to the polymorphism of java.

Let's start with the source code and then explain:

Source code

Annotation class:

All "noodles" should be identified with this annotation!

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Component
public @interface ZoeAnnotation {
    String value();
}

Tools for manually injecting bean s

explain:

As a class of "noodle" implementation class, internal bean injection using @ autowired will fail. The reason for this bug has not been found yet.

However, manual injection can be successful, so let's use manual injection for the time being.

@Component
public class SpringUtil implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringUtil.applicationContext == null) {
            SpringUtil.applicationContext = applicationContext;
        }
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }
}

Context:

What type of noodles should be represented by the field of context (class)

@Data
public class ZoeContext {
    public String context;
}

controller:

All the requested entrances are running bb out

@Slf4j
@RestController
@RequestMapping("/zoe")
public class Controller {

    @Autowired
    TestExecutor testExecutor;

    @RequestMapping("/exe/{enum}")
    public String execute(@PathVariable("enum") String enu) {
        System.out.println("enu=" + enu);
        ZoeContext zoeContext = new ZoeContext();
        zoeContext.setContext(enu);
        return testExecutor.executeVoid(ExtInterface.class, zoeContext, ext -> ext.say());
    }

    @RequestMapping("/exe/")
    public String execute() {
        ZoeContext zoeContext = new ZoeContext();
        zoeContext.setContext(Enum.DEFAULT_SHUI_ZHU_NOODLES);
        return testExecutor.executeVoid(ExtInterface.class, zoeContext, ext -> ext.say());
    }
}

Enumeration representing "face type":

This is the content of the above context, not much bb.

Without enumeration, it is not easy to manage and identify.

public class Enum {
    /**
     * Fat beef noodles
     */
    public static final String FEI_NIU_NOODLES = "Fat beef noodles";

    /**
     * Fat sausage noodles
     */
    public static final String FEI_CHANG_NOODLES = "Fat sausage noodles";

    /**
     * Egg and tomato noodles
     */
    public static final String JI_DAN_XI_HONG_SHI_NOODLES = "Egg and tomato noodles";

    /**
     * Default boiled noodles
     */
    public static final String DEFAULT_SHUI_ZHU_NOODLES = "Default boiled noodles";
}

Implement the actuator interface of "matching different faces":

Implement the core interface of "matching different faces according to context (input parameter)"

public interface AbstractExecutor {
    <T,R>String executeVoid(Class<T> clz, ZoeContext context, Function<T,R> function);
}

Implementation class of actuator interface:

Implement the core implementation class of "matching different faces according to context (input parameter)"

Core idea:

According to the content of context, return different implementation classes, and then execute the method.

@Component
public class TestExecutor implements AbstractExecutor {
    private Logger logger = LoggerFactory.getLogger(this.getClass().getName());

    @Override
    public <T, R> String executeVoid(Class<T> clz, ZoeContext context, Function<T, R> function) {
        if (context == null) {
            return null;
        }
        if (clz == null) {
            return null;
        }
        if (function == null) {
            return null;
        }
        T t = matchExt(context, clz);
        return function.apply(t).toString();
    }

    /**
     * Matcher
     */
    private <T> T matchExt(ZoeContext context, Class<T> clz) {
        String ct = context.getContext();
        Map<String, ExtInterface> extMap = TestFactory.extMap;
        if (ct == null) {
            logger.info("Match failed,Go default");
            return (T) extMap.get(Enum.DEFAULT_SHUI_ZHU_NOODLES);
        }
        T t = (T) extMap.get(ct);
        if (t == null) {
            logger.info("Match failed,Go default");
            return (T) extMap.get(Enum.DEFAULT_SHUI_ZHU_NOODLES);
        }
        return t;
    }

}

Face class unified parent interface:

public interface ExtInterface {
    String say();
}

"Boiled noodles":

Internal bean s using @ autowired will fail to inject. The reason for this bug has not been found yet.

However, manual injection can be successful, so let's use manual injection for the time being.

@ZoeAnnotation(Enum.DEFAULT_SHUI_ZHU_NOODLES)
@Component
public class ExtDefault implements ExtInterface {

    @Override
    public String say() {

        ZoeService zoeService = SpringUtil.getBean("zoeService", ZoeService.class);

        if (zoeService == null) {
            System.out.println("Injection failed-----------");
        }
        return zoeService.sayDefault();
    }
}

"Egg tomato noodles":

Internal bean s using @ autowired will fail to inject. The reason for this bug has not been found yet.

However, manual injection can be successful, so let's use manual injection for the time being.

@ZoeAnnotation(Enum.JI_DAN_XI_HONG_SHI_NOODLES)
public class ExtOne implements ExtInterface {
    public String say() {
        ZoeService zoeService = SpringUtil.getBean("zoeService", ZoeService.class);

        if (zoeService == null) {
            System.out.println("Injection failed-----------");
        }
        return zoeService.sayJiDanMian();
    }
}

"Fat beef noodles":

Internal bean s using @ autowired will fail to inject. The reason for this bug has not been found yet.

However, manual injection can be successful, so let's use manual injection for the time being.

@ZoeAnnotation(Enum.FEI_NIU_NOODLES)
public class ExtTwo implements ExtInterface {
    @Override
    public String say() {
        ZoeService zoeService = SpringUtil.getBean("zoeService", ZoeService.class);

        if (zoeService == null) {
            System.out.println("Injection failed-----------");
        }
        return zoeService.sayFeiNiuMian();
    }
}

"Fat sausage noodles":

Internal bean s using @ autowired will fail to inject. The reason for this bug has not been found yet.

However, manual injection can be successful, so let's use manual injection for the time being.

@ZoeAnnotation(Enum.FEI_CHANG_NOODLES)
public class ExtThree implements ExtInterface {
    @Override
    public String say() {
        ZoeService zoeService = SpringUtil.getBean("zoeService", ZoeService.class);

        if (zoeService == null) {
            System.out.println("Injection failed-----------");
        }
        return zoeService.sayFeiChangMian();
    }
}

"Noodle" factory:

All "noodle classes" are actually instantiated here and stored in the cache. In other words, it is not instantiated only by calling, but when the whole project is started, all "noodle" implementation classes have been instantiated into objects, stored in the cache and waiting to be called.

This is also the core of "matching".

@Component
public class TestFactory {

    public static Map<String, ExtInterface> extMap;

    public TestFactory() {
        Object zoeService = SpringUtil.getBean("zoeService");

        System.out.println("TestFactory:" + zoeService.toString());
        if (zoeService != null) {
            System.out.println("Factory injection zoeservice success");
        }
        //initialization
        extMap = new ConcurrentHashMap<>(4);
        ExtOne extOne = new ExtOne();
        ExtTwo extTwo = new ExtTwo();
        ExtThree extThree = new ExtThree();
        ExtDefault extDefault = new ExtDefault();
        //register
        extMap.put(ExtOne.class.getAnnotation(ZoeAnnotation.class).value(), extOne);
        extMap.put(ExtTwo.class.getAnnotation(ZoeAnnotation.class).value(), extTwo);
        extMap.put(ExtThree.class.getAnnotation(ZoeAnnotation.class).value(), extThree);
        extMap.put(ExtDefault.class.getAnnotation(ZoeAnnotation.class).value(), extDefault);
    }
}

"Noodle" service class:

At present, there is no complex content in the implementation class, just a simple return string.

@Service
public class ZoeService {
    public String sayDefault() {
        return "Default boiled noodles";
    }

    public String sayJiDanMian() {
        return "Egg and tomato noodles";
    }

    public String sayFeiNiuMian() {
        return "Fat beef noodles";
    }

    public String sayFeiChangMian() {
        return "Fat sausage noodles";
    }
}

Main startup class:

@SpringBootApplication
@ComponentScan(basePackages = {"com.zoe2.*"})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

Topics: Java Spring Boot Design Pattern