精华内容
下载资源
问答
  • @Path注解

    万次阅读 2019-11-14 21:49:00
    最近用到的一个项目,看到Controller控制层、Method方法都是通篇的@Path注解,由于之前并没有使用过该注解,故记此篇。 首先看一下项目中的使用方式: @Path("clientWeb")publicclassClientWeb{@POST@Path("/getData...

    最近用到的一个项目,看到Controller控制层、Method方法都是通篇的@Path注解,由于之前并没有使用过该注解,故记此篇。

    首先看一下项目中的使用方式:

    @Path("clientWeb")
    public class ClientWeb {

        @POST
        @Path("/getData")
        public String getData(@QueryParam("start") Integer start,@QueryParam("limit") Integer limit) {
            return "xxx"
        }
    }

    如上我们可以看到,@Path注解可以标记在类名之上,也可以标记在方法名上。该注解接收一个value参数,表示定义资源的地址。

    另外,资源地址相同,但是HTTP方法不同的两个方法是完全两个不同的REST接口,HTTP方法和资源地址相结合在一起才可以完成对一个资源的定位。

    点进@Path注解后查看其源码如下:

    package javax.ws.rs;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    @Target({ElementType.TYPEElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Path {
        String value();
    }

    我们可以发现其属于 javax.ws.rs 包,java.ws.rsjax-rs 规范中定义的包名,什么鬼?

    jax-rs 全称是:java API for RESTful Services,这是一套规范,规范文档

    简单点说就是这套规范是为构建restful服务的 [restful可以理解为web接口],我们知道Spring自带了对restful的支持,但是呢,Spring并没有遵循这个协议,言外之意就是不具有移植特性,怎么讲?

    举例:JPA大家应该了解吧,采用JPA开发规范的话,很容易实现MysqlOracle等数据库的迁移,因为符合规范。

    尽管Spring并不遵循这个协议,但还是Spring好用。

    再回到jax-rs,目前能够实现jax-rs标准的框架有很多,比如:

    • Apache CXF,开源的Web服务框架。
    • Jersey, 由Sun提供的JAX-RS的参考实现。
    • RESTEasy,JBoss的实现。
    • Restlet,由Jerome Louvel和Dave Pawson开发,是最早的REST框架,先于JAX-RS出现。
    • Apache Wink,一个Apache软件基金会孵化器中的项目,其服务模块实现JAX-RS规范。

    说这么多,其实是想get一点,就是在当下都用Spring的环境下,还是有许多可以实现web服务的,重新再来看@Path吧。

    @Path就当@RequestMapping来用,如下所例。

    @Path("/clientWeb")
    public class  ClientWeb{    
        @GET    
        @Path("/getData")
        public String getData(String params) {
            return “xxx";
        }
    }

    访问路径为:http://:/<应用域>/clientWeb/getData

    参考:https://segmentfault.com/q/1010000002484009/a-1020000002484552


    我创建了一个用来记录自己学习之路的公众号,感兴趣的小伙伴可以关注一下微信公众号:niceyoo

    展开全文
  • 在Eclipse里单击练习代码的@Path: 发现这个path的value还支持正则表达式: 所以我用了这样一个正则表达式: 测试成功: 用这个测试失败,这是我期望的:http://localhost:9498//hello/3 从Eclipse的outpu...

    这个annotation和Spring里的@RequestMapping作用完全一样。下图是Spring里的annotation:

    在Eclipse里单击练习代码的@Path:

    发现这个path的value还支持正则表达式:

    所以我用了这样一个正则表达式:

    测试成功:

    用这个测试失败,这是我期望的:http://localhost:9498//hello/3
    从Eclipse的output里还找到了框架解析这个@Path的入口代码,一举两得。在这个入口方法设置断点之后,

    然后就可以开始debug了:

    正则表达式是怎么被evaluate的

    我们在应用代码里指定的正则表达式总归要在某个地方被框架用到,通过上面的方法已经可以设断点debug了:
    这就是我们应用指定的正则表达式:

    这是我测试传入的路径:/3, 显然正则表达式instance的match()方法返回false,

    因此最后是404 error,

    要获取更多Jerry的原创文章,请关注公众号"汪子熙":

    展开全文
  • 一、背景 ...把xmlpath通用注释映射对应的字段。通过反射读取xmlPath,根据xmlPath读取xml文档对应节点内容后反射设值。 三、代码实现 1、缓存字段与xmlpath映射 import java.lang.reflect.Field;...

    一、背景

    xml文档节点比较多,结构层次复杂,而无需根据xml结构映射实体和取所有的节点内容(一两百个节点,只需取二十多个节点信息)。

    二、实现思路

    把xmlpath通用注释映射对应的字段。通过反射读取xmlPath,根据xmlPath读取xml文档对应节点内容后反射设值。

    三、代码实现

    1、缓存字段与xmlpath映射

    import java.lang.reflect.Field;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.ConcurrentMap;
    
    /**
     * 缓存解析过的xmlField
     *
     */
    public class XmlFieldBeanFactory {
    	
    
    	private final static ConcurrentMap<Class<?>, Map<Field,XmlField>> xmlTypeMap = new ConcurrentHashMap<Class<?>,Map<Field,XmlField>>();
    	
    	public static Map<Field,XmlField> getForClass(Class<?> type){
    		Map<Field,XmlField> xmlType = xmlTypeMap.get(type);
    		if(xmlType == null) {
    			xmlType = getXmlType(type);
    			xmlTypeMap.put(type, xmlType);
    		}
    		return xmlType;
    	}
    	
    	private static Map<Field,XmlField> getXmlType(Class<?> type){
    		
    		Map<Field,XmlField> xmlType = new HashMap<>();
    		while (type != null) {//当父类为null的时候说明到达了最上层的父类(Object类).
    		      for (Field field : type .getDeclaredFields()) {  
    		    	  if(field.isAnnotationPresent(XmlField.class)){//判断成员变量是否有注解
    		    		  xmlType.put(field, field.getAnnotation(XmlField.class));
    		    	  }
    		    	 }
    		      type = type.getSuperclass(); //得到父类,然后赋给自己
    		}
    
    		return xmlType;
    	}
    	
    }
    

     

    2、根据xmlPath获取body值或属性值

    import java.io.InputStream;
    import java.lang.reflect.Field;
    import java.lang.reflect.ParameterizedType;
    import java.lang.reflect.Type;
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import org.apache.commons.beanutils.BeanUtils;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.io.IOUtils;
    import org.dom4j.Document;
    import org.dom4j.Node;
    import org.dom4j.io.SAXReader;
    
    
    /**
     * xml path 映射
     *
     */
    public class XmlPathUtil {
    	
    	
    	/**
    	 * xml字符串转换Object
    	 * @param xml
    	 * @param clz 转换对象class
    	 * @return
    	 * @throws Exception
    	 */
    	public static <T> T convert(String xml,Class<T> clz) throws Exception{
    		InputStream in = null;
    		try {
    			in = IOUtils.toInputStream(xml);
    			return convert(in,clz);
    		}finally {
    			IOUtils.closeQuietly(in);
    		}
    	}
    	
    	/**
    	 * xml字符串转换Object
    	 * @param xml xml字符串
    	 * @param clz 转换对象class
    	 * @param ns 命名空间
    	 * @param alisa 别名
    	 * @return
    	 * @throws Exception
    	 */
    	public static <T> T convert(String xml,Class<T> clz,String ns,String alisa) throws Exception{
    		InputStream in = null;
    		try {
    			in = IOUtils.toInputStream(xml);
    			return convert(in,clz,ns,alisa);
    		}finally {
    			IOUtils.closeQuietly(in);
    		}
    	}
    	
    	/**
    	 * inputStream xml转Object
    	 * @param in
    	 * @param clz
    	 * @return
    	 * @throws Exception
    	 */
    	public static <T> T convert(InputStream in,Class<T> clz) throws Exception {
    			Document doc = new SAXReader().read(in);
    			return convert(doc,clz);
    	}
    	
    	/**
    	 * inputStream xml转Object
    	 * @param in
    	 * @param clz
    	 * @param ns 命名空间
    	 * @param alisa 别名
    	 * @return
    	 * @throws Exception
    	 */
    	
    	public static <T> T convert(InputStream in,Class<T> clz,String ns,String alisa) throws Exception {
    		SAXReader saxReader = new SAXReader();
    		Document doc = saxReader.read(in);
    		Map<String,String> map = new HashMap<>();  
    	    map.put(alisa,ns); 
    	    saxReader.getDocumentFactory().setXPathNamespaceURIs(map);
    		return convert(doc,clz);
    	}
    	
    	
    	
    	/**
    	 * document 转 Object
    	 * @param doc
    	 * @param clz
    	 * @return
    	 * @throws Exception 
    	 */
    	public static <T> T convert(Document doc,Class<T> clz) throws Exception {
    		T t = clz.newInstance();
    		getValue(doc, t, clz,"");
    		return t;
    	}
    	
    	@SuppressWarnings({ "rawtypes", "unchecked" })
    	private static void getValue(Document doc,Object obj,Class clz,String parentPath) throws Exception{
    		
    		Map<Field,XmlField> xmlType = XmlFieldBeanFactory.getForClass(clz);
    		
    		for(Field field : xmlType.keySet()) {
    			XmlField xmlField = xmlType.get(field);
    			Object value = field.getType().newInstance();
    			BeanUtils.setProperty(obj, field.getName(), value);
    			if(!xmlField.haveSon()) {
    				if(isCollection(field.getType())) {
    					Collection cl = (Collection) value;
    					List<Node> nodes = doc.selectNodes(parentPath+xmlField.path());
    					if(CollectionUtils.isNotEmpty(nodes)) {
    						for (Node node : nodes) {
    							cl.add(node.getText());
    						}
    					}
    					
    				}else {
    					if(xmlField.isText()) {
    						Node node = doc.selectSingleNode(parentPath+xmlField.path());
    						if(node!=null) {
    							BeanUtils.setProperty(obj,field.getName() , node.getText());
    						}
    					}else {
    						BeanUtils.setProperty(obj,field.getName() , doc.valueOf(parentPath+xmlField.path()));
    					}
    				}
    			}else {
    				if(isCollection(field.getType())){
    					Collection cl = (Collection) value;
    					List<Node> nodes = doc.selectNodes(parentPath+xmlField.path());
    					if(CollectionUtils.isNotEmpty(nodes)) {
    						for (Node node : nodes) {
    							 Type genericType = field.getGenericType();                   
    							  if(genericType == null) 
    								  return;                    // 如果是泛型参数的类型                    
    							  if(genericType instanceof ParameterizedType){                        
    								  ParameterizedType pt = (ParameterizedType) genericType;//得到泛型里的class类型对象                     
    								  Class<?> accountPrincipalApproveClazz = (Class<?>)pt.getActualTypeArguments()[0];
    								  Object v = accountPrincipalApproveClazz.newInstance();
    								  cl.add(v);
    								  getValue(node.getDocument(), v,accountPrincipalApproveClazz,node.getUniquePath());
    							  }
    						}
    					}
    				}else {
    					  Node node = doc.selectSingleNode(xmlField.path());
    					  if(node != null) {
    						  Object v = field.getType().newInstance();
    						  getValue(doc, v,field.getType(),node.getUniquePath());
    					  }
    					 
    				}
    			}
    		}
    
    	}
    	
    	
    	 private static boolean isCollection(Class<?> type) {
    		    return Collection.class.isAssignableFrom(type);
    	 }
    	
    
    }
    

     

    3、xmlField注解

    import static java.lang.annotation.ElementType.FIELD;
    import static java.lang.annotation.RetentionPolicy.RUNTIME;
    
    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;
    
    /**
     * 用于注解字段对应的xpath,进行解析xml
     *
     */
    
    @Retention(RUNTIME)
    @Target(FIELD)
    public @interface XmlField {
    	/**
    	 * xml对应的xPath路径
    	 * @return
    	 */
    	String path();
    	/**
    	 * 是否嵌套对象
    	 * @return
    	 */
    	boolean haveSon() default false;
    	
    	/**
    	 * 取属性值
    	 * @return
    	 */
    	boolean isText() default true;
    }
    

    4、Test

    实体类

    public class Grade {
    	
    	@XmlField(path = "/grade/student",haveSon=true)
    	private ArrayList<Student> students;
    	
    	@XmlField(path = "/grade/gradeCode/text()",isText=false)
    	private String gradeCode;
    	
    	@XmlField(path = "/grade/gradeName")
    	private String gradeName;
    
        //省略get set方法
    }
    
    
    public class Student {
    	
    	@XmlField(path="/id")
    	private String id;
    	
    	@XmlField(path="/name")
    	private String name;
    	
        //默认是直接获得text(),如果isText是false时,直接读取path的内容如@arr获取属性,text()获得文本内容
    	@XmlField(path="/@aliso",isText=false)
    	private String aliso;
       //省略get set方法
    }
    
    

    xml 文档内容

    <grade>
        <gradeCode>SD</gradeCode>
        <gradeName>grade2</gradeName>
        <student aliso="aa">
          <id>1</id>
          <name>zhangsan</name>
        </student>
        <student aliso="bb">
          <id>2</id>
          <name>lisi</name>
        </student>
    </grade>

    测试 

    public class Test {
    	public static void main(String[] args) throws Exception {
    		String xml = "<grade>\r\n" + 
    				"    <gradeCode>SD</gradeCode>\r\n" + 
    				"    <gradeName>grade2</gradeName>\r\n" + 
    				"    <student aliso=\"aa\">\r\n" + 
    				"      <id>1</id>\r\n" + 
    				"      <name>zhangsan</name>\r\n" + 
    				"    </student>\r\n" + 
    				"    <student aliso=\"bb\">\r\n" + 
    				"      <id>2</id>\r\n" + 
    				"      <name>lisi</name>\r\n" + 
    				"    </student>\r\n" + 
    				"</grade>";
    		Grade grade = XmlPathUtil.convert(xml, Grade.class);
    		System.out.println(grade);
    	}
    }

     

    展开全文
  • 在前文 中,我详细的阐述了如何撸出一个酷炫的Path动画View,我们的口号是:I have a path.I have a view. Oh~,Path(Anim)View. 本文的目标是:I have a pic.I have a view. Oh~,Path(Anim)View. 然后手把手教你图片-...

    本篇文章已授权微信公众号 hongyangAndroid (鸿洋)独家发布
    转载请标明出处:
    http://blog.csdn.net/zxt0601/article/details/54018970
    本文出自:【张旭童的博客】(http://blog.csdn.net/zxt0601)
    代码传送门:喜欢的话,随手点个star。多谢
    https://github.com/mcxtzhang/PathAnimView

    概述

    新年第一篇技术文章哈,大家新年快乐,先来个简单点的,主要介绍工具的使用,预预热,下周一奉上一个骚气的购物车动画按钮,敬请期待。

    在前文 给我一个Path,还你一个酷炫动画 中,我详细的阐述了如何撸出一个酷炫的Path动画View,我们的口号是:
    I have a path.I have a view. Oh~,Path(Anim)View.

    也就是说,只要有了Path,剩下的就是绚丽的动画。

    但是,这个Path要怎么获取呢,这是一个重点&痛点
    之前我也给出了三种解决方案,可以满足一些场景,但是SVG->Path的方案,在我心中仍是未完品。

    这就相当于我给大家看了一晚好喝的鸡汤,Path动画是很酷,然而我给的勺子还不够好,有些喝的不尽兴

    所以本文的目标是:I have a pic.I have a view. Oh~,Path(Anim)View.

    考虑到实际SVG/美工给的图->Path也是一种很大的数据源来源。
    于是我经过一段时间的搅基讨论,有了此文。
    本文会先简单回顾上文,然后手把手教你图片->SVG->Path的姿势.。
    从此酷炫Path动画,如此简单。

    效果先随便上几个图,以后你找到的图有多精彩,gif就有多精彩

    随便搜了一个铅笔画的图,丢进去

    随手复制的二维码icon
    随手复制的二维码icon

    来自大佬wing的铁塔

    前文回顾

    这里简单回顾一下前文,GIF如下图:
    上文效果图

    PathAnimView接受的唯一数据源是Path(给我一个Path,还你一个动画View)
    所以内置了几种将别的资源->Path的方法:

    • 直接传string。(A-Z,0-9 “.” “- ” “)
        //根据String 转化成Path
        setSourcePath(PathParserUtils.getPathFromArrayFloatList(StoreHousePath.getPath("ZhangXuTong", 1.1f, 16)));
    • 定义在R.array.xxx里
        //动态设置 从StringArray里取
        storeView2.setSourcePath(PathParserUtils.getPathFromStringArray(this, R.array.storehouse, 3));
    • 简单的SVG(半成品)
            //SVG转-》path
            //还在完善中,我从github上找了如下工具类,发现简单的SVG可以转path,复杂点的 就乱了
    /*        SvgPathParser svgPathParser = new SvgPathParser();
            try {
                Path path = svgPathParser.parsePath("M1,1 L1,50 L50,50 L50,50 L50,1 Z");
                storeView3.setSourcePath(path);
            } catch (ParseException e) {
                e.printStackTrace();
            }*/

    当时我称之为简单的SVG ,因为当时我对SVG也不是很懂,现在经过一段时间的学习和基友们的讨论(wing神,白神,群友等),我才知道我从gayhub上找到的这个工具类,是可以将标准的SVG转换为Android中的Path(android.graphics.Path)的。

    之前的痛点

    之前我转换失败的,所谓 复杂的SVG,其实是我直接利用AS生成的vector(我称之android svg)。里面对标准SVG进行了一些语法的扩充,才导致我转换的失败。(例如扩充了 S,Q等标记)
    例如我们利用AS的工具,直接生成一个Android机器人的icon的Vector,之前我以为这就是SVG数据了,实际上我发现这是对标准SVG进行了扩展,


    生成的数据如下:


    标红处可以看到,1.5s, s并不在标准的SVG语法中,所以解析会出错。

    图片->SVG->Path的正确姿势

    那么为什么我今天又敢出来写(zhuang)博(b)客了呢,因为我已经有了解决这个问题的方案。
    好,让我们想一下,实际开发中,如果要用Path动画,我们的场景是什么?
    嗯,看到一张想要她动的图,或 UI妹子给了你一张,让你自己动的图。

    步骤一:图->SVG


    利用vmde软件,我们可以轻松完成将图->SVG。(大佬wing提供的方案)
    首先我们先get一张喜欢的图,可以从Iconfont里取,下载方式直接不要选SVG,这里的SVG直接使用会有问题,原因不明.我们就选PNG下载即可。

    然后打开vmde软件

    • 直接将刚才的PNG图片拖入其中
    • 点击右上角的全自动
    • 点击完成
    • 点击另存,格式记得选择*.svg

    其实现在我们已经可以用一些文本编辑工具直接打开SVG,并且复制其中的PathData,以String形式传入PathAnimView即可。

                Path path = svgPathParser.parsePath(pathString);
                storeView3.setSourcePath(path);

    嗯,方法其实就这么简单,但是~有很多的图,是有N段PathData的,也就是说复制起来极其麻烦,
    而且如果要从中剔除一些不需要的Path,或者改变几个Path的绘制顺序,就更难筛选了。

    步骤二:利用工具网站预览Path

    于是我就求白神给我做了一个工具页面,它可以完成SVG的解析、预览、并将每段Path分隔开,方便我们复制黏贴。
    http://liuyouth.github.io/utils/svg2android/index.html
    使用方式也相当简单,直接拖动SVG的图丢进去即可。

    解析后的效果图
    可以看到,我们可以方便的选取每一段Path,如果我只需要最外面的齿轮,那我只对齿轮部分点击select all即可。

    也可以调整顺序,例如我想先绘制外圈,就将外圈的Path放在前面复制进我们的app中。

    这里再拿文首第一张妹子图举例:
    经过PNG->SVG->预览的步骤后,如下:

    vmde给我们生成了海量的path数据,我们只想要其中一部分有用的,
    于是通过预览&放大,我只复制了两段Path,效果就如文首了。

    关于这个网站,白神已经承诺我,会尽快加上图片预览的放大一键复制全部Path的功能,大家敬请期待。

    手摸手实战:支付宝支付成功动画

    其实支付宝支付成功动画相当简单,路径 就是画一个圆 + 一个勾.
    路径的获取,可以:

    • 利用本文介绍的图片->SVG->Path 的方法。
    • 也直接用Path的一些draw方法实现。

    我们利用本文的办法去实现:
    1 拿到这张图

    2 PNG丢进vmde
    3 SVG丢进工具网页

    4 根据预览,依次复制一个圆 + 一个勾的String。

            String success = "...PathString";

    5 利用SvgPathParser工具类得到Path

        Path path = svgPathParser.parsePath(success);

    6 设置给PathAnimView.

        storeView3.setSourcePath(path);

    效果图:

    总结

    代码传送门:喜欢的话,随手点个star。多谢
    https://github.com/mcxtzhang/PathAnimView

    现在我们已经可以做到,I have a pic.I have a view. Oh~,Path(Anim)View.
    步骤:

    • 一张图
    • 丢进vmde
    • 丢进SVG-Path预览网站
    • 复制需要的Path以String形式传入PathAnimView
    • 酷炫动画

    在提取出SVG中的Path数据后,我个人喜欢将比较长的Path,放进values目录下一个新建文件paths.xml中,以以下形式存储:

        <string name="xxx"> 复制过来的Path数据</string>

    java代码中如下设置:

            String xxx= getString(R.string.xxx);
            Path path = svgPathParser.parsePath(xxx);
            storeView.setSourcePath(path);

    想了解更详细的使用以及细节,请下载DEMO后查看。

    看大神们都有QQ群,
    向他们靠齐。
    我也建了个QQ搞基交流群:
    557266366 。

    下文预告

    最近略忙,项目电商模块重构,UI升级,于是我撸了一个购物车的控件:
    项目中使用的效果
    图录花了,我也不管了。。下次博文重新录个图吧,大家重点看购物车伸缩旋转闪转腾挪的动画即可。

    代码已经撸完,考虑到了View的回收复用,
    并且可以看到在RecyclerView中使用,切换LayoutManager也是没有问题的,
    博文在梳理中,预计下周一输出。
    心急可先去gayhub查看代码:
    https://github.com/mcxtzhang/AnimShopButton

    文章地址:
    http://blog.csdn.net/zxt0601/article/details/54235736

    鸣谢

    白神的个人网站,很666的一个全栈
    wing,邮电三精-大精,又称静静,奶zi静

    转载请标明出处:
    http://blog.csdn.net/zxt0601/article/details/54018970
    本文出自:【张旭童的博客】(http://blog.csdn.net/zxt0601)
    代码传送门:喜欢的话,随手点个star。多谢
    https://github.com/mcxtzhang/PathAnimView

    展开全文
  • 平时使用Retrofit来实现动态地址时,用的是@Url注解,但是需要传地址时连带baseUrl一起给出,这次使用@Path注解来实现,base地址不变,只需改变后边的地址。 @Multipart @POST("/{param}") open fun uploadFile(@...
  • @Path注解的源码如下,可以看到该注解可以标记在类上,也可以标记在方法上,接收一个value参数,标识资源的地址。 @Target ( { ElementType . TYPE , ElementType . METHOD } ) @Retention ( RetentionPolicy . ...
  • 其中@Path、@Query、@QueryMap 使用 Get 请求 , 加入使用了Post 请求注解使用@Path 一般都会是项目崩溃的, 所以这里我总结了一下自己使用的经验 1 @Path 会是url 中带有参数一般配合{} 一起 @GET("toutiao/...
  • RequestMapping 注解path和value有什么区别?
  • 本文浅析 springboot 中的 context-path servlet-path endpoint-path 相关的配置使用
  • 这种情况可以理解为参数和接口地址在一起,而我们在使用Retrofit去请求的时候,参数注解就需要使用@Path,具体代码如下: @GET("data/{sid}/{page}") Call<DataBean> getItem(@Path("sid") String sid, @Path...
  • js文件三斜杠注释///reference path用途

    千次阅读 2018-01-26 09:45:23
    编辑某个js文件时,要想这个js文件出现其他js成员的ide提示,可以再js文件开头使用3个斜杠注释和reference指令的path指向此js文件路径,这样在编写这个js文件时,ide就会自动出现path指向的js文件中的成员。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 446,762
精华内容 178,704
关键字:

path注解