【小家Spring】聊聊Spring中的数据绑定 --- BeanWrapper以及内省Introspector和PropertyDescriptor...

本文主要是介绍【小家Spring】聊聊Spring中的数据绑定 --- BeanWrapper以及内省Introspector和PropertyDescriptor...,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

每篇一句

> 千古以来要饭的没有要早饭的,知道为什么吗?

相关阅读

【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor 【小家Spring】聊聊Spring中的数据绑定 --- 属性访问器PropertyAccessor和实现类DirectFieldAccessor的使用


<center>对Spring感兴趣可扫码加入wx群:`Java高工、架构师3群`(文末有二维码)</center>


前言

这篇文章需要依赖于对属性访问器PropertyAccessor的理解,也就是上篇文章的内容:【小家Spring】聊聊Spring中的数据绑定 --- 属性访问器PropertyAccessor和实现类DirectFieldAccessor的使用

如果说上篇文章所说的PropertyAccessor你没有接触过和听过,那么本文即将要说的重点:BeanWrapper你应该多少有所耳闻吧~ BeanWrapper可以简单的把它理解为:一个方便开发人员使用字符串来对Java Bean的属性执行get、set操作的工具。关于它的数据转换使用了如下两种机制:

  1. PropertyEditor:隶属于Java Bean规范PropertyEditor只提供了String &lt;-&gt; Object的转换。
  2. ConversionService:Spring自3.0之后提供的替代PropertyEditor的机制(BeanWrapper在Spring的第一个版本就存在了~) > 按照Spring官方文档的说法,当容器内没有注册ConversionService的时候,会退回使用PropertyEditor机制。言外之意:首选方案是ConversionService > 其实了解的伙伴应该知道,这不是BeanWrapper的内容,而是父接口PropertyAccessor的内容~

BeanWrapper

官方解释:Spring低级JavaBeans基础设施的中央接口。通常来说并不直接使用BeanWrapper,而是借助BeanFactory或者DataBinder来一起使用~

//@since 13 April 2001  很清晰的看到,它也是个`PropertyAccessor`属性访问器
public interface BeanWrapper extends ConfigurablePropertyAccessor {// @since 4.1void setAutoGrowCollectionLimit(int autoGrowCollectionLimit);int getAutoGrowCollectionLimit();Object getWrappedInstance();Class<!--?--> getWrappedClass();// 获取属性们的PropertyDescriptor  获取属性们PropertyDescriptor[] getPropertyDescriptors();// 获取具体某一个属性~PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException;
}

BeanWrapper相当于一个代理器,Spring委托BeanWrapper完成Bean属性的填充工作。关于此接口的实现类,简单的说它只有唯一实现类:BeanWrapperImpl

BeanWrapperImpl

它作为BeanWrapper接口的默认实现,它足以满足所有的典型应用场景,它会缓存Bean的内省结果而提高效率。 > 在Spring2.5之前,此实现类是非public的,但在2.5之后给public了并且还提供了工厂:PropertyAccessorFactory帮助第三方框架能快速获取到一个实例~

