刚学java半年 自己写个简单的Spring
第一周--- 2020 08 12
刚刚上班基本没事做,太无聊了。也不能连外网,异想天开自己写个Spring?
这是第一天,按照自己的理解简单的实现了注入功能,离目标还有很大差距。
1.新建annotation包,新建如**解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
Public @interface AutoWired{}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
Public @interface Component{}
作用想必没人不知道。注意@Retention必须选择RUNTIME否则反射无法读取。
2.新建bean包,新建如下类
Public class BeanDefine{
Private String name;
Private Object bean;
省略了setget和get方法。这是对要注入到容器的bean的一层简单封装。其实就是取代了键值对
3.新建utils表,新建如下类
public class StringUtil { /** * 将一个字符串的首字母变小写 */ public static String firstToLowerCase(String str) { StringBuilder builder = new StringBuilder(); char[] arr = str.toCharArray(); if (arr[0] < 65 || arr[0] > 90) { throw new IllegalArgumentException(str); } arr[0] += 32; return builder.append(arr).toString(); } } |
还有个ReflectUtil,目前只有一个功能就是找出一个包下的所有类。参考自
https://blog.****.net/qq_30285985/article/details/102988013
public class ReflectUtil { /** * find all classes in a pack */ public static Set<Class<?>> getClasses(String pack, boolean recursive) throws Exception { Set<Class<?>> classes = new LinkedHashSet<>(); pack = pack.replace(".", "/"); Enumeration<URL> dirs = Thread.currentThread().getContextClassLoader().getResources(pack); while (dirs.hasMoreElements()) { URL url = dirs.nextElement(); String protocol = url.getProtocol(); if ("file".equals(protocol)) { String filePath = URLDecoder.decode(url.getFile(), "utf8"); findClassesByFile(pack, filePath, recursive, classes); } else { System.out.println("this is jar"); } } return classes; }
private static void findClassesByFile(String pack, String packPath, final boolean recursive, Set<Class<?>> classes) throws ClassNotFoundException { File dir = new File(packPath); if (!dir.exists() || !dir.isDirectory()) { return; } File[] dirs = dir.listFiles(pathname -> recursive && pathname.isDirectory() || pathname.getName().endsWith(".class")); if (dirs != null) { for (File file : dirs) { if (file.isDirectory()) { findClassesByFile(pack + "." + file.getName(), file.getAbsolutePath(), recursive, classes); } else { String className = file.getName().substring(0, file.getName().length() - 6); classes.add(Thread.currentThread().getContextClassLoader().loadClass(pack.replace("/", ".") + "." + className)); } } } } } |
4.新建context包,新建如下接口
public interface Context { Object getBean(Class<?> c);
Object getBean(String name); } |
包含了基本的取出bean的功能。
在包下新建impl包新建实现类,
private static final Logger logger = LoggerFactory.getLogger(ApplicationContext.class); private static final Map<Class<?>, BeanDefine> beanMap = new LinkedHashMap<>(); |
用map作为容器。
在构造方法中初始化容器。
public ApplicationContext() { try { Properties prop = new Properties(); prop.load(ApplicationContext.class.getClassLoader().getResourceAsStream("application.properties")); Set<Class<?>> classes = ReflectUtil.getClasses(prop.getProperty("component-scan"), true); for (Class<?> aClass : classes) { setBean(aClass); } for (Class<?> aClass : classes) { setField(aClass); } } catch (IllegalArgumentException e) { logger.error("***** {}", e.getMessage()); logger.error("***** help : maybe lost @Component on class needed autowired"); e.printStackTrace(); } catch (Exception e) { logger.error("***** something wrong in create context : {}", e.getMessage()); e.printStackTrace(); } } |
其中的部分方法如下
private void setBean(Class<?> c) throws IllegalAccessException, InstantiationException { if (c.isAnnotationPresent(Component.class)) { Object instance = c.newInstance(); beanMap.put(c, new BeanDefine(StringUtil.firstToLowerCase(c.getSimpleName()), instance)); } } |
显然这是向容器中注入带有@Component注解的类的实例。
private void setField(Class<?> c) throws IllegalAccessException { Field[] fields = c.getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(AutoWired.class)) { Class<?> type = field.getType(); Object bean = getBean(type); if (bean == null) { throw new IllegalArgumentException("can not autowired not find bean in context classed : " + type.getName()); } field.setAccessible(true); field.set(getBean(c), bean); } } } |
这是往已经注入容器的bean设置带有@Autowired注解的属性。
@Override public Object getBean(Class<?> c) { for (Map.Entry<Class<?>, BeanDefine> entry : beanMap.entrySet()) { if (entry.getKey().equals(c)) { return entry.getValue().getBean(); } } return null; }
@Override public Object getBean(String name) { for (Map.Entry<Class<?>, BeanDefine> entry : beanMap.entrySet()) { if (name.equals(entry.getValue().getName())) { return entry.getValue().getBean(); } } return null; } |
这就是实现接口的通过名称和类型取出bean的两个方法。
5.至此,今天的任务基本完成。spring容器已经有了基本的注入功能。建立test包,新建测试类。
@Component public class Test {
@AutoWired private Wired wired;
public void test() { System.out.println(wired); wired.wired(); } }
@Component public class Wired { public void wired() { System.out.println("wired success!!!"); } } |
在ApplicationContext类中新建主方法测试。
public static void main(String[] args) { ApplicationContext context = new ApplicationContext(); System.out.println(ApplicationContext.beanMap); Test t1 = (Test) context.getBean("test"); Test t2 = (Test) context.getBean(Test.class); System.out.println(t1 == t2); t1.test(); t2.test(); } |
运行测试
成功!!!
遇到异常情况,如Wired类未加@Component注解,也有提示。
明天上班再想想可以添加什么,,,,,,