简介
SWAK在闲鱼搜索上的应用


回顾SWAK使用方式
if(搜商品) {
if(搜商品A版本) {
doSomething1();
}else if(搜商品B版本) {
doSomething2();
}
} else if(搜会玩) {
doSomething3();
} else if(搜用户) {
if(搜用户A版本) {
doSomething4();
}else if(搜用户B版本) {
doSomething5();
}
}
/**
* 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<String> {
@Override
public SwakTagGroup parse(String pageType) {
// pageType = ComponentTypeTag.COMMON_SEARCH
return new SwakTagGroup.Builder().buildByTags(pageType);
}
}

注册过程
因为闲鱼服务端应用基本都基于Spring框架,所以SWAK在设计的时候就借用了很多Spring的特性。Spring相关的特性如果有不了解的可以自行进行查阅,这边就不进行详细介绍了。
@SwakInterface标注的
IPage类并将其交给Spring容器进行托管,这样在使用的时候可以天然使用到Spring的依赖注入能力。同时为了后续能动态进行接口实现的替换,我们不能直接把找到的类注册到Spring容器中,我们需要将其hook成一个代理类,并在代理类中根据情况返回不同
@SwakTag的实例。
如何找到@SwakInterface标注的Bean
public Set<Class<?>> getSwakInterface() {
Reflections reflections = new Reflections(new ConfigurationBuilder()
.addUrls(ClasspathHelper.forPackage(this.packagePath))
.setScanners(new TypeAnnotationsScanner(), new SubTypesScanner())
);
return reflections.getTypesAnnotatedWith(SwakInterface.class);
}
@SwakInterface之外,我们也应该把
@SwakTag对应的类也扫出来,并将其存在一个map中,保证我们后面可以通过Tag去找到一个Class。
如何在Spring进行Bean注册的时候进行偷梁换柱
BeanDefinitionRegistryPostProcessor的bean,并调用
postProcessBeanDefinitionRegistry方法,所以我们可以直接继承这个类并重写相应的方法来Hook这一流程。在这个方法中,我们可以创建一个新的BeanDefinition并将准备好的代理类作为BeanClass设置进去,这样生成对应的Bean时,就会直接使用到我们准备好的代理类了。(这里的原理涉及到Spring Bean的注册过程,可以自行查阅资料,不再详述)
@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);
}
}
}
代理类怎么实现动态进行接口实现的替换
SwakInterfaceProxyFactoryBean作为代理类注册到了BeanDefinitionMap中,但其实
SwakInterfaceProxyFactoryBean严格意义上来说并不是一个代理类,正如它名字所描述的它是一个FactoryBean,FactoryBean是Spring中用来创建比较复杂的bean的一个类,在这个类的getObject()方法中,我们真正地使用动态代理的方式创建相应的对象,创建出相应的对象。
SwakInterfaceProxy.intercept()方法进行拦截。
intercept()方法我们放到下面执行过程中再进行详细介绍,先看看这部分代码
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();
}
}
执行过程
@SwakSessionAop标记的方法体执行前,通过
SwakTagGroupParser根据参数解析出成员变量
IPage iPage对应的实现类
CommonSearchPage,之后在这个方法体中调用
ipage.parse()就会直接调用到
CommonSearchPage.parse()方法了。
怎么在
@SwakSessionAop
标记的方法体执行前插入我们解析的代码呢解析出对应的实现类后,是怎么"赋值"给iPage变量的
如何在方法前面插入代码
@Around注解在方法前进行一层切面来执行我们的代码,我们先使用SwakTagGroupParser解析tagGroup并将解析出来的tagGroup存起来,然后可以调用
jointPoint.proceed()继续执行方法体,这样在方法体中所使用到的iPage就会使用到相应的实现了。
@Component
@Aspect
public class SwakSessionInterceptor {
@Pointcut("@annotation(com.taobao.idle.swak.core.aop.SwakSessionAop)")
public void sessionAop() {
}
@Around("sessionAop()&&@annotation(swakSessionAop)")
public Object execute(ProceedingJoinPoint jointPoint, SwakSessionAop swakSessionAop) {
// 根据类型获取需要传入Parser的参数
Class instanceClass = swakSessionAop.instanceClass();
Object sessionInstance;
for (Object object : args) {
if (instanceClass.isAssignableFrom(object.getClass())) {
sessionInstance = object;
}
}
//通过Parser解析出相应的tagGroup
Class parserClass = swakSessionAop.tagGroupParserClass();
SwakTagGroupParser swakTagGroupParser = (SwakTagGroupParser)(parserClass.newInstance());
SwakTagGroup tagGroup = swakTagGroupParser.parse(sessionInstance);
try {
//SwakSessionHolder就是一个储存tagGroup的地方,可以随意实现
SwakSessionHolder.hold(tagGroup);
Object object = jointPoint.proceed();
return object;
} finally {
SwakSessionHolder.clear();
}
}
}
如何"赋值"iPage变量
@SwakInterface所标注的类在注册的时候做了一层动态代理,所以iPage对应的对象在调用方法前,都会调用一下之前提到的
intercept()方法,在这个方法中,我们可以通过之前存起来的tagGroup找到需要调用的SwakTag,并通过SwakTag找到相应的实现类的实例,最后通过
method.invoke()方法调用其实例。
关于反射的相关API这里就不详细介绍了,引用一下廖雪峰[2]对于Method的解释:对 Method
实例调用invoke
就相当于调用该方法,invoke
的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致,否则将报错。
public class SwakInterfaceProxy implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] parameters, MethodProxy methodProxy) throws Throwable {
String interfaceName = clazz.getName();
SwakTagGroup tagGroup = SwakSessionHolder.getTagGroup();
// 这里还可以根据tag的优先级配置调整执行顺序,这里就简单取一下
List<String> tags = tagGroup.getTags();
Object retResult = null;
try {
// 按照优先级依次执行
for (String tag : tags) {
// 根据TAG可以获取到实现类的实例
// 可能第一次用,那么没有实例只有Class,拿Class去Spring容器里找对应的实例
Object tagImplInstance = getInvokeInstance(tag);
retResult = method.invoke(tagImplInstance, parameters);
}
return retResult;
} catch (Throwable t) {
throw t;
}
}
}
总结
References
[1]Github: https://github.com/ronmamo/reflections
[2]廖雪峰: https://www.liaoxuefeng.com/
文章转载自闲鱼技术,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。