public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements BeanWrapper {// 缓存内省结果~@Nullableprivate CachedIntrospectionResults cachedIntrospectionResults;// The security context used for invoking the property methods.@Nullableprivate AccessControlContext acc;// 构造方法都是沿用父类的~public BeanWrapperImpl() {this(true);}... private BeanWrapperImpl(Object object, String nestedPath, BeanWrapperImpl parent) {super(object, nestedPath, parent);setSecurityContext(parent.acc);}// @since 4.3  设置目标对象~~~public void setBeanInstance(Object object) {this.wrappedObject = object;this.rootObject = object;this.typeConverterDelegate = new TypeConverterDelegate(this, this.wrappedObject);// 设置内省的clazzsetIntrospectionClass(object.getClass());}// 复写父类的方法  增加内省逻辑@Overridepublic void setWrappedInstance(Object object, @Nullable String nestedPath, @Nullable Object rootObject) {super.setWrappedInstance(object, nestedPath, rootObject);setIntrospectionClass(getWrappedClass());}// 如果cachedIntrospectionResults它持有的BeanClass并不是传入的clazz 那就清空缓存 重新来~~~protected void setIntrospectionClass(Class<!--?--> clazz) {if (this.cachedIntrospectionResults != null &amp;&amp; this.cachedIntrospectionResults.getBeanClass() != clazz) {this.cachedIntrospectionResults = null;}}private CachedIntrospectionResults getCachedIntrospectionResults() {if (this.cachedIntrospectionResults == null) {// forClass此方法:生成此clazz的类型结果,并且缓存了起来~~this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(getWrappedClass());}return this.cachedIntrospectionResults;}...// 获取到此属性的处理器。此处是个BeanPropertyHandler 内部类~@Override@Nullableprotected BeanPropertyHandler getLocalPropertyHandler(String propertyName) {PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(propertyName);return (pd != null ? new BeanPropertyHandler(pd) : null);}@Overrideprotected BeanWrapperImpl newNestedPropertyAccessor(Object object, String nestedPath) {return new BeanWrapperImpl(object, nestedPath, this);}@Overridepublic PropertyDescriptor[] getPropertyDescriptors() {return getCachedIntrospectionResults().getPropertyDescriptors();}// 获取具体某一个属性的PropertyDescriptor @Overridepublic PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException {BeanWrapperImpl nestedBw = (BeanWrapperImpl) getPropertyAccessorForPropertyPath(propertyName);String finalPath = getFinalPath(nestedBw, propertyName);PropertyDescriptor pd = nestedBw.getCachedIntrospectionResults().getPropertyDescriptor(finalPath);if (pd == null) {throw new InvalidPropertyException(getRootClass(), getNestedPath() + propertyName, "No property '" + propertyName + "' found");}return pd;}...// 此处理器处理的是PropertyDescriptor private class BeanPropertyHandler extends PropertyHandler {private final PropertyDescriptor pd;// 是否可读、可写  都是由PropertyDescriptor 去决定了~// java.beans.PropertyDescriptor~~public BeanPropertyHandler(PropertyDescriptor pd) {super(pd.getPropertyType(), pd.getReadMethod() != null, pd.getWriteMethod() != null);this.pd = pd;}...@Override@Nullablepublic Object getValue() throws Exception {...ReflectionUtils.makeAccessible(readMethod);return readMethod.invoke(getWrappedInstance(), (Object[]) null);}...}
}

从继承体系上,首先我们应该能看出来BeanWrapperImpl的三重身份:

  1. Bean包裹器
  2. 属性访问器(PropertyAccessor)
  3. 属性编辑器注册表(PropertyEditorRegistry)

从源码中继续分析还能再得出如下两个结论:

  1. 它给属性赋值调用的是Method方法,如readMethod.invokewriteMethod.invoke
  2. 它对Bean的操作,大都委托给CachedIntrospectionResults去完成~

因此若想了解它,必然主要是要先了解java.beans.PropertyDescriptororg.springframework.beans.CachedIntrospectionResults,首当其冲的自然还有Java内省


Java内省Introspector

首先可以先了解下JavaBean的概念:一种特殊的类,主要用于传递数据信息。这种类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。如果在两个模块之间传递信息,可以将信息封装进JavaBean中,这种对象称为“值对象”(Value Object),或“VO”。

因此JavaBean都有如下几个特征:

  1. 属性都是私有的;
  2. 有无参的public构造方法;
  3. 对私有属性根据需要提供公有的getXxx方法以及setXxx方法;
  4. getters必须有返回值没有方法参数;setter值没有返回值,有方法参数;

符合这些特征的类,被称为JavaBean;JDK中提供了一套API用来访问某个属性的getter/setter方法,这些API存放在java.beans中,这就是内省(Introspector)

==内省和反射的区别==

反射:Java反射机制是在运行中,对任意一个类,能够获取得到这个类的所有属性和方法;它针对的是任意类 内省(Introspector):是Java语言对JavaBean类属性、事件的处理方法

  1. 反射可以操作各种类的属性,而内省只是通过反射来操作JavaBean的属性
  2. 内省设置属性值肯定会调用seter方法,反射可以不用(反射可直接操作属性Field)
  3. 反射就像照镜子,然后能看到.class的所有,是客观的事实。内省更像主观的判断:比如看到getName()内省就会认为这个类中有name字段,但事实上并不一定会有name;通过内省可以获取bean的getter/setter

