在软件开发中,随着业务的不断发展,代码复杂度会急剧上升,尤其是搜索类型判断和代码耦合问题,让人头疼不已。别担心,SWAK的出现为我们带来了转机。
搜索类型处理痛点
在搜索功能里,依据不同的搜索类型返回各异的页面编排很常见。可要是把判断类型和返回编排的代码都写在一块,代码文件会越来越臃肿。比如一个大型电商系统,搜索商品、店铺、活动的类型不同,页面呈现差异也大。要是代码耦合,后期维护就像在一堆乱麻里找线头,又慢又容易出错,成本还高。
if(搜商品) {
if(搜商品A版本) {
doSomething1();
}else if(搜商品B版本) {
doSomething2();
}
} else if(搜会玩) {
doSomething3();
} else if(搜用户) {
if(搜用户A版本) {
doSomething4();
}else if(搜用户B版本) {
doSomething5();
}
}
SWAK闪亮登场
为解决上述难题,SWAK应运而生。它可以把复杂的if - else逻辑平铺,转变成TAG,进行路由。比如原本为不同搜索类型层层嵌套大量代码,现在用SWAK就简洁多了。它能把逻辑解耦,让各部分代码独立,便于修改和扩展,就像给混乱的代码进行了一次大整理。
/**
* 1.首先先定义一个接口
*/
@SwakInterface(desc = "组件解析") // 使用注解声明这是一个多实现接口
public interface IPage {
SearchPage parse(Object data);
}
/**
* 2.然后编写相应的实现,这个实现可以有很多个,用TAG进行标识
*/
@Component
@SwakTag(tags = {ComponentTypeTag.COMMON_SEARCH})
public class CommonSearchPage implements IPage {
@Override
public SearchPage parse(Object data) {
return null;
}
}
/**
* 3.编写Swak路由的入口
*/
@Component
public class PageService {
@Autowired
private IPage iPage;
@SwakSessionAop(tagGroupParserClass = PageTypeParser.class,
instanceClass = String.class)
public SearchPage getPage(String pageType, Object data) {
return iPage.parse(data);
}
}
/**
* 4.编写相应的解析类
*/
public class PageTypeParser implements SwakTagGroupParser {
@Override
public SwakTagGroup parse(String pageType) {
// pageType = ComponentTypeTag.COMMON_SEARCH
return new SwakTagGroup.Builder().buildByTags(pageType);
}
}
核心问题剖析
SWAK最核心的问题是如何找到对应的接口实现。这得从注册和执行两个过程分析。在注册阶段,确定接口实现类怎么在系统里注册;执行阶段,思考调用时怎么找到正确的实现。就像我们要给很多人安排座位,先得知道每个人的信息,再按规则安排座位,最后确定从哪里找到人。
代理类的巧妙运用
为了能动态替换接口实现,不能直接把找到的类注册到Spring容器,而是要hook成代理类。在代理类里,根据实际情况返回不同@SwakTag的实例。举个简单例子,假如有不同的搜索方式,可根据用户的新需求随时切换,就像为不同季节搭配不同衣服,适应性很强。Spring也为我们提供了实现途径,继承BeanDefinitionRegistryPostProcessor类重写方法就行。
public Set>> getSwakInterface() {
Reflections reflections = new Reflections(new ConfigurationBuilder()
.addUrls(ClasspathHelper.forPackage(this.packagePath))
.setScanners(new TypeAnnotationsScanner(), new SubTypesScanner())
);
return reflections.getTypesAnnotatedWith(SwakInterface.class);
}
执行过程详解
执行时,目标是在@SwakSessionAop标记的方法体执行前,用SwakTagGroupParser根据参数解析出对应实现类。如在一个搜索方法里,能解析出IPage iPage对应的CommonSearchPage。之后调用ipage.parse(),其实执行的就是CommonSearchPage.parse()。这过程就像接力比赛,有条不紊地找到对应的选手完成任务。
切面的实际应用
@Configuration
public class ProxyBeanDefinitionRegister implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
Set<Class> typesAnnotatedWith = getSwakInterface();
for (Class superClass : typesAnnotatedWith) {
if (!superClass.isInterface()) {
continue;
}
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClass(SwakInterfaceProxyFactoryBean.class);
beanDefinition.getPropertyValues().addPropertyValue("swakInterfaceClass", superClass);
String beanName = superClass.getName();
beanDefinition.setPrimary(true);
beanDefinitionRegistry.registerBeanDefinition(beanName, beanDefinition);
}
}
}
通过@Around注解在方法前加切面执行代码。先解析tagGroup并保存,再调用方法体。之后iPage会自动使用相应实现。而且由于之前对@SwakInterface标注的类做了动态代理,iPage对象调用方法前,会先调用intercept()方法。通过保存的tagGroup找SwakTag,进而找到实现类实例,最后用method.invoke()调用。就像层层筛选,确保每次执行的都是最合适、最准确的代码。
大家在实际开发中,有遇到过和搜索类型判断、代码耦合类似的难题吗?觉得SWAK这种解决方案是否能有效提高开发效率?欢迎点赞、分享本文,并在评论区留言讨论!
public class SwakInterfaceProxyFactoryBean implements FactoryBean {
@Override
public Object getObject() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this);
this.clazz = clazz;
// 这里一般不用new出来,可以把SwakInterfaceProxy也交给Spring进行托管,这里为了表述清晰用new指代一下
enhancer.setCallback(new SwakInterfaceProxy());
// 返回代理对象,返回的对象起始就是一个封装了"实体类"的代理类,是实现类的实例。
return enhancer.create();
}
}