之前一段时间在折腾ServiceCenter服务发布中心,这玩意是个框架,主要作用是集成WebService与Http服务。
同时发布两种服务,互不影响,业务逻辑,通过应用(SDK模块)的方式集成进去。
通过将业务模块化,来实现业务模块的可插拔。
这个框架与应用之间有一些参数肯定是需要进行关联映射的,使用的是比较普通的配置文件进行关联的。
当时也没有觉得不妥,后来在框架上线部署....
之前有段时间没事看看Spring,看到了Spring的注解...
然后就想,这个ServiceCenter框架与模块之间能不能不写配置文件,通过注解的方式,传递必要的一些参数...
然后就有了大致的一个思路:
1.首先自定义注解
2.在模块的主类的中使用
3.模块在加入框架之后,框架要能扫描到这个注解所在的主类。
好了,下面直接贴笔记、代码
Java注解
参考资料:
《深入理解Java:注解(Annotation)自定义注解入门》
元注解
元注解的作用是负责注解其他的注解(自定义注解);
Java5中定义了四个标准的meta-annotation类型,它们被用来提供对其他annotation类型说明。
1. @Target
2. @Retention
3. @Documented
4. @Inherited
上面这些类型和他们所支持的类,可以在java.lang.annotation包中找到。
下面我们分别来看看。
@Target
该注解说明了Annoataion所修饰的对象范围,在Annotation类型的生命中使用了target便可以更加明确其修饰的目标。
取值:
CONSTRUCTOR | 用于描述构造器 |
FIELD | 用于描述域 |
LOCAL_VARIABLE | 用于描述局部变量 |
METHOD | 用于描述方法 |
PACKAGE | 用于描述包 |
PARAMETER | 用于描述参数 |
TYPE | 用于描述类、接口(包括注解类型) 或enum声明 |
@Retention
该注解定义了Annotation被保留的时间长短;用于描述该注解的生命周期
取值:
SOURCE | 在源文件中有效(即源文件保留) |
CLASS | 在Class文件中有效(即class保留) |
RUNTIME | 在运行时有效(即运行时保留) |
@Documented
该注解用于描述其他类型的annotation应该被作为被标注程序成员的公共API,因此可以被例如javadoc此类的工具文档化。
Documented是一个标记注解,没有成员。
@Inherited
该注解是一个标记注解,Inherited注解阐述了某个被标注的类型是被继承的。
例如一个使用了@Inherited修改的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
自定义注解
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。
在定义注解时,不能继承其他注解或接口。
@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。
方法的名称就是参数的名称,返回值类型就是参数的类型。可以通过default来声明参数的默认值。
定义注解格式:
public @interface 注解名 {定义体}
注解参数可支持数据类型:
1. 所有的基本类型
2. String类型
3. Class类型
4. enum类型
5. Annotation类型
6. 以上所有类型的数组
下面来一个Demo
/**
* 水果名称注解
*
* @author CYX
* @time 2017年9月13日上午9:18:24
*/
// 注解所修饰的对象范围(类、接口、枚举、成员变量、方法参数等..),这里是用于描述域
@Target(ElementType.FIELD)
// 定义该注释被保留的时间长短(生命周期);这里是在运行时有效
@Retention(RetentionPolicy.RUNTIME)
// 标记注解,javadoc文档化
@Documented
public @interface FruitName {
String value() default "";
}
/**
* 水果颜色注解
*
* @author CYX
* @time 2017年9月13日上午9:24:24
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
/**
* 颜色枚举
*
* @author CYX
* @time 2017年9月13日上午9:25:34
*/
public enum Color {
BULE, RED, GREEN
};
/**
* 颜色属性
*
* @return
*/
Color fruitColor() default Color.GREEN;
}
public class Apple {
@FruitName(value = "Apple")
private String appleName;
@FruitColor(fruitColor = Color.GREEN)
private String appleColor;
public String getAppleName() {
return appleName;
}
public void setAppleName(String appleName) {
this.appleName = appleName;
}
public String getAppleColor() {
return appleColor;
}
public void setAppleColor(String appleColor) {
this.appleColor = appleColor;
}
public void displayName() {
System.out.println("水果名字是:苹果");
}
}
public class TestMain {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("com.demo.annotation.annotation_1.Apple");
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
Annotation annotation = field.getAnnotation(FruitName.class);
if (annotation instanceof FruitName) {
FruitName fruitName = (FruitName) annotation;
System.out.println(fruitName.value());
}
}
}
}
下面贴上获取注解的其他方式,仅供参考
1。
2.
3.
4.
使用注解之后,怎么找到这个注解所在的类呢?
这里就需要一个扫描所有类的工具,直接上代码:
/**
* 处理Class类相关的工具类
*
* @author Simba
* @time 2017年9月12日下午7:23:37
*/
public class ClassAnalysis {
/**
* 传入指定包路径,获取包下所有全类名。<br>
*
* <p>
* <tt>注意:eclipse直接导出的jar包,可能无法读取,因为jar包 <i>/META-INF/MANIFEST.MF</i>文件中,缺少补充信息。</tt>
* </p>
*
* <p>
* <tt>推荐使用Ant打包,或者使用Eclipse中的build.xml进行打包</tt>
* </p>
*
* @param packageName
* 包名(例如:org、org.apache)
*
* @return 指定包名下,所有全类名
*
* @throws Exception
*/
public static Set<String> getClassNamesByPackage(String packageName) throws Exception {
// 第一个class类的集合,使用linkedHashSet为了保证顺序。
Set<String> classes = new LinkedHashSet<String>();
// 是否循环迭代
boolean recursive = true;
// 获取包的名字 并进行替换
String packageDirName = packageName.replace('.', '/');
// 定义一个枚举的集合 并进行循环来处理这个目录下的things
Enumeration<URL> dirs;
try {
dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
} catch (IOException e) {
throw new Exception(e);
}
// 循环迭代下去
while (dirs.hasMoreElements()) { // 获取下一个元素
try {
URL url = dirs.nextElement();
System.err.println(url);
// 得到协议的名称
String protocol = url.getProtocol();
// 如果是以文件的形式
if ("file".equals(protocol)) {
// 获取包的物理路径
String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
// 以文件的方式扫描整个包下的文件 并添加到集合中
findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);
} else if ("jar".equals(protocol)) {
// 获取jar
JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile();
// 从此jar包 得到一个枚举类
Enumeration<JarEntry> entries = jar.entries();
// 同样的进行循环迭代
while (entries.hasMoreElements()) {
findAndAddClassesInPackageByJar(packageName, packageDirName, entries, recursive, classes);
}
}
} catch (Exception e) {
throw new Exception(e);
}
}
return classes;
}
/**
* jar包的形式来获取包路径下的所有Class
*
* @param packageName
* 包名(用点划分'com.apache')
* @param packageDirName
* 包名(用斜线划分'com/apache')
* @param entries
* 返回jar包中所有的文件条目
* @param recursive
* 是否循环迭代
* @param classes
* 存放class集合
* @throws Exception
*/
private static void findAndAddClassesInPackageByJar(String packageName, String packageDirName, Enumeration<JarEntry> entries, final boolean recursive, Set<String> classes) throws Exception {
// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
JarEntry entry = entries.nextElement();
String name = entry.getName();
// 如果是以/开头的
if (name.charAt(0) == '/') {
// 获取后面的字符串
name = name.substring(1);
}
// 如果前半部分和定义的包名相同
if (name.startsWith(packageDirName)) {
int idx = name.lastIndexOf('/');
// 如果以"/"结尾 是一个包
if (idx != -1) {
// 获取包名 把"/"替换成"."
packageName = name.substring(0, idx).replace('/', '.');
}
// 如果可以迭代下去 并且是一个包
if ((idx != -1) || recursive) {
// 如果是一个.class文件 而且不是目录
if (name.endsWith(".class") && !entry.isDirectory()) {
// 去掉后面的".class" 获取真正的类名
String className = name.substring(packageName.length() + 1, name.length() - 6);
// 添加到classes
classes.add(packageName + '.' + className);
}
}
}
}
/**
* 文件的形式来获取包路径下的所有Class
*
* @param packageName
* 包名(用点划分'com.apache')
* @param packagePath
* 包路径(系统全路径)
* @param recursive
* 是否循环迭代
* @param classes
* 存放class集合
*/
private static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, Set<String> classes) throws Exception {
// 获取此包的目录 建立一个File
File dir = new File(packagePath);
// 如果不存在或者 也不是目录就直接返回
if (!dir.exists() || !dir.isDirectory()) {
return;
}
// 如果存在 就获取包下的所有文件 包括目录
File[] dirfiles = dir.listFiles(new FileFilter() {
// 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
public boolean accept(File file) {
return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
}
});
List<String> clazzs = new ArrayList<String>();
// 循环所有文件
for (File file : dirfiles) {
// 如果是目录 则继续扫描
if (file.isDirectory()) {
findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive, classes);
} else {
// 如果是java类文件 去掉后面的.class 只留下类名
String className = file.getName().substring(0, file.getName().length() - 6);
// 添加到集合中去
clazzs.add(packageName + '.' + className);
classes.add(packageName + '.' + className);
}
}
}
}
带有一定特殊要求的工具类
/**
* 处理Class类相关的工具类
*
* @author Simba
* @time 2017年9月12日下午7:23:37
*/
public class ClassAnalysis {
/**
* 传入指定包路径,获取包下所有使用<i>@ModuleContextAnn</i>类注解的全类名。<br>
*
* <p>
* <tt>注意:eclipse直接导出的jar包,可能无法读取,因为jar包 <i>/META-INF/MANIFEST.MF</i>文件中,缺少补充信息。</tt>
* </p>
*
* <p>
* <tt>推荐使用Ant打包,或者使用Eclipse中的build.xml进行打包</tt>
* </p>
*
* @param <A>
*
* @param <A>
*
* @param packageName
* 包名(例如:org、org.apache)
*
* @return 返回使用指定注解的全类名
*
* @throws Exception
*/
public static Set<Class<?>> getAnnotationClassByPackage(String packageName) throws Exception {
Set<String> allTempClass = getClassNamesByPackage(packageName);
Set<Class<?>> resultClazzs = new LinkedHashSet<Class<?>>();
for (String allClass : allTempClass) {
Class<?> clazz = Class.forName(allClass);
// 判断类中是否使用ModuleContextAnn注解
if (clazz.isAnnotationPresent(ModuleContextAnn.class)) {
resultClazzs.add(clazz);
}
}
return resultClazzs;
}
/**
* 传入指定包路径,获取包下所有全类名。<br>
*
* <p>
* <tt>注意:eclipse直接导出的jar包,可能无法读取,因为jar包 <i>/META-INF/MANIFEST.MF</i>文件中,缺少补充信息。</tt>
* </p>
*
* <p>
* <tt>推荐使用Ant打包,或者使用Eclipse中的build.xml进行打包</tt>
* </p>
*
* @param packageName
* 包名(例如:org、org.apache)
*
* @return 指定包名下,所有全类名
*
* @throws Exception
*/
public static Set<String> getClassNamesByPackage(String packageName) throws Exception {
// 第一个class类的集合,使用linkedHashSet为了保证顺序。
Set<String> classes = new LinkedHashSet<String>();
// 是否循环迭代
boolean recursive = true;
// 获取包的名字 并进行替换
String packageDirName = packageName.replace('.', '/');
// 定义一个枚举的集合 并进行循环来处理这个目录下的things
Enumeration<URL> dirs;
try {
dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
} catch (IOException e) {
throw new Exception(e);
}
// 循环迭代下去
while (dirs.hasMoreElements()) { // 获取下一个元素
try {
URL url = dirs.nextElement();
System.err.println(url);
// 得到协议的名称
String protocol = url.getProtocol();
// 如果是以文件的形式
if ("file".equals(protocol)) {
// 获取包的物理路径
String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
// 以文件的方式扫描整个包下的文件 并添加到集合中
findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);
} else if ("jar".equals(protocol)) {
// 获取jar
JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile();
// 从此jar包 得到一个枚举类
Enumeration<JarEntry> entries = jar.entries();
// 同样的进行循环迭代
while (entries.hasMoreElements()) {
findAndAddClassesInPackageByJar(packageName, packageDirName, entries, recursive, classes);
}
}
} catch (Exception e) {
throw new Exception(e);
}
}
return classes;
}
/**
* jar包的形式来获取包路径下的所有Class
*
* @param packageName
* 包名(用点划分'com.apache')
* @param packageDirName
* 包名(用斜线划分'com/apache')
* @param entries
* 返回jar包中所有的文件条目
* @param recursive
* 是否循环迭代
* @param classes
* 存放class集合
* @throws Exception
*/
private static void findAndAddClassesInPackageByJar(String packageName, String packageDirName, Enumeration<JarEntry> entries, final boolean recursive, Set<String> classes) throws Exception {
// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
JarEntry entry = entries.nextElement();
String name = entry.getName();
// 如果是以/开头的
if (name.charAt(0) == '/') {
// 获取后面的字符串
name = name.substring(1);
}
// 如果前半部分和定义的包名相同
if (name.startsWith(packageDirName)) {
int idx = name.lastIndexOf('/');
// 如果以"/"结尾 是一个包
if (idx != -1) {
// 获取包名 把"/"替换成"."
packageName = name.substring(0, idx).replace('/', '.');
}
// 如果可以迭代下去 并且是一个包
if ((idx != -1) || recursive) {
// 如果是一个.class文件 而且不是目录
if (name.endsWith(".class") && !entry.isDirectory()) {
// 去掉后面的".class" 获取真正的类名
String className = name.substring(packageName.length() + 1, name.length() - 6);
// 添加到classes
classes.add(packageName + '.' + className);
}
}
}
}
/**
* 文件的形式来获取包路径下的所有Class
*
* @param packageName
* 包名(用点划分'com.apache')
* @param packagePath
* 包路径(系统全路径)
* @param recursive
* 是否循环迭代
* @param classes
* 存放class集合
*/
private static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, Set<String> classes) throws Exception {
// 获取此包的目录 建立一个File
File dir = new File(packagePath);
// 如果不存在或者 也不是目录就直接返回
if (!dir.exists() || !dir.isDirectory()) {
return;
}
// 如果存在 就获取包下的所有文件 包括目录
File[] dirfiles = dir.listFiles(new FileFilter() {
// 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
public boolean accept(File file) {
return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
}
});
List<String> clazzs = new ArrayList<String>();
// 循环所有文件
for (File file : dirfiles) {
// 如果是目录 则继续扫描
if (file.isDirectory()) {
findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive, classes);
} else {
// 如果是java类文件 去掉后面的.class 只留下类名
String className = file.getName().substring(0, file.getName().length() - 6);
// 添加到集合中去
clazzs.add(packageName + '.' + className);
classes.add(packageName + '.' + className);
}
}
}
}