「java注解demo」java注解的作用有哪些
本篇文章给大家谈谈java注解demo,以及java注解的作用有哪些对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。
本文目录一览:
- 1、怎样优雅地使用java注解
- 2、关于java注解方法isAnnotationPresent
- 3、如何创建,使用以及解析自定义注解
- 4、java注解继承和Inherited的关系
- 5、java注解的类型可以是哪些
怎样优雅地使用java注解
优雅的使用Java注解的前提是理解Java注解,并学习优秀的Java注解的使用demo。
注解作用:每当你创建描述符性质的类或者接口时,一旦其中包含重复性的工作,就可以考虑使用注解来简化与自动化该过程。
Java提供了四种元注解,专门负责新注解的创建工作。
比如Junit3和Junit4 ,比如Servlet2与Servlet3 比如Hibernate3与Hibernate4 比如Spring2之后的Spring版本,都引用注解这一机制,作用就是利用注解将一些本来重复性的工作,变成程序自动完成,简化和自动化该过程(PostScript:上述各个组件我也不是很熟悉,具体加入注解的版本是几不一定正确)。
关于java注解方法isAnnotationPresent
1、声明
以下是java.lang.Package.isAnnotationPresent()方法的声明
public boolean isAnnotationPresent(Class? extends Annotation annotationClass)
2、参数
annotationClass -- 对应于注释类型的Class对象
3、返回值
如果一个注解指定注释类型是存在于此元素上此方法返回true,否则返回false
4、异常
NullPointerException -- 如果给定的注释类为null
5、例子
下面的例子显示lang.Object.isAnnotationPresent()方法的使用:
package com.yiibai;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
// declare a new annotation
@Retention(RetentionPolicy.RUNTIME)
@interface Demo {
String str();
int val();
}
public class PackageDemo {
// set values for the annotation
@Demo(str = "Demo Annotation", val = 100)
// a method to call in the main
public static void example() {
PackageDemo ob = new PackageDemo();
try {
Class c = ob.getClass();
// get the method example
Method m = c.getMethod("example");
// get the annotation for class Demo
Demo annotation = m.getAnnotation(Demo.class);
// print the annotation
System.out.println(annotation.str() + " " + annotation.val());
} catch (NoSuchMethodException exc) {
exc.printStackTrace();
}
}
public static void main(String args[]) {
example();
Package[] pack = Package.getPackages();
// check if annotation hello exists
for (int i = 0; i pack.length; i++) {
System.out.println("" + pack[i].isAnnotationPresent(Demo.class));
}
}
}
编译和运行上面的程序,这将产生以下结果:
Demo Annotation 100
false
false
false
false
false
false
false
false
false
false
false
false
false
false
false
false
false
false
false
false
false
false
false
false
false
false
false
false
false
false
false
false
false
false
false
false
false
如何创建,使用以及解析自定义注解
首先要想使用自定义注解,必须创建自己的注解类
右键项目,new - Annotation
然后在注解里定义自己的方法,该方法是别的类使用注解时需要填的属性
package com.sy.demo.annotation;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Table {public String value();}
注:如果只有一个方法时,应该用value()来指定方法名,这样就可以直接简写@Table("xxx")而不是@Table(aaa="xxx");
其中注解类上的注解称为元注解
@Target(ElementType.TYPE)
@Target的意思是,该注解类是放在什么位置的,是放在类上、字段上还是方法上,ElementType.TYPE意思是只能放在类上或接口上,ElementType.FIELD意思是只能放在字段上等等。
如果有多个位置选择可以这么写:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
意思是作用域,一般写RUNTIME就行
@Documented
意思是是否在生成JavaDoc时加入该注解类,这个看情况写不写
还有其他元注解,想要研究的就自己研究吧
定义完自定义注解了,下面就是使用的时候了
package com.sy.demo.entity;import com.sy.demo.annotation.Column;import com.sy.demo.annotation.Table;@Table("tdb_user")public class User { @Column("id") private Long id; @Column("email") private String email; @Column("password") private String password; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; }}
在这里我定义了一个实体类,用于表示用户信息,其中还是用了一个@Column类,代码如下
package com.sy.demo.annotation;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Column { public String value();}
由代码可知,@Column是放在field上的
使用也使用完了,下面该是解析的时候了。
package com.sy.demo.util;import java.lang.reflect.Field;import com.sy.demo.annotation.Column;import com.sy.demo.annotation.Table;public class SqlUtil { private static final String EMPTY = ""; @SuppressWarnings("unchecked") public static String getSql(Object object) { StringBuilder sb = new StringBuilder(); ClassObject c; boolean isExist; Column column; String columnName; String getMethodName; Object columnValue; String[] strs; try { c = (ClassObject) object.getClass(); isExist = c.isAnnotationPresent(Table.class); if (!isExist) { return EMPTY; } Table table = c.getAnnotation(Table.class); sb.append(" SELECT * FROM " + table.value() + " WHERE 1 = 1 " ); Field[] fields = c.getDeclaredFields(); for (Field field: fields) { isExist = field.isAnnotationPresent(Column.class); if (!isExist) { continue; } column = field.getAnnotation(Column.class); columnName = column.value(); getMethodName = "get" + columnName.substring(0, 1).toUpperCase() + columnName.substring(1); columnValue = c.getMethod(getMethodName, new Class[0]).invoke(object, new Object[0]); if (columnValue == null) { continue; } if (columnValue instanceof String) { columnValue = (String)columnValue; if(((String) columnValue).contains(",")) { sb.append("AND " + columnName + " IN ("); strs = ((String) columnValue).split(","); for(String str: strs) { sb.append("'" + str + "',"); } sb.deleteCharAt(sb.length() - 1); sb.append(") "); } else { sb.append("AND " + columnName + " = '" + columnValue + "' "); } } else if (columnValue instanceof Integer || columnValue instanceof Long) { sb.append("AND " + columnName + " = " + columnValue + " "); } } } catch (Exception e) { e.printStackTrace(); } return sb.toString(); }}
解析的时候用的是反射机制,可能看着比较麻烦比较乱,而且也新手可能也不太理解,在用的时候会发现其实还是挺方便的。
原理解释根据反射找到User类,在判断是否有注解,接着拼接sql什么的
整个列子项目中完整的代码如下(有许多步骤测试用例,懒得删了,全贴出来吧)
Controller
package com.sy.demo.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import com.sy.demo.entity.User;import com.sy.demo.service.IUserService;@Controller@RequestMapping("hello")public class UserController { @Autowired private IUserService hService; @RequestMapping(value = "demo1") public String demo1() { return "demo1"; } @SuppressWarnings("deprecation") @RequestMapping(value = "demo2") public String demo2() { return hService.test(); } @RequestMapping(value = "demo3") @ResponseBody public String demo3() { User user = new User(); user.setId(1L); user.setEmail("mr_songyang1990@163.com"); user.setPassword("1q2w3e4r,123456,aaaaa"); return hService.getUser(user); } @RequestMapping(value = "demo4") @ResponseBody public String demo4() { User user = new User(); user.setId(1L); user.setEmail("mr_songyang1990@163.com"); user.setPassword("1q2w3e4r,123456,aaaaa"); return hService.getUser2(user); }}
service:
package com.sy.demo.service;import com.sy.demo.entity.User;public interface IUserService { @Deprecated public String test(); public String getUser(User user); public String getUser2(User user);}
package com.sy.demo.service.impl;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import com.sy.demo.entity.User;import com.sy.demo.repository.IUserRepository;import com.sy.demo.service.IUserService;@Service("hService")public class UserServiceImpl implements IUserService { @Autowired private IUserRepository hRepository; @Deprecated @Override public String test() { return "demo2"; } @Override public String getUser(User user) { return hRepository.queryUser(user); } @Override public String getUser2(User user) { return hRepository.queryUser2(user); }}
Repository
package com.sy.demo.service.impl;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import com.sy.demo.entity.User;import com.sy.demo.repository.IUserRepository;import com.sy.demo.service.IUserService;@Service("hService")public class UserServiceImpl implements IUserService { @Autowired private IUserRepository hRepository; @Deprecated @Override public String test() { return "demo2"; } @Override public String getUser(User user) { return hRepository.queryUser(user); } @Override public String getUser2(User user) { return hRepository.queryUser2(user); }}
package com.sy.demo.repository.impl;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;import org.springframework.stereotype.Repository;import com.sy.demo.entity.User;import com.sy.demo.repository.IUserRepository;import com.sy.demo.util.DBUtil;import com.sy.demo.util.SqlUtil;@Repository("hRepository")public class UserRepositoryImpl implements IUserRepository { public String queryUser(User user) { String sql = SqlUtil.getSql(user); System.out.println(sql); return sql; } @Override public String queryUser2(User user) { StringBuilder sb = new StringBuilder(); String sql = SqlUtil.getSql(user); System.out.println(sql); PreparedStatement ps = DBUtil.getPreparedStatement(sql); Long id; String email; String password; try { ResultSet rs = ps.executeQuery(); while (rs.next()) { id = rs.getLong("id"); email = rs.getString("email"); password = rs.getString("password"); sb.append("ID:").append(id).append(", email:"). append(email).append(", password:").append(password).append("br/"); } } catch (SQLException e) { e.printStackTrace(); } return sb.toString(); }}
entity:
package com.sy.demo.entity;import com.sy.demo.annotation.Column;import com.sy.demo.annotation.Table;@Table("tdb_user")public class User { @Column("id") private Long id; @Column("email") private String email; @Column("password") private String password; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; }}
annotation
package com.sy.demo.annotation;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Table { public String value();}
package com.sy.demo.annotation;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Column { public String value();}
util工具类
package com.sy.demo.util;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.SQLException;import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;public class DBUtil { public static final String URL = "jdbc:mysql://localhost:3306/db_test"; public static final String USERNAME = "root"; public static final String PASSWORD = "root"; public static Connection conn = null; public static MysqlDataSource dataSource; static { dataSource = new MysqlDataSource(); dataSource.setUser(USERNAME); dataSource.setPassword(PASSWORD); dataSource.setURL(URL); } public static PreparedStatement getPreparedStatement(String sql) { try { conn = dataSource.getConnection(); return conn.prepareStatement(sql); } catch (SQLException e) { e.printStackTrace(); } return null; }}
package com.sy.demo.util;import java.lang.reflect.Field;import com.sy.demo.annotation.Column;import com.sy.demo.annotation.Table;public class SqlUtil { private static final String EMPTY = ""; @SuppressWarnings("unchecked") public static String getSql(Object object) { StringBuilder sb = new StringBuilder(); ClassObject c; boolean isExist; Column column; String columnName; String getMethodName; Object columnValue; String[] strs; try { c = (ClassObject) object.getClass(); isExist = c.isAnnotationPresent(Table.class); if (!isExist) { return EMPTY; } Table table = c.getAnnotation(Table.class); sb.append(" SELECT * FROM " + table.value() + " WHERE 1 = 1 " ); Field[] fields = c.getDeclaredFields(); for (Field field: fields) { isExist = field.isAnnotationPresent(Column.class); if (!isExist) { continue; } column = field.getAnnotation(Column.class); columnName = column.value(); getMethodName = "get" + columnName.substring(0, 1).toUpperCase() + columnName.substring(1); columnValue = c.getMethod(getMethodName, new Class[0]).invoke(object, new Object[0]); if (columnValue == null) { continue; } if (columnValue instanceof String) { columnValue = (String)columnValue; if(((String) columnValue).contains(",")) { sb.append("AND " + columnName + " IN ("); strs = ((String) columnValue).split(","); for(String str: strs) { sb.append("'" + str + "',"); } sb.deleteCharAt(sb.length() - 1); sb.append(") "); } else { sb.append("AND " + columnName + " = '" + columnValue + "' "); } } else if (columnValue instanceof Integer || columnValue instanceof Long) { sb.append("AND " + columnName + " = " + columnValue + " "); } } } catch (Exception e) { e.printStackTrace(); } return sb.toString(); }}
java注解继承和Inherited的关系
一般来说java的注解是不可以继承的。
意思就是java的注解,在子类里面是拿不到的。
但是客户以通过给注解添加@Inherited注解来实现继承。
可是。。
看看demo:
Ani注解标记了@Inherited,是一个可以继承的注解。
package testannotation;
/**
* 描述:
*
* @author: DASHU
* @since: 14-1-16.
*/
@Ani("sup")
@AniNoi("sup noi")
public class SupClass {
@Ani("sup a")
public String a;
@AniNoi("sup b")
public String b;
@Ani("sup c")
public void c(){}
@AniNoi("sup d")
public void d(){};
}
java注解的类型可以是哪些
使用注解
在一般的Java开发中,最常接触到的可能就是@Override和@SupressWarnings这两个注解了。使用@Override的时候只需要一个简单的声明即可。这种称为标记注解(marker annotation ),它的出现就代表了某种配置语义。而其它的注解是可以有自己的配置参数的。配置参数以名值对的方式出现。使用 @SupressWarnings的时候需要类似@SupressWarnings({"uncheck", "unused"})这样的语法。在括号里面的是该注解可供配置的值。由于这个注解只有一个配置参数,该参数的名称默认为value,并且可以省略。而花括号则表示是数组类型。在JPA中的@Table注解使用类似@Table(name = "Customer", schema = "APP")这样的语法。从这里可以看到名值对的用法。在使用注解时候的配置参数的值必须是编译时刻的常量。
从某种角度来说,可以把注解看成是一个XML元素,该元素可以有不同的预定义的属性。而属性的值是可以在声明该元素的时候自行指定的。在代码中使用注解,就相当于把一部分元数据从XML文件移到了代码本身之中,在一个地方管理和维护。
开发注解
在一般的开发中,只需要通过阅读相关的API文档来了解每个注解的配置参数的含义,并在代码中正确使用即可。在有些情况下,可能会需要开发自己的注解。这在库的开发中比较常见。注解的定义有点类似接口。下面的代码给出了一个简单的描述代码分工安排的注解。通过该注解可以在源代码中记录每个类或接口的分工和进度情况。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Assignment {
String assignee();
int effort();
double finished() default 0;
}
@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型。可以通过default来声明参数的默认值。在这里可以看到@Retention和@Target这样的元注解,用来声明注解本身的行为。@Retention用来声明注解的保留策略,有CLASS、RUNTIME和SOURCE这三种,分别表示注解保存在类文件、JVM运行时刻和源代码中。只有当声明为RUNTIME的时候,才能够在运行时刻通过反射API来获取到注解的信息。@Target用来声明注解可以被添加在哪些类型的元素上,如类型、方法和域等。
处理注解
在程序中添加的注解,可以在编译时刻或是运行时刻来进行处理。在编译时刻处理的时候,是分成多趟来进行的。如果在某趟处理中产生了新的Java源文件,那么就需要另外一趟处理来处理新生成的源文件。如此往复,直到没有新文件被生成为止。在完成处理之后,再对Java代码进行编译。JDK 5中提供了apt工具用来对注解进行处理。apt是一个命令行工具,与之配套的还有一套用来描述程序语义结构的Mirror API。Mirror API(com.sun.mirror.*)描述的是程序在编译时刻的静态结构。通过Mirror API可以获取到被注解的Java类型元素的信息,从而提供相应的处理逻辑。具体的处理工作交给apt工具来完成。编写注解处理器的核心是AnnotationProcessorFactory和AnnotationProcessor两个接口。后者表示的是注解处理器,而前者则是为某些注解类型创建注解处理器的工厂。
以上面的注解Assignment为例,当每个开发人员都在源代码中更新进度的话,就可以通过一个注解处理器来生成一个项目整体进度的报告。 首先是注解处理器工厂的实现。
public class AssignmentApf implements AnnotationProcessorFactory {
public AnnotationProcessor getProcessorFor(SetAnnotationTypeDeclaration atds,? AnnotationProcessorEnvironment env) {
if (atds.isEmpty()) {
return AnnotationProcessors.NO_OP;
}
return new AssignmentAp(env); //返回注解处理器
}
public CollectionString supportedAnnotationTypes() {
return Collections.unmodifiableList(Arrays.asList("annotation.Assignment"));
}
public CollectionString supportedOptions() {
return Collections.emptySet();
}
}
AnnotationProcessorFactory接口有三个方法:getProcessorFor是根据注解的类型来返回特定的注解处理器;supportedAnnotationTypes是返回该工厂生成的注解处理器所能支持的注解类型;supportedOptions用来表示所支持的附加选项。在运行apt命令行工具的时候,可以通过-A来传递额外的参数给注解处理器,如-Averbose=true。当工厂通过 supportedOptions方法声明了所能识别的附加选项之后,注解处理器就可以在运行时刻通过AnnotationProcessorEnvironment的getOptions方法获取到选项的实际值。注解处理器本身的基本实现如下所示。
public class AssignmentAp implements AnnotationProcessor {
private AnnotationProcessorEnvironment env;
private AnnotationTypeDeclaration assignmentDeclaration;
public AssignmentAp(AnnotationProcessorEnvironment env) {
this.env = env;
assignmentDeclaration = (AnnotationTypeDeclaration) env.getTypeDeclaration("annotation.Assignment");
}
public void process() {
CollectionDeclaration declarations = env.getDeclarationsAnnotatedWith(assignmentDeclaration);
for (Declaration declaration : declarations) {
processAssignmentAnnotations(declaration);
}
}
private void processAssignmentAnnotations(Declaration declaration) {
CollectionAnnotationMirror annotations = declaration.getAnnotationMirrors();
for (AnnotationMirror mirror : annotations) {
if (mirror.getAnnotationType().getDeclaration().equals(assignmentDeclaration)) {
MapAnnotationTypeElementDeclaration, AnnotationValue values = mirror.getElementValues();
String assignee = (String) getAnnotationValue(values, "assignee"); //获取注解的值
}
}
}
}
注解处理器的处理逻辑都在process方法中完成。通过一个声明(Declaration)的getAnnotationMirrors方法就可以获取到该声明上所添加的注解的实际值。得到这些值之后,处理起来就不难了。
在创建好注解处理器之后,就可以通过apt命令行工具来对源代码中的注解进行处理。 命令的运行格式是apt -classpath bin -factory annotation.apt.AssignmentApf src/annotation/work/*.java,即通过-factory来指定注解处理器工厂类的名称。实际上,apt工具在完成处理之后,会自动调用javac来编译处理完成后的源代码。
JDK 5中的apt工具的不足之处在于它是Oracle提供的私有实现。在JDK 6中,通过JSR 269把自定义注解处理器这一功能进行了规范化,有了新的javax.annotation.processing这个新的API。对Mirror API也进行了更新,形成了新的javax.lang.model包。注解处理器的使用也进行了简化,不需要再单独运行apt这样的命令行工具,Java编译器本身就可以完成对注解的处理。对于同样的功能,如果用JSR 269的做法,只需要一个类就可以了。
@SupportedSourceVersion(SourceVersion.RELEASE_6)
@SupportedAnnotationTypes("annotation.Assignment")
public class AssignmentProcess extends AbstractProcessor {
private TypeElement assignmentElement;
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
Elements elementUtils = processingEnv.getElementUtils();
assignmentElement = elementUtils.getTypeElement("annotation.Assignment");
}
public boolean process(Set? extends TypeElement annotations, RoundEnvironment roundEnv) {
Set? extends Element elements = roundEnv.getElementsAnnotatedWith(assignmentElement);
for (Element element : elements) {
processAssignment(element);
}
}
private void processAssignment(Element element) {
List? extends AnnotationMirror annotations = element.getAnnotationMirrors();
for (AnnotationMirror mirror : annotations) {
if (mirror.getAnnotationType().asElement().equals(assignmentElement)) {
Map? extends ExecutableElement, ? extends AnnotationValue values = mirror.getElementValues();
String assignee = (String) getAnnotationValue(values, "assignee"); //获取注解的值
}
}
}
}
仔细比较上面两段代码,可以发现它们的基本结构是类似的。不同之处在于JDK 6中通过元注解@SupportedAnnotationTypes来声明所支持的注解类型。另外描述程序静态结构的javax.lang.model包使用了不同的类型名称。使用的时候也更加简单,只需要通过javac -processor annotation.pap.AssignmentProcess Demo1.java这样的方式即可。
上面介绍的这两种做法都是在编译时刻进行处理的。而有些时候则需要在运行时刻来完成对注解的处理。这个时候就需要用到Java的反射API。反射API提供了在运行时刻读取注解信息的支持。不过前提是注解的保留策略声明的是运行时。Java反射API的AnnotatedElement接口提供了获取类、方法和域上的注解的实用方法。比如获取到一个Class类对象之后,通过getAnnotation方法就可以获取到该类上添加的指定注解类型的注解。
实例分析
下面通过一个具体的实例来分析说明在实践中如何来使用和处理注解。假定有一个公司的雇员信息系统,从访问控制的角度出发,对雇员的工资的更新只能由具有特定角色的用户才能完成。考虑到访问控制需求的普遍性,可以定义一个注解来让开发人员方便的在代码中声明访问控制权限。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiredRoles {
String[] value();
}
下一步则是如何对注解进行处理,这里使用的Java的反射API并结合动态代理。下面是动态代理中的InvocationHandler接口的实现。
public class AccessInvocationHandlerT implements InvocationHandler {
final T accessObj;
public AccessInvocationHandler(T accessObj) {
this.accessObj = accessObj;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
RequiredRoles annotation = method.getAnnotation(RequiredRoles.class); //通过反射API获取注解
if (annotation != null) {
String[] roles = annotation.value();
String role = AccessControl.getCurrentRole();
if (!Arrays.asList(roles).contains(role)) {
throw new AccessControlException("The user is not allowed to invoke this method.");
}
}
return method.invoke(accessObj, args);
}
}
在具体使用的时候,首先要通过Proxy.newProxyInstance方法创建一个EmployeeGateway的接口的代理类,使用该代理类来完成实际的操作。
java注解demo的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于java注解的作用有哪些、java注解demo的信息别忘了在本站进行查找喔。
发布于:2022-11-22,除非注明,否则均为
原创文章,转载请注明出处。