1.å¦ä½ç¨SwingBuilder使ç¨èªå®ä¹çswingç»ä»¶
2.mybatis插件机制源码解析
3.Spring Cloud OpenFeign源码FeignClientFactoryBean原理
å¦ä½ç¨SwingBuilder使ç¨èªå®ä¹çswingç»ä»¶
groovyå·²ç»å¨SwingBuilderé为æ们é¢è®¾äºå¾å¤swingç»ä»¶ï¼ä»¥ä½¿æ们å¯ä»¥ç¨ä¸é¢è¿æ ·çå¿«æ·å½¢å¼æ¥æ建çé¢ï¼
swingBuilder.frame(
//å±æ§é
){
//åç»ä»¶
}
对äºä» ä» ä½¿ç¨swingçåçç»ä»¶ï¼è¿æ²¡æä»»ä½é®é¢ï¼ä½å¨å®é å¼åä¸å¯è½ä¼éå°éè¦ä½¿ç¨å¯¹åçç»ä»¶æ©å±åçèªå®ä¹ç»ä»¶ï¼å¦ä¸ä¸ªç»§æ¿äºJPanelçMyPanelç±»ï¼çæ åµï¼èè¿ä¸ªç±»ææ¾ä¸å¯è½åºç°å¨SwingBuilderçé¢è®¾å表éï¼é£è¿ç§æ åµä¸æ¯ä¸æ¯å°±æå³çä¸å¯ä»¥ä½¿ç¨groovy为æ们æä¾ç便å©ï¼åªè½ç¨åä¼ ç»çjavaå½¢å¼å»æ建çé¢å¢ï¼
çæ¡æ¯å¦å®çãSwingBuilderè½ç¶ä¸ä¼é¢è®¾ç¨æ·èªå®ä¹çç»ä»¶ï¼ä½å®æä¾äºå 个æ¥å£å¯ä»¥è®©ç¨æ·æèªå®ä¹çç»ä»¶è®¾ç½®è¿å»ï¼è¿æ ·å°±ä¾ç¶å¯ä»¥ç¨groovyçå¿«æ·å½¢å¼æ¥æ建çé¢äºãè¿å 个æ¥å£åå«æ¯ï¼
public void registerFactory(String name, Factory factory)
public void registerBeanFactory(String theName, Class beanClass)
registerFactory()
registerFactoryæ¹æ³å¯ä»¥æ³¨åä¸ä¸ªèªå®ä¹ç»ä»¶ï¼nameæå®äºéè¿swingBuilderæ建ç»ä»¶çå称ï¼å¦JFrameå¨swingBuilderéé¢è®¾çnameæ¯"frame"ï¼è¿å°±ä½¿å¾æ们å¯ä»¥éè¿swingBuilder.frame()æ建ä¸ä¸ªJFrameã
第äºä¸ªåæ°æ¥æ¶ä¸ä¸ªå®ç°äºFactoryæ¥å£çç±»ï¼è¿ä¸ªç±»éææï¼å®ç°ï¼å¦ä½å®ä¾åèªå®ä¹ç»ä»¶ï¼newInstanceï¼ï¼å¦ä½å¤çaddè¿æ¥çåç»ä»¶ï¼setChildï¼çä¸ç³»åçç¥ãé常并ä¸éè¦ç´æ¥å®ç°Factoryæ¥å£ï¼groovy为æ们æä¾äºä¸ä¸ªæ½è±¡ç±»ï¼AbstractFactoryï¼ï¼æ们åºä¼å 继æ¿è¿ä¸ªç±»ï¼ç¶ååæéæç¸åºçæ¹æ³è¿è¡éåãä¸é¢æ¥çä¸ä¸ªå¦ä½ä½¿ç¨è¿ä¸ªæ¥å£çä¾åï¼
/
*** èªå®ä¹äºä¸ä¸ªå¯ä»¥æ¾ç¤ºèæ¯å¾ççé¢æ¿
* @author keenlight
*
*/
class ImagePanel extends JPanel{
/
***
*/
private static final long serialVersionUID = 1L
private BufferedImage image
public ImagePanel(BufferedImage image){
super()
this.image = image
}
public ImagePanel(BufferedImage image, LayoutManager layout) {
super(layout)
this.image = image
}
public void paintComponent(Graphics g)
{
super.paintComponent(g)
if(image != null){
g.drawImage(image, 0, 0, this)
}
}
}
/
*** ImagePanelçFactory
* @author keenlight
*/
class ImagePanelFactory extends AbstractFactory{
private BufferedImage image
ImagePanelFactory(BufferedImage image){
this.image = image
}
@Override
public Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map properties) throws InstantiationException, IllegalAccessException {
def layout = properties.remove("layout")
new ImagePanel(image, layout)
}
public void setChild(FactoryBuilderSupport builder, Object parent, Object child) {
if (!(child instanceof Component) || (child instanceof Window)) {
return
}
parent = parent as ImagePanel
try {
def constraints = builder.context.constraints
if (constraints != null) {
parent.add(child, constraints)
if (child instanceof JComponent) {
child.putClientProperty(LayoutFactory.DEFAULT_DELEGATE_PROPERTY_CONSTRAINT, constraints)
}
builder.context.remove('constraints')
} else {
parent.add(child)
}
} catch (MissingPropertyException mpe) {
parent.add(child)
}
}
}
å®ä¹å¥½è¿äºç±»åå¦ä½ä½¿å ¶çæï¼åªéä¸æ¥ï¼
def bgImage = ImageIO.read(new File(image_path))
swingBuilder.registerFactory("imagePanel", new ImagePanelFactory(bgImage))
注å好ä¹åï¼ImagePanelç»ä»¶ä¾¿å¯åå ¶ä»åçç»ä»¶ä¸æ ·ä½¿ç¨äºï¼å¦ä¸æçJFrameä¸æ ·ï¼ã
registerBeanFactory()
registerBeanFactoryæ¯registerFactoryçä¸ç§ä¾¿å©å½¢å¼ï¼è°ç¨æ¤æ¥å£æ éæä¾Factoryï¼åªéæä¾èªå®ä¹ç±»çç±»åå³å¯ãæ¯å¦åçç»ä»¶éçJPanelå°±æ¯ç¨è¿ä¸ªæ¥å£æ³¨åçï¼SwingBuilderç±»ä¸æ³¨åJPanelçæºç å¦ä¸ï¼
registerBeanFactory("panel", JPanel)
è¿æ ·å°±å¯ä»¥æJPanel注å为"panel"æ¥ä½¿ç¨äºãå ¶å®registerBeanFactoryè¿ä¸ªæ¹æ³éé¢ä¸æ ·ä¹å®ä¾åäºä¸ä¸ªç»§æ¿äºAbstractFactoryç±»ï¼å®ä¼æè°ç¨è¿ä¸ªæ¹æ³æ³¨åçç»ä»¶å½åä¸ä¸ªjavaBeanï¼æ¹æ³åå·²ç»å¾ææ¾äºï¼ï¼æ以å®å¨ç»§æ¿AbstractFactoryç±»æ¶éåçnewInstanceæ¹æ³éæ¯ç´æ¥è¿åè°ç¨class.newInstance()çå®ä¾åç»æï¼classç±ç¬¬äºä¸ªå ¥åæä¾ãæ以对äºé£äºå¨å®ä¾åæ¶è°ç¨é»è®¤æé å¨å°±è¶³å¤çç»ä»¶ï¼ç¨registerBeanFactory()æ¹æ³æ¥æ³¨åä¼æ´ä¸ºä¾¿å©ãå½ç¶ï¼å¯¹äºæä¸é¢å®ä¹çImagePanelæ¥è¯´å°±ä¸è¡äºï¼å 为ImagePanelç±»å¨å®ä¾åæ¶éè¦æ¥åä¸ä¸ªå¾ç对象ï¼æä»¥å¿ é¡»ä½¿ç¨ç¥éº»ç¦ä¸ç¹çregisterFactory()ã
mybatis插件机制源码解析
引言
本篇源码解析基于MyBatis3.5.8版本。
首先需要说明的是,本篇文章不是mybatis插件开发的教程,而是从源码层面分析mybatis是如何支持用户自定义插件开发的。
mybatis的插件机制,让其扩展能力大大增加。魔兽源码比如我们项目中经常用到的PageHelper,这就是一款基于mybatis插件能力开发的产品,它的功能是让基于mybatis的数据库分页查询更容易使用。
当然基于插件我们还可以开发其它功能,比如在执行sql前打印日志、做权限控制等。
正文mybatis插件也叫mybatis拦截器,它支持从方法级别对mybatis进行拦截。整体架构图如下:
解释下几个相关概念:
Interceptor拦截器接口,用户自定义的补码加上源码拦截器就是实现该接口。
InterceptorChain拦截器链,其内部维护一个interceptorslist,表示拦截器链中所有的拦截器,并提供增加或获取拦截器链的方法。比如有个核心的方法是pluginAll。该方法用来生成代理对象。
Invocation拦截器执行时的上下文环境,其实就是目标方法的调用信息,包含目标对象、调用的方法信息、参数信息。核心方法是proceed。该方法的主要目的就是进行处理链的传播,执行完拦截器的方法后,最终需要调用目标方法的invoke方法。
mybatis支持在哪些地方进行拦截呢?你只需要在代码里搜索interceptorChain.pluginAll的请愿网站源码使用位置就可以获取答案,一共有四处:
parameterHandler=(ParameterHandler)interceptorChain.pluginAll(parameterHandler);resultSetHandler=(ResultSetHandler)interceptorChain.pluginAll(resultSetHandler);statementHandler=(StatementHandler)interceptorChain.pluginAll(statementHandler);executor=(Executor)interceptorChain.pluginAll(executor);这四处实现的原理都是一样的,我们只需要选择一个进行分析就可以了。
我们先来看下自定义的插件是如何加载进来的,比如我们使用PageHelper插件,通常会在mybatis-config.xml中加入如下的配置:
<plugins><plugininterceptor="com.github.pagehelper.PageInterceptor"><!--configparamsasthefollowing--><propertyname="param1"value="value1"/></plugin></plugins>mybatis在创建SqlSessionFactory的时候会加载配置文件,
publicConfigurationparse(){ if(parsed){ thrownewBuilderException("EachXMLConfigBuildercanonlybeusedonce.");}parsed=true;parseConfiguration(parser.evalNode("/configuration"));returnconfiguration;}parseConfiguration方法会加载包括plugins在内的很多配置,
privatevoidparseConfiguration(XNoderoot){ try{ ...pluginElement(root.evalNode("plugins"));...}catch(Exceptione){ thrownewBuilderException("ErrorparsingSQLMapperConfiguration.Cause:"+e,e);}}privatevoidpluginElement(XNodeparent)throwsException{ if(parent!=null){ for(XNodechild:parent.getChildren()){ Stringinterceptor=child.getStringAttribute("interceptor");Propertiesproperties=child.getChildrenAsProperties();InterceptorinterceptorInstance=(Interceptor)resolveClass(interceptor).getDeclaredConstructor().newInstance();interceptorInstance.setProperties(properties);configuration.addInterceptor(interceptorInstance);}}}pluginElement干了几件事情:
创建Interceptor实例
设置实例的属性变量
添加到Configuration的interceptorChain拦截器链中
mybatis的插件是通过动态代理实现的,那肯定要生成代理对象,生成的逻辑就是前面提到的pluginAll方法,比如对于Executor生成代理对象就是,
executor=(Executor)interceptorChain.pluginAll(executor);接着看pluginAll方法,
/***该方法会遍历用户定义的插件实现类(Interceptor),并调用Interceptor的plugin方法,对target进行插件化处理,*即我们在实现自定义的2020挣钱源码Interceptor方法时,在plugin中需要根据自己的逻辑,对目标对象进行包装(代理),创建代理对象,*那我们就可以在该方法中使用Plugin#wrap来创建代理类。*/publicObjectpluginAll(Objecttarget){ for(Interceptorinterceptor:interceptors){ target=interceptor.plugin(target);}returntarget;}这里遍历所有我们定义的拦截器,调用拦截器的plugin方法生成代理对象。有人可能有疑问:如果有多个拦截器,target不是被覆盖了吗?
其实不会,所以如果有多个拦截器的话,生成的代理对象会被另一个代理对象代理,从而形成一个代理链条,执行的时候,依次执行所有拦截器的拦截逻辑代码。
plugin方法是接口Interceptor的默认实现类,
defaultObjectplugin(Objecttarget){ returnPlugin.wrap(target,聚合引擎源码this);}然后进入org.apache.ibatis.plugin.Plugin#wrap,
publicstaticObjectwrap(Objecttarget,Interceptorinterceptor){ Map<Class<?>,Set<Method>>signatureMap=getSignatureMap(interceptor);Class<?>type=target.getClass();Class<?>[]interfaces=getAllInterfaces(type,signatureMap);if(interfaces.length>0){ returnProxy.newProxyInstance(type.getClassLoader(),interfaces,newPlugin(target,interceptor,signatureMap));}returntarget;}首先是获取我们自己实现的Interceptor的方法签名映射表。然后获取需要代理的对象的Class上声明的所有接口。比如如果我们wrap的是Executor,就是Executor的所有接口。然后就是最关键的一步,用Proxy类创建一个代理对象(newProxyInstance)。
注意,newProxyInstance方法的第三个参数,接收的是一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用。
我们这里传入的是Plugin类,故在动态运行过程中会执行Plugin的invoker方法。
如果对这一段不是很理解,建议先了解下java动态代理的原理。java动态代理机制中有两个重要的角色:InvocationHandler(接口)和Proxy(类),这个是背景知识需要掌握的。
我们在深入看下上面的getSignatureMap方法,
privatestaticMap<Class<?>,Set<Method>>getSignatureMap(Interceptorinterceptor){ //从Interceptor的类上获取Intercepts注解,说明我们自定义拦截器需要带注解InterceptsinterceptsAnnotation=interceptor.getClass().getAnnotation(Intercepts.class);//issue#if(interceptsAnnotation==null){ thrownewPluginException("No@Interceptsannotationwasfoundininterceptor"+interceptor.getClass().getName());}Signature[]sigs=interceptsAnnotation.value();Map<Class<?>,Set<Method>>signatureMap=newHashMap<>();//解析Interceptor的values属性(Signature[])数组,存入HashMap,Set<Method>>for(Signaturesig:sigs){ Set<Method>methods=MapUtil.computeIfAbsent(signatureMap,sig.type(),k->newHashSet<>());try{ Methodmethod=sig.type().getMethod(sig.method(),sig.args());methods.add(method);}catch(NoSuchMethodExceptione){ thrownewPluginException("Couldnotfindmethodon"+sig.type()+"named"+sig.method()+".Cause:"+e,e);}}returnsignatureMap;}首先需要从Interceptor的类上获取Intercepts注解,说明我们自定义拦截器需要带注解,比如PageHelper插件的定义如下:
<plugins><plugininterceptor="com.github.pagehelper.PageInterceptor"><!--configparamsasthefollowing--><propertyname="param1"value="value1"/></plugin></plugins>0所以我们可以知道,getSignatureMap其实就是拿到我们自定义拦截器声明需要拦截的类以及类对应的方法。
前面说过,当我们调用代理对象时,最终会执行Plugin类的invoker方法,我们看下Plugin的invoker方法,
<plugins><plugininterceptor="com.github.pagehelper.PageInterceptor"><!--configparamsasthefollowing--><propertyname="param1"value="value1"/></plugin></plugins>1Interceptor接口的intercept方法就是我们自定义拦截器需要实现的逻辑,其参数为Invocation,可从Invocation参数中拿到执行方法的对象,方法,方法参数,比如我们可以从statementHandler拿到SQL语句,实现自己的特殊逻辑。
在该方法的结束需要调用invocation#proceed()方法,进行拦截器链的传播。
参考:
blogs.com/chenpi/p/.html
Spring Cloud OpenFeign源码FeignClientFactoryBean原理
Spring Cloud OpenFeign的FeignClientFactoryBean在实例化过程中,通过FactoryBean接口实现,GetObject方法的关键步骤包括获取FeignContext、配置Feign.Builder、创建HardCodedTarget和调用loadBalance方法。这些步骤涉及自动配置、FeignClientSpecification的使用、Logger和Builder组件的定制以及动态代理的生成。最后,getObject方法返回的是一个接口的代理类,用于执行远程调用。
详细分析:
FeignClientFactoryBean在Spring容器中,通过getObject方法转化为实际的FeignClient实例。首先,它从FeignContext获取相关配置,这个配置在引入OpenFeign依赖时自动注入。接下来,通过getTarget方法,FeignClientFactoryBean配置了Builder组件,如Logger(非Slf4j)、RequestInterceptor、Encoder和Decoder等,同时考虑了用户自定义组件的配置。之后,创建了HardCodedTarget,基于FeignClient接口、注解值和完整URL构建,然后通过loadBalance方法,整合了LoadBalancerFeignClient和HystrixTargeter,进行负载均衡和目标URL定位。
在newInstance方法中,解析了接口方法的注解,生成了MethodHandler,并用FeignInvocationHandler封装,这个InvocationHandler在代理类实例化时被调用,实现了远程调用。最终,通过Proxy.newProxyInstance动态生成了代理类,完成FeignClientFactoryBean的实例化过程。
总的来说,FeignClientFactoryBean实例化是通过一系列配置和代理生成,实现了Spring Cloud OpenFeign的远程调用功能。如果你对源码的深入理解感兴趣,下期文章将继续解析调用源码细节。
2024-11-23 02:56
2024-11-23 02:03
2024-11-23 01:53
2024-11-23 01:38
2024-11-23 01:29
2024-11-23 01:25
2024-11-23 01:07
2024-11-23 00:47