Spring中有很多注解,在这里我们将自己设计一个注解进行使用。那么怎么设计注解呢?Spring的注解设计是基于 元注解实现的。元注解是Java基础,元注解如下:
@Target
用于指定注解的使用范围
@Retention
用于指定注解的保留策略
@Documented
@Inherited
用于指明父类注解会被子类继承得到
@Repeatable
用于声明标记的注解为可重复类型注解,可以在同一个地方多次使用
这些元注解就是最基本的部件,我们设计注解需要用到它们。现在我们就设计一个自己的ComponentScan注解:
/**
* @author linghu
* @date 2023/8/30 13:56
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
表明这个注解在运行时会生效;@Target(ElementType.TYPE)
表明注解可以修饰的类型,我们进入这个Type
的源码,我们通过注释得知,TYPE
包含了 Class, interface (including annotation type), or enum declaration,也就是可以是类,接口…:
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
这个时候我们为了验证我们设计的新注解ComponentScan,我们新建一个LingHuSpringConfig配置类,其实这个配置类不会具体实现什么,就是在类名上放一个注解ComponentScan,然后设置一个value值,如下:
/**
* @author linghu
* @date 2023/8/30 14:09
* 这个配置文件作用类似于beans.xml文件,用于对spring容器指定配置信息
*/
@ComponentScan(value = "com.linghu.spring.component")
public class LingHuSpringConfig {
}
我们设置这个配置类的目的是:我们在初始化容器的时候,直接传递 LingHuSpringConfig.class
接口就行了,通过接口类型初始化ioc容器,容器根据我们设计的注解去扫描这个全类路径com.linghu.spring.component。
其实这个容器的设计和《手动开发-简单的Spring基于XML配置的程序–源码解析》讲的差不多,都需要:
ConcurrentHashMap
作为容器getBean
方法,返回我们 的ioc容器。这里面大部分工作是在构造器里完成的,完成的工作如下:
@ComponentScan
配置类,并读取value值,得到类路径。target
目录的路径下去索引所有文件,其实就是那些.class文件,我们对这些文件进行过滤,过滤的过程中判断它们有没有加注解,如果加了就把这些文件的类路径放到ioc容器中保存下来。最后我们实现了,我们通过自己定义的注解,将被注解的类的类路径扫描并加入到了我们自己创建的容器ioc中,最后我们通过我们自己设计的ioc容器得到了我们需要的对象。ioc怎么帮我们创建的对象?通过反射创建的,反射所需要的类路径是我们在注解上读取过来的。
LingSpringApplicationContext.java:
package com.linghu.spring.annotation;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author linghu
* @date 2023/8/30 14:13
* 这个类充当spring原生的容器ApplicationContext
*/
public class LingSpringApplicationContext {
private Class configClass;
//ioc里存放的是通过反射创建的对象(基于注解形式)
private final ConcurrentHashMap<String,Object> ioc=
new ConcurrentHashMap<>();
public LingSpringApplicationContext(Class configClass) {
this.configClass = configClass;
// System.out.println("this.configClass="+this.configClass);
//获取到配置类的@ComponentScan(value = "com.linghu.spring.component")
ComponentScan componentScan =
(ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);
//取出注解的value值:com.linghu.spring.component。得到类路径,要扫描的包
String path = componentScan.value();
// System.out.println("value="+value);
//得到要扫描包下的资源(.class文件)
//1、得到类的加载器
ClassLoader classLoader =
LingSpringApplicationContext.class.getClassLoader();
path = path.replace(".", "/");
URL resource = classLoader.getResource(path);
// System.out.println("resource="+resource);
//将要加载的资源(.class)路径下的文件进行遍历=》io
File file = new File(resource.getFile());
if (file.isDirectory()){
File[] files = file.listFiles();
for (File f :files) {
//获取"com.linghu.spring.component"下的所有class文件
System.out.println("===========");
//D:\Java\JavaProjects\spring\target\classes\com\linghu\spring\component\UserDAO.class
System.out.println(f.getAbsolutePath());
String fileAbsolutePath = f.getAbsolutePath();
//只处理.class文件
if (fileAbsolutePath.endsWith(".class")){
//1、获取到类名=》字符串截取
String className =
fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
// System.out.println("className="+className);
//2、获取类的完整的路径
String classFullName = path.replace("/", ".") + "." + className;
System.out.println("classFullName="+classFullName);
//3、判断该类是不是需要注入容器,就看该类是不是有注解@compoment,@controller,@Service...
try {
//得到指定类的类对象,相当于Class.forName("com.xxx")
Class<?> aClass = classLoader.loadClass(classFullName);
if (aClass.isAnnotationPresent(Component.class)||
aClass.isAnnotationPresent(Service.class)||
aClass.isAnnotationPresent(Repository.class)||
aClass.isAnnotationPresent(Controller.class)){
//演示一个component注解指定value,分配id
if (aClass.isAnnotationPresent(Component.class)){
Component component = aClass.getDeclaredAnnotation(Component.class);
String id = component.value();
if (!"".endsWith(id)){
className=id;//用户自定义的bean id 替换掉类名
}
}
//这时就可以反射对象,放入到ioc容器中了
Class<?> clazz = Class.forName(classFullName);
Object instance = clazz.newInstance();//反射完成
//放入到容器中,将类的首字母变成小写,这里用了Stringutils
ioc.put(StringUtils.uncapitalize(className),instance);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
}
//返回容器中的对象
public Object getBean(String name){
return ioc.get(name);
}
}
Gitee:《实现Spring容器机制》