下面分步实现
一、创建四个常用的注解@Controller、@Service、@Component、@Repository
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Repository {
}
二、提取被注解标记的类
(1). 指定范围,获取范围内的所有类
获取类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
通过类加载器获取到加载的资源信息
//通过包名获取资源的地址
URL url = classLoader.getResource(packageName.replace(".", "/"));
//这里查看URL的getResource源码可以发现,URL是通过‘/‘来进行分割的,所以要将包名中的‘.‘替换成‘/‘
/**
* Finds the resource with the given name. A resource is some data
* (images, audio, text, etc) that can be accessed by class code in a way
* that is independent of the location of the code.
*
* <p> The name of a resource is a ‘<tt>/</tt>‘-separated path name that
* identifies the resource.
*
* <p> This method will first search the parent class loader for the
* resource; if the parent is <tt>null</tt> the path of the class loader
* built-in to the virtual machine is searched. That failing, this method
* will invoke {@link #findResource(String)} to find the resource. </p>
*
* @apiNote When overriding this method it is recommended that an
* implementation ensures that any delegation is consistent with the {@link
* #getResources(java.lang.String) getResources(String)} method.
*
* @param name
* The resource name
*
* @return A <tt>URL</tt> object for reading the resource, or
* <tt>null</tt> if the resource could not be found or the invoker
* doesn‘t have adequate privileges to get the resource.
*
* @since 1.1
*/
public URL getResource(String name) {
URL url;
if (parent != null) {
url = parent.getResource(name);
} else {
url = getBootstrapResource(name);
}
if (url == null) {
url = findResource(name);
}
return url;
}
依据不同的资源类型,采用不同的方式获取资源的集合
//首先过滤出文件类型的资源
//然后通过extractClassFile函数递归提取所需的所有文件
//这里只file类型的资源
if(url.getProtocol().equalsIgnoreCase(FILE_PROTOCOL)){
classSet = new HashSet<>();
File packageDirectory = new File(url.getPath());
if(!packageDirectory.isDirectory()){
return null;
}
extractClassFile(classSet, packageDirectory, packageName);
}
/**
* 递归获取目标package下面的所有class文件
* @param emptyClassSet 装载目标类的集合
* @param fileSource 文件或者目录
* @param packageName 包名
*/
private static void extractClassFile(Set<Class<?>> emptyClassSet, File fileSource, String packageName) {
//如果不是文件夹,直接返回
if(!fileSource.isDirectory()){
return;
}
//如果是文件夹,则列出所有文件和子文件夹,获取到的是文件夹类型的File数组
File[] files = fileSource.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
//如果是文件夹,则被过滤提取出来
if(file.isDirectory()){
return true;
}else {
//获取文件的绝对值路径
String absoluteFilePath = file.getAbsolutePath();
if(absoluteFilePath.endsWith(".class")){
//class文件直接加载
addToClassSet(absoluteFilePath);
}
return false;
}
}
private void addToClassSet(String absoluteFilePath) {
//1.从class文件的绝对值路径中提取包含了package的类名
//将现在的‘/‘或‘\‘替换为包名中的‘.‘
absoluteFilePath = absoluteFilePath.replace(File.separator, ".");
String className = absoluteFilePath.substring(absoluteFilePath.indexOf(packageName));
className = className.substring(0, className.lastIndexOf("."));
//2.通过反射获取Class对象,并加入classSet里
Class<?> targetClass = loadClass(className);
emptyClassSet.add(targetClass);
}
});
if(files != null){
//递归调用
for (File file : files) {
extractClassFile(emptyClassSet, file, packageName);
}
}
}
//loadClass通过反射获取Class对象
/**
* 获取class对象
* @param className 类名
* @return class对象
*/
public static Class<?> loadClass(String className){
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
log.error("load class error ", e);
throw new RuntimeException(e);
}
}
三、实现统一容器管理
容器的组成部分:
第一步:需要用同一个容器实例将所有目标管理起来,所以容器需要写成单例模式,这里采用装备内部枚举类的饿汉模式来抵御序列化和反射的攻击:
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings("unused")
public class BeanContainer {
/**
* 获取bean容器实例
* @return 单例BeanContainer
*/
public static BeanContainer getInstance(){
return ContainHolder.HOLDER.instance;
}
private enum ContainHolder{
HOLDER;
private final BeanContainer instance;
ContainHolder(){
instance = new BeanContainer();
}
}
}
//使用@NoArgsConstructor(access = AccessLevel.PRIVATE)添加私有的构造函数
首先在容器中定义两个变量,其中beanMap用来存放被标记的目标对象的Map,BEAN_ANNOTATION是被加载的注解的列表,例子中只写了四个
private final Map<Class<?>, Object> beanMap = new ConcurrentHashMap<>();
private static final List<Class<? extends Annotation>> BEAN_ANNOTATION =
Arrays.asList(Component.class, Controller.class, Service.class, Repository.class);
第二步:开始扫描加载bean,其中synchronized防止多线程访问冲突,:
/**
* 扫描加载所有Bean
* @param packageName 包名
*/
public synchronized void loadBeans(String packageName){
//判断bean容器是否被加载过
if(loaded){
log.warn("BeanContainer has been loaded.");
return;
}
//通过上面讲解的extractPackageClass方法获取所有类对象
Set<Class<?>> classSet = ClassUtil.extractPackageClass(packageName);
//进行判空
if(ValidationUtil.isEmpty(classSet)){
log.warn("extract nothing from packageName" + packageName);
return;
}
for (Class<?> clazz : classSet) {
for (Class<? extends Annotation> annotation : BEAN_ANNOTATION) {
//如果类上面标记了定义的注解
if(clazz.isAnnotationPresent(annotation)){
//将目标类本身作为键,实例作为值,放入到beanMap中
beanMap.put(clazz, ClassUtil.newInstance(clazz, true));
break;
}
}
}
loaded = true;
}
//beanMap.put(clazz, ClassUtil.newInstance(clazz, true));
//这里有一个newInstance方法利用反射创建实例
/**
* 实例化Class
* @param clazz Class
* @param accessible 是否支持创建出私有clazz对象的实例
* @param <T> clazz的类型
* @return 类的实例
*/
public static <T> T newInstance(Class<T> clazz, boolean accessible){
try {
Constructor<T> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(accessible);
return constructor.newInstance();
} catch (InstantiationException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
log.error("newInstance error", e);
throw new RuntimeException(e);
}
}
第三步:实现容器的操作方式
增加、删除操作
/**
* 添加一个class对象及其bean实例
* @param clazz Class对象
* @param bean bean实例
* @return 原有的bean实例,没有则返回null
*/
public Object addBean(Class<?> clazz, Object bean){
return beanMap.put(clazz, bean);
}
/**
* 移除一个IOC容器管理的对象
* @param clazz Class对象
* @return 删除的bean实例,没有则返回null
*/
public Object removeBean(Class<?> clazz){
return beanMap.remove(clazz);
}
根据Class获取对应实例
/**
* 根据Class对象获取Bean实例
* @param clazz Class对象
* @return 相应Bean实例
*/
public Object getBean(Class<?> clazz){
return beanMap.get(clazz);
}
获取所有的Class及其实例
/**
* 获取容器管理的所有对象的集合
* @return Class集合
*/
public Set<Class<?>> getClasses(){
return beanMap.keySet();
}
/**
* 获取所有Bean集合
* @return Bean集合
*/
public Set<Object> getBeans(){
return new HashSet<>(beanMap.values());
}
通过注解获取被标注的Class
/**
* 根据注解筛选出Bean的Class集合
* @param annotation 注解
* @return Class集合
*/
public Set<Class<?>> getClassesByAnnotation(Class<? extends Annotation> annotation){
//1.获取beanMap的所有对象
Set<Class<?>> keySet = getClasses();
if(ValidationUtil.isEmpty(keySet)){
log.warn("nothing in beanMap");
return null;
}
//2.通过注解筛选出被注解标记的对象,并添加到classSet里
Set<Class<?>> classSet = new HashSet<>();
for (Class<?> clazz : keySet) {
//类是否被相关注解标记
if(clazz.isAnnotationPresent(annotation)){
classSet.add(clazz);
}
}
return classSet.size() > 0?classSet:null;
}
通过超类获取对应子类的Class
/**
* 通过接口或者父类获取实现类或子类的Class集合,不包括其本身
* @param interfaceOrClass 接口或父类
* @return Class集合
*/
public Set<Class<?>> getClassesBySuper(Class<?> interfaceOrClass){
//1.获取beanMap的所有对象
Set<Class<?>> keySet = getClasses();
if(ValidationUtil.isEmpty(keySet)){
log.warn("nothing in beanMap");
return null;
}
//2.判断keySet里面的元素是否是传入的接口或者类的子类,并添加到classSet里
Set<Class<?>> classSet = new HashSet<>();
for (Class<?> clazz : keySet) {
if(interfaceOrClass.isAssignableFrom(clazz) && !clazz.equals(interfaceOrClass)){
classSet.add(clazz);
}
}
return classSet.size() > 0?classSet:null;
}
通过容器载体保存Class数量
/**
* Bean实例数量
* @return 数量
*/
public int size(){
return beanMap.size();
}
这样一来,我们的容器已经实现完毕,下面进行测试:
//定义一个测试接口
public interface DemoService {
String getName();
}
//接口的实现类
@Service
public class DemoServiceImpl implements DemoService {
@Override
public String getName() {
return "DemoServiceImpl";
}
}
//以及调用接口的Controller
@Controller
public class ControllerDemo {
private DemoService demoService;
String getServiceName(){
return demoService.getName();
}
}
//编写测试类进行调试
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BeanContainerTest {
private static BeanContainer beanContainer;
@BeforeAll
static void init(){
beanContainer = BeanContainer.getInstance();
}
@DisplayName("根据类获取其实例:getBeanTest")
@Order(1)
@Test
public void getBeanTest(){
beanContainer.loadBeans("com.joker");
ControllerDemo controller = (ControllerDemo) beanContainer.getBean(ControllerDemo.class);
Assertions.assertNotNull(controller);
}
}
如下图发现ControllerDemo已经成功从容器中获取到了,但是其内部的DemoService却是null,那是因为我们只实现了容器的管理,还没实现依赖的注入
四、实现依赖注入
这里以@Autowired注解示例
/**
* Autowired目前仅支持成员变量的注入
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
String value() default "";
}
实现创建被注解标记的成员变量实例,并将其注入到成员变量里
public void doIoc(){
if(ValidationUtil.isEmpty(beanContainer.getClasses())){
log.warn("empty classSet in BeanContainer.");
return;
}
//1.遍历Bean容器中所有的Class对象
for (Class<?> clazz : beanContainer.getClasses()) {
//2.遍历Class对象的所有成员变量
Field[] fields = clazz.getDeclaredFields();
if(ValidationUtil.isEmpty(fields)){
continue;
}
for (Field field : fields) {
//3.找出被Autowired标记的成员变量
if(field.isAnnotationPresent(Autowired.class)){
Autowired autowired = field.getAnnotation(Autowired.class);
String autowiredValue = autowired.value();
//4.获取这些成员变量的类型
Class<?> fieldClass = field.getType();
//5.获取这些成员变量的类型在容器里对应的实例
Object fieldValue = getFieldInstance(fieldClass, autowiredValue);
if(fieldValue == null){
throw new RuntimeException("unable to inject relevant type, target field is:" + fieldClass.getName());
}
//6.通过反射将对应的成员变量实例注入到成员变量所在类的实例里
Object targetObject = beanContainer.getBean(clazz);
ClassUtil.setField(field, targetObject, fieldValue, true);
}
}
}
}
//5.获取这些成员变量的类型在容器里对应的实例
/**
* 根据Class对象在beanContainer里获取其实例或者实现类
* @param fieldClass Class对象
* @param autowiredValue 注解中的值
* @return 实例或实现类
*/
private Object getFieldInstance(Class<?> fieldClass, String autowiredValue) {
Object fieldValue = beanContainer.getBean(fieldClass);
if(fieldValue != null){
return fieldValue;
}
Class<?> implementedClass = getImplementClass(fieldClass, autowiredValue);
if (implementedClass != null){
return beanContainer.getBean(implementedClass);
}
return null;
}
/**
* 获取接口的实现类
* @param fieldClass 接口
* @param autowiredValue 注解中的值
* @return 实现类
*/
private Class<?> getImplementClass(Class<?> fieldClass, String autowiredValue) {
Set<Class<?>> classSet = beanContainer.getClassesBySuper(fieldClass);
if(ValidationUtil.isEmpty(classSet)){
return null;
}
if(ValidationUtil.isEmpty(autowiredValue)){
if(classSet.size() == 1){
return classSet.iterator().next();
}
//如果多于两个实现类且未被指定,抛出异常
throw new RuntimeException("multiple implement classes for " + fieldClass.getName() + "please set @Autowired‘s value to pick one");
}
for (Class<?> clazz : classSet) {
if(autowiredValue.equals(clazz.getSimpleName())){
return clazz;
}
}
//没有被容器管理
return null;
}
//6.通过反射将对应的成员变量实例注入到成员变量所在类的实例里
ClassUtil.setField(field, targetObject, fieldValue, true);
//写在了ClassUtil类中
/**
* 设置类的属性值
* @param field 成员变量
* @param target 类实例
* @param value 成员变量的值
* @param accessible 是否允许设置私有属性
*/
public static void setField(Field field, Object target, Object value, boolean accessible){
field.setAccessible(accessible);
try {
field.set(target, value);
} catch (IllegalAccessException e) {
log.error("set field error", e);
throw new RuntimeException(e);
}
}
依赖注入的使用
上述依赖注入程序编写完毕,只需在ControllerDemo的Service变量上面加一个注解,即使用@Autowired:
@Controller
public class ControllerDemo {
@Autowired
private DemoService demoService;
public String getServiceName(){
return demoService.getName();
}
}
然后编写测试类:
public class DependencyInjectorTest {
@DisplayName("依赖注入:doIoc")
@Test
public void doIocTest(){
BeanContainer beanContainer = BeanContainer.getInstance();
beanContainer.loadBeans("com.joker");
Assertions.assertTrue(beanContainer.isLoaded());
ControllerDemo controllerDemo = (ControllerDemo) beanContainer.getBean(ControllerDemo.class);
Assertions.assertNotNull(controllerDemo);
new DependencyInjector().doIoc();
Assertions.assertNotNull(controllerDemo.getServiceName());
}
}
调试运行结果如图:
结果无误,此篇结。
原文:https://www.cnblogs.com/zqm-sau/p/13410459.html