模拟Spring扫描包
转载的是老师的博客,只是写了一些注释,便于理解。原博客地址: https://blog.csdn.net/kongfanyu/article/details/103930414
通过自定义注解扫描包及子包下的所有类,并通过反射创建对象.
定义注解
package com.hanker.domain12;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Retention 表示定义的注解何时有效, RUNTIME运行时有效
* @Target 表示定义的注解运行在哪里, TYPE应用在类上, METHOD应用在方法上, FIELD应用在类上
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Component {
}
//========================
/**
* {ElementType.TYPE}的花括号可以省略,如:ElementType.TYPE,因为只有一个值
* {ElementType.TYPE,ElementType.METHOD,ElementType.FIELD}
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@interface Controller {
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@interface Service {
}
业务类
package com.hanker.domain12;
public interface UserService {
void saveUser();
}
@Service
class UserServiceImpl implements UserService {
@Override
public void saveUser() {
System.out.println("添加用户");
}
}
// 通过Controller注解来创建UserController 实例
@Controller
class UserController {
public void login() {
System.out.println("用户登录控制器");
}
}
包扫描
package com.hanker.domain12;
import java.io.File;
import java.io.FileFilter;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class AnnotationApplicationContext {
private Map<String, Object> factory = new HashMap<>();
public AnnotationApplicationContext() {
//包名
String pkg = "com.hanker.domain12";
scanPackage(pkg);
}
//包扫描方法
private void scanPackage(final String pkg) {
/*
* replaceAll() 是用来替换字符串的,第一个参数都是 匹配字符串的正则表达式 第二个参数是用来替换每个匹配项的字符串
* 成功则返回替换的字符床,识别则返回原始字符串
* \\.实际上被转义为两次,\\在java中被转换为一个'\'字符,然后'\.'被传给正则,\.表示对点字符进行转义,使.就表示字符'.',而不使用它在正则中的特殊意义
* */
String pkgDir = pkg.replaceAll("\\.", "/");
URL url = getClass().getClassLoader().getResource(pkgDir);
// url 的值 是"file:/D:/work/Idea-work/jdbc_01/out/production/jdbc_01/com/hanker/domain12"
System.out.println("--url.getFile()-" + url.getFile());
//url.getFile 的值 是"/D:/work/Idea-work/jdbc_01/out/production/jdbc_01/com/hanker/domain12"
File file = new File(url.getFile());
// file 的值 D:\work\Idea-work\jdbc_01\out\production\jdbc_01\com\hanker\domain12
/*java.io.File.listFiles(FileFilter filter) 返回抽象路径名数组,表示在目录中此抽象路径名表示,满足指定过滤器的文件和目录。
*
* */
//listFiles()方法是返回某个目录下所有文件和目录的绝对路径,返回的是File数组
//过滤
File fs[] = file.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
String fname = file.getName();
//isDirectory()是检查一个对象是否是文件夹。返回值是boolean类型的。如果是则返回true,否则返回false。
//是目录则继续调用自身
if (file.isDirectory()) {
scanPackage(pkg + "." + fname);
} else {
//判断文件名后缀是否.class
if (fname.endsWith(".class")) {
return true;
}
}
return false;
}
});
//循环所有class类
for (File f : fs) {
String fname = f.getName();
System.out.println("f.getName=========" + f.getName());
//去除.class以后的文件名
fname = fname.substring(0, fname.lastIndexOf("."));
//将名字的第一字母转换为小写(用它作为key存储map)
//将下标为0的字符转换为小写 在 + 从下标为1 开始 组成一个新的字符串
String key = String.valueOf(fname.charAt(0)).toLowerCase() + fname.substring(1);
//构建一个全类名(包名.类名)
String pkgCls = pkg + "." + fname;
try {
//通过全类名反射创建对象
Class<?> c = Class.forName(pkgCls);
//判定类上是否有注解isAnnotationPresent()
if (c.isAnnotationPresent(Controller.class) ||
c.isAnnotationPresent(Service.class) ||
c.isAnnotationPresent(Component.class)) {
Object obj = c.newInstance();
//将对象放到map容器
factory.put(key, obj);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
//通过键值(小写开头的类名)获取对象
public Object getBean(String key) {
return factory.get(key);
}
//初始化map
public void close() {
//clear是清空整个map
factory.clear();
factory = null;
}
}
测试类
package com.hanker.domain12;
import org.junit.Test;
public class TestApp {
@Test
public void Test() {
AnnotationApplicationContext ctx = new AnnotationApplicationContext();
UserController obj = (UserController) ctx.getBean("userController");
System.out.println(obj);
obj.login();
UserServiceImpl obj2 = (UserServiceImpl) ctx.getBean("userServiceImpl");
System.out.println(obj2);
obj2.saveUser();
ctx.close();
}
}
执行效果
可以看到已经创建了对象,并且调用了其方法