自定义注解实战,注解取代配置文件

Hello,I'm Shendi.

这几天熟悉了下注解解析器,并且实战了一下.

网上资源真少,搜不到想要的,大多数文章都是提供获取注解的方法,并没有扫描包什么的...

看完觉得对你有用的话点个关注再走吧~


要先会下面这两个,做起来才能印象深刻

不知道注解机制的可以先看下我这篇文章: https://blog.csdn.net/qq_41806966/article/details/105668779

不知道使用策略+配置取代 if else 多分支结构的请先看这篇文章: https://blog.csdn.net/qq_41806966/article/details/104693350


在之前学过的注解机制里包括定义注解,注解处理器

注解处理器又分为编译期处理,运行时处理,注解最重要的就是注解处理器了

下面会制作一个运行时注解处理器,以及取代多分支.


学过 Servlet 的就知道 Servlet 有一注解:@WebServlet,使用了此注解就可以直接让用户访问到此 Servlet

例如以下代码

@WebServlet("/hello")
public class TestServlet extends HttpServlet {
    public void doGet(HttpRequestServlet req,HttpResponseServlet resp) {
        System.out.println("hello,world");
    }
}

在启动 Tomcat 后,通过访问此项目的 /hello 映射,就会调用此类的 doGet 方法,然后在控制台输出 hello,world

接下来我们实现一个与之类似的,用注解机制(其实就是多态取代多分支,但是这里用注解替代了配置文件).


首先,我们新建一个接口

接口名就叫 Execute 吧,里面有一个exec方法

至于为什么实现的类必须要有一个公有无参构造是因为我们后面要使用反射调用.


然后我们把自定义注解写出来

我们的功能只有一个参数,接收一个调用名就ok了(等于Servlet的映射路径)

我们是通过反射调用,所以注解需要保留在运行时

所以自定义注解如下

上面的 value 方法可以直接不指定来进行赋值,例如 @PropertiesAnnotation("这样可以赋值")


接下来实现接口

我们多弄几个实现,我们在测试类里使用 Scanner 进行接收,所以需要一个退出程序的类

新建三个类实现此接口,分别为 ExecuteA,ExecuteB,ExecuteExit,将注解写好(对应映射)


最重要的一步,也是最后一步

实现一个工厂,用于创建对应的 Execute

这里遇到很多坑,比如静态代码块执行居然比静态方法慢...

代码比较多

先讲一下实现思想

此类有扫描功能,用于获取有注解的类,因为是工厂,加上扫描开销大(线程+递归),所以我们用到了享元模式,用一个集合将我们创建的对象存起来,在第一次使用的时候会进行一次扫描,后续的话如果开启了运行时扩展则可以在扩展的时候在进行扫描.

主要是扫描包的路径,这里可能我们需要扫描的东西在jar包里...这个时候就需要特别处理,这里不做对应操作,如果需要,可以看一下这篇文章 https://blog.csdn.net/a729913162/article/details/81698109

我用的 Eclipse,所以我的根目录需要在 bin 目录,使用 getResource 获取对应路径

以及我们在扫描包的时候需要递归和线程(这样是为了速度),但是我们需要等执行完才执行后面的操作,所以需要一个计数器num,

具体思路是每次将当前文件夹文件数加到 num 中,然后执行完减去对应数量,这样等到num <= 1的时候基本上就执行完了

为了节省性能,我们需要将我们已经扫描过的类名存起来,下次遇到直接跳过(只能扩展)

定义变量的代码如下

上面的 CLASS_PATH获取到的第一个字符为 \ 所以需要去掉

工厂的获取产品方法如下

scanClass是扫描包下类的方法,需要一个计数器.

scanClass代码如下

/**
 * 扫描所有类,获取有无注解进行处理.
 * @author Shendi <a href='tencent://AddContact/?fromId=45&fromSubId=1&subcmd=all&uin=1711680493'>QQ</a>
 * @param path 需要遍历的路径
 */
private static void scanClass(String path) {
	File[] files = new File(path).listFiles();
	
	num += files.length;
	for (File file : files) {
		
		String filePath = file.getPath();
		
		if (file.isDirectory()) {
			new Thread(() -> scanClass(filePath)).start();
		} else if (file.getName().endsWith(".class")) {
			String className = filePath.substring(CLASS_PATH.length(), filePath.length() - 6).replace(File.separatorChar, '.');
			
			// 如果此类已经在集合里则不作操作,否则加入集合(此类已经执行操作)
			if (EXECUTE_NAMES.contains(className)) continue;
			else EXECUTE_NAMES.add(className);
			
			try {
				Class<?> execute = Class.forName(className);
				PropertiesAnnotation anno = execute.getAnnotation(PropertiesAnnotation.class);
				
				// 有对应注解,通过一些判断最终放入集合.
				if (anno != null) {
					try {
						Constructor<?> c = execute.getConstructor();
						try {
							Execute exeObj = (Execute) c.newInstance();
							// 一切就绪,存入集合.
							EXECUTES.put(anno.value(), exeObj);
						} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
							e.printStackTrace();
							System.out.println("在创建执行类的实例失败了,请检查类是否实现 Execute 接口,创建的类为: " + className);
						}
					} catch (NoSuchMethodException e) {
						e.printStackTrace();
						System.out.println("你的执行类没有公有无参构造函数,请提供一个无参的构造函数!");
					} catch (SecurityException e) {
						e.printStackTrace();
						System.out.println("安全管理器不允许获取此类公有构造!");
					}
				}
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
		}
	}
	num -= files.length;
	if (num <= 1) synchronized (EXECUTES) { EXECUTES.notify(); }
}

代码量有点多,仔细看一下就能看懂了,具体内容就是扫描带有指定注解的类,然后存集合里

使用Class的方法 getAnnotation(Class) 可以获取这个类的指定注解


接下来我们编写测试类

运行测试,结果如下


点个关注吧~

查看我专栏,有惊喜

猜你喜欢

转载自blog.csdn.net/qq_41806966/article/details/107290204