既然反射比内省比内省强大这么多,那内省用在什么时候场景呢?下面给出一个示例来说明它的用武之地:

	// 就这样简单几步,就完成了表单到User对象的封装~public void insertUser(HttpServletRequest request) throws Exception {User user = new User();// 遍历:根据字段名去拿值即可(此处省略判空、类型转换等细节,不在本文讨论范围)PropertyDescriptor[] pds = Introspector.getBeanInfo(User.class).getPropertyDescriptors();for (PropertyDescriptor pd : pds) {pd.getWriteMethod().invoke(user, request.getParameter(pd.getName()));}}

通过内省可以很轻松的将form表单的内容填充进对象里面,比反射轻松省力多了。其实像MyBatis这种框架,底层都用到了Java的内省机制。

内省的API主要有Introspector、BeanInfo、PropertyDescriptor等,下面就以他三为例来操作一个JavaBean:

@Getter
@Setter
@ToString
public class Child {private String name;private Integer age;}
使用Introspector + BeanInfo
    public static void main(String[] args) throws IntrospectionException {BeanInfo beanInfo = Introspector.getBeanInfo(Child.class);BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();MethodDescriptor[] methodDescriptors = beanInfo.getMethodDescriptors();PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();// 打印System.out.println(beanDescriptor);System.out.println("------------------------------");Arrays.stream(methodDescriptors).forEach(x -&gt; System.out.println(x));System.out.println("------------------------------");Arrays.stream(propertyDescriptors).forEach(x -&gt; System.out.println(x));System.out.println("------------------------------");}

输入内容如下:

java.beans.BeanDescriptor[name=Child; beanClass=class com.fsx.bean.Child]
------------------------------
java.beans.MethodDescriptor[name=getClass; method=public final native java.lang.Class java.lang.Object.getClass()]
java.beans.MethodDescriptor[name=getName; method=public java.lang.String com.fsx.bean.Child.getName()]
java.beans.MethodDescriptor[name=setAge; method=public void com.fsx.bean.Child.setAge(java.lang.Integer)]
java.beans.MethodDescriptor[name=setName; method=public void com.fsx.bean.Child.setName(java.lang.String)]
java.beans.MethodDescriptor[name=getAge; method=public java.lang.Integer com.fsx.bean.Child.getAge()]
java.beans.MethodDescriptor[name=wait; method=public final void java.lang.Object.wait() throws java.lang.InterruptedException]
java.beans.MethodDescriptor[name=notifyAll; method=public final native void java.lang.Object.notifyAll()]
java.beans.MethodDescriptor[name=notify; method=public final native void java.lang.Object.notify()]
java.beans.MethodDescriptor[name=wait; method=public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException]
java.beans.MethodDescriptor[name=hashCode; method=public native int java.lang.Object.hashCode()]
java.beans.MethodDescriptor[name=wait; method=public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException]
java.beans.MethodDescriptor[name=equals; method=public boolean java.lang.Object.equals(java.lang.Object)]
java.beans.MethodDescriptor[name=toString; method=public java.lang.String com.fsx.bean.Child.toString()]
------------------------------
java.beans.PropertyDescriptor[name=age; propertyType=class java.lang.Integer; readMethod=public java.lang.Integer com.fsx.bean.Child.getAge(); writeMethod=public void com.fsx.bean.Child.setAge(java.lang.Integer)]
java.beans.PropertyDescriptor[name=class; propertyType=class java.lang.Class; readMethod=public final native java.lang.Class java.lang.Object.getClass()]
java.beans.PropertyDescriptor[name=name; propertyType=class java.lang.String; readMethod=public java.lang.String com.fsx.bean.Child.getName(); writeMethod=public void com.fsx.bean.Child.setName(java.lang.String)]
------------------------------

可以看到getMethodDescriptors()它把父类的MethodDescriptor也拿出来了。 而PropertyDescriptor中比较特殊的是因为有getClass()方法,因此class也算是一个PropertyDescriptor,但是它没有writeMethod哦~ > 关于BeanInfo,Spring在3.1提供了一个类ExtendedBeanInfo继承自它实现了功能扩展,并且提供了BeanInfoFactory来专门生产它~~~(实现类为:ExtendedBeanInfoFactory

但是如果只想拿某一个属性的话,使用Introspector就不是那么方便了,下面介绍更为常用的PropertyDescriptor来处理某一个属性~

PropertyDescriptor 属性描述器

属性描述符描述了Java bean通过一对访问器方法导出的一个属性。上面的示例此处用PropertyDescriptor试试:

    public static void main(String[] args) throws IntrospectionException {PropertyDescriptor age = new PropertyDescriptor("age", Child.class);System.out.println(age.getPropertyType()); //class java.lang.IntegerSystem.out.println(age.getDisplayName()); //age// 最重要的两个方法~~~System.out.println(age.getReadMethod()); //public java.lang.Integer com.fsx.bean.Child.getAge()System.out.println(age.getWriteMethod()); //public void com.fsx.bean.Child.setAge(java.lang.Integer)}

可以看到它可以实现更加细粒度的控制。将PropertyDescriptor类的一些主要方法描述如下:

  1. getPropertyType(),获得属性的Class对象;
  2. getReadMethod(),获得用于读取属性值的方法;
  3. getWriteMethod(),获得用于写入属性值的方法;
  4. setReadMethod(Method readMethod),设置用于读取属性值的方法;
  5. setWriteMethod(Method writeMethod),设置用于写入属性值的方法。

CachedIntrospectionResults

Spring如果需要依赖注入那么就必须依靠Java内省这个特性了,说到Spring IOC与JDK内省的结合那么就不得不说一下Spring中的CachedIntrospectionResults这个类了。 它是Spring提供的专门用于缓存JavaBean的PropertyDescriptor描述信息的类,不能被应用代码直接使用。

它的缓存信息是被静态存储起来的(应用级别),因此对于同一个类型的被操作的JavaBean并不会都创建一个新的CachedIntrospectionResults,因此,这个类使用了工厂模式,使用私有构造器和一个静态的forClass工厂方法来获取实例。

public final class CachedIntrospectionResults {// 它可以通过在spring.properties里设置这个属性,来关闭内省的缓存~~~public static final String IGNORE_BEANINFO_PROPERTY_NAME = "spring.beaninfo.ignore";private static final boolean shouldIntrospectorIgnoreBeaninfoClasses = SpringProperties.getFlag(IGNORE_BEANINFO_PROPERTY_NAME);// 此处使用了SpringFactoriesLoader这个SPI来加载BeanInfoFactory,唯一实现类是ExtendedBeanInfoFactory/** Stores the BeanInfoFactory instances. */private static List<beaninfofactory> beanInfoFactories = SpringFactoriesLoader.loadFactories(BeanInfoFactory.class, CachedIntrospectionResults.class.getClassLoader());static final Set<classloader> acceptedClassLoaders = Collections.newSetFromMap(new ConcurrentHashMap&lt;&gt;(16));static final ConcurrentMap<class<?>, CachedIntrospectionResults&gt; strongClassCache = new ConcurrentHashMap&lt;&gt;(64);static final ConcurrentMap<class<?>, CachedIntrospectionResults&gt; softClassCache = new ConcurrentReferenceHashMap&lt;&gt;(64);// 被包裹类的BeanInfo~~~也就是目标类private final BeanInfo beanInfo;// 它缓存了被包裹类的所有属性的属性描述器PropertyDescriptor。private final Map<string, propertydescriptor> propertyDescriptorCache;... // 其它的都是静态方法// 只有它会返回一个实例,此类是单例的设计~  它保证了每个beanClass都有一个CachedIntrospectionResults 对象,然后被缓存起来~static CachedIntrospectionResults forClass(Class<!--?--> beanClass) throws BeansException { ... }
}

本处理类的核心内容是Java内省getBeanInfo()以及PropertyDescriptor~注意:为了使此内省缓存生效,有个前提条件请保证了:

  • 确保将Spring框架的Jar包和你的应用类使用的是同一个ClassLoader加载的,这样在任何情况下会允许随着应用的生命周期来清楚缓存。

因此对于web应用来说,Spring建议给web容器注册一个IntrospectorCleanupListener监听器来防止多ClassLoader布局,这样也可以有效的利用caching从而提高效率~


监听器的配置形如这样(此处以web.xml里配置为例):

<listener><listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
</listener>

> 说明:请保证此监听器配置在第一个位置,比ContextLoaderListener还靠前~ 此监听器能有效的防止内存泄漏问题~~~(因为内省的缓存是应用级别的全局缓存,很容易造成泄漏的~) > 其实流行框架比如struts, Quartz等在使用JDK的内省时,存在没有释的内存泄漏问题~

DirectFieldAccessFallbackBeanWrapper

说完了BeanWrapperImpl,可以看看它的子类DirectFieldAccessFallbackBeanWrapper,他就像BeanWrapperImplDirectFieldAccessor的结合体。它先用BeanWrapperImpl.getPropertyValue(),若抛出异常了(毕竟内省不是十分靠谱,哈哈)再用DirectFieldAccessor~~~此子类在JedisClusterConnection有被使用到过,比较简单没啥太多好说的~

PropertyAccessorFactory

Spring2.5后提供的快速获取PropertyAccessor两个重要实现类的工厂。

public final class PropertyAccessorFactory {private PropertyAccessorFactory() {}// 生产一个BeanWrapperImpl(最为常用)public static BeanWrapper forBeanPropertyAccess(Object target) {return new BeanWrapperImpl(target);}// 生产一个DirectFieldAccessorpublic static ConfigurablePropertyAccessor forDirectFieldAccess(Object target) {return new DirectFieldAccessor(target);}}
BeanWrapper使用Demo

说了这么多,是时候实战一把了~

// 省略Apple类和Size类,有需要的请参照上篇文章(加上@Getter、@Setter即可public static void main(String[] args) {Apple apple = new Apple();BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(apple);// ================当作一个普通的PropertyAccessor来使用  默认情况下字段也都必须有初始值才行~===================// 设置普通属性beanWrapper.setPropertyValue("color", "红色"); //请保证对应字段有set方法才行,否则抛错:Does the parameter type of the setter match the return type of the getter?// 设置嵌套属性(注意:此处能够正常work是因为有= new Size(),// 否则报错:Value of nested property 'size' is null 下同~)beanWrapper.setPropertyValue("size.height", 10);// 设置集合/数组属性beanWrapper.setPropertyValue("arrStr[0]", "arrStr");beanWrapper.setPropertyValue("arrStr[1]", "arrStr1"); // 注意:虽然初始化时初始化过数组了,但是仍以此处的为准// =========打印输出System.out.println(apple); //Apple(color=红色, size=Size(height=10, width=null), arrStr=[arrStr, arrStr1], listStr=[], map={}, listList=[[]], listMap=[{}])// 当作BeanWrapper使用PropertyDescriptor[] propertyDescriptors = beanWrapper.getPropertyDescriptors();PropertyDescriptor color = beanWrapper.getPropertyDescriptor("color");System.out.println(propertyDescriptors.length); // 8System.out.println(color); //org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=color]System.out.println(beanWrapper.getWrappedClass()); //class com.fsx.bean.AppleSystem.out.println(beanWrapper.getWrappedInstance()); //Apple(color=红色, size=Size(height=10...}

上面代码能够清晰的表示了通过BeanWrapper来操作JavaBean还是非常之简便的。


最后,上一张比较丑的结构图,画一画属性编辑器、类型转换器、属性解析器、属性访问器大致的一个关系(此图不喜勿碰): 在这里插入图片描述

总结

BeanWrapper接口,作为Spring内部的一个核心接口,正如其名,它是bean的包裹类,即在内部中将会保存该bean的实例,提供其它一些扩展功能。

Spring对Bean的属性存取都是通过BeanWrapperImpl实现的,BeanWrapperImplBean是一对一的关系,BeanWrapperImpl通过属性的读方法写方法来存取Bean属性的。为了更加深刻的了解BeanWrapper,下篇文章会深入分析Spring BeanFactory对它的应用~

知识交流

==The last:如果觉得本文对你有帮助,不妨点个赞呗。当然分享到你的朋友圈让更多小伙伴看到也是被作者本人许可的~==

若对技术内容感兴趣可以加入wx群交流:Java高工、架构师3群。 若群二维码失效,请加wx号:fsx641385712(或者扫描下方wx二维码)。并且备注:"java入群" 字样,会手动邀请入群 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190703175020107.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Y2NDEzODU3MTI=,size_16,color_FFFFFF,t_70,pic_center =300x)</string,></class<?></class<?></classloader></beaninfofactory>

转载于:https://my.oschina.net/fangshixiang/blog/3074437

这篇关于【小家Spring】聊聊Spring中的数据绑定 --- BeanWrapper以及内省Introspector和PropertyDescriptor...的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/291008

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定