java注解的使用——摒弃xml,以注解的方式来实现BeanFactory

历史的车轮永无止境的向前推进,碾压着万物生灵的每一天。

 前一篇文章讲述了从new对象到xml配置文件初始化对象,这一篇文章将会抛弃复杂的xml配置,直接使用注解来初始化我们简单的BeanFactory。

本篇涉及知识点 :

  • 反射基础

  • 注解基础

 接下来先看一下本次代码的目录结构,如下:

java注解的使用——摒弃xml,以注解的方式来实现BeanFactory_第1张图片

说明一下类的情况:BeanScanPath是两个自定义注解,infos包下面是用来测试的JavaBean,BeanFactory是一个接口,他的直接实现子类是AnnotationBeanFactory

Bean注解源码如下:

package bean.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** Bean注解 @author 大灰狼 */
@Documented
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
	/** 单例模式 */
	String SINGLETON = "singleton";
	/** 原型模式 */
	String PROTOTYPE = "prototype";
	/** bean的别名,可以是多个 */
	String[] name();
	/** bean创建完成后调用的方法,默认为无方法 */
	String initMethod() default "";
	String scope() default SINGLETON;
}

注解定义相关的请参考前面的涉及知识点链接文章

ScanPath注解源码如下:

package bean.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/** 定义自动扫描注解的路径,用于启动配置 */
@Documented
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface ScanPath {
	/** 基础路径,例如配置为{bean}则会扫描bean下面的所有类与子包的类 */
	String[] basePath() default "";
}

infos包下面java源码,

package bean.infos;

import bean.ExtUtil;
import bean.annotation.Bean;

/** 操作人 */
@Bean(name = { "optUser" }, scope = Bean.SINGLETON)
public class OptUser {
	private int age = ExtUtil.rnd.nextInt(100);
	private String name = ExtUtil.randStr(3);

	@Override
	public String toString() {
		return "【" + name + "," + age + "岁】";
	}
}

操作用户javaBean,里面的字段都是随机生成的,该类是用来测试字段注入的。单例模式

Car类源码:

package bean.infos;

import bean.ExtUtil;
import bean.annotation.Bean;

/** 小汽车,单例模式 */
@Bean(name = { "myCar", "yourCar" }, initMethod = "init")
public class Car {
	/** 制造 */
	private String madeBy = ExtUtil.randStr(3);

	/** 无参方法,用于测试initMethod */
	public void init() {
		System.out.println(this + "init");
	}

	public void showInfo() {
		System.out.println("beanInfo" + this + "====>制造作人:" + madeBy);
	}
}

默认单例模式,这里定义了两个别名。 

Train源码:

package bean.infos;

import javax.annotation.Resource;

import bean.ExtUtil;
import bean.annotation.Bean;

/** 火车,原型模式 */
@Bean(name = { "myTrain" }, scope = Bean.PROTOTYPE)
public class Train {
	/** 目的地 */
	private String dest = ExtUtil.randStr(5);

	/** 操作人 */
	@Resource(name = "optUser")
	private OptUser optUser;

	public void setOptUser(OptUser optUser) {
		this.optUser = optUser;
	}

	public void showInfo() {
		System.out.println("beanInfo" + this + "==>操作人:" + optUser + ",目的地:" + dest);
	}
}

原型模式,optUser加了@Resource注解。

接下来是我们的工具类

package bean;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.LinkedBlockingQueue;

/** 扩展工具类 */
public class ExtUtil {
	final static String baseStr = "国年着就那和要她出也得里后自以会家可下而过天去能对小多然于心学么之都好看起发";
	public final static Random rnd = new Random();

	/**根据基础路径获取该路径下的所有class文件 */
	public static List getAllClassByPackage(String... basePkg) throws Exception {
		List classList = new ArrayList();
		for (String basePath : basePkg) {
			String sysPath = basePath.replaceAll("\\.", "/");
			URL bsePath = ExtUtil.class.getClassLoader().getResource(sysPath);
			File f = new File(bsePath.getFile());
			if (!f.exists())
				throw new RuntimeException("指定路径不存在!");
			// 相对位置
			String root = f.getPath().replace(sysPath.replace("/", "\\"), "");
			Queue dirs = new LinkedBlockingQueue();
			dirs.add(f);
			for (;;) {
				File nextFile = dirs.poll();
				Arrays.asList(nextFile.listFiles()).forEach(file -> {
					if (file.isDirectory()) {
						dirs.add(file);
					} else {
						String filePath = file.getAbsolutePath();
						String classPath = filePath.replace(root, "").replaceAll("\\\\", "\\.");
						classList.add(classPath.substring(0, classPath.length() - ".Class".length()));
					}
				});
				if (dirs.isEmpty())
					break;
			}
		}
		return classList;
	}

	/** 字符串 */
	public static String randStr(int size) {
		StringBuffer buffer = new StringBuffer();
		for (int i = 0; i < size; i++) {
			buffer.append((char) baseStr.charAt(rnd.nextInt(baseStr.length() - 1)));
		}
		return buffer.toString();
	}

	/** 简单的将字段名称转成set方法 */
	public static String toSetMethodName(String fieldName) {
		String strHead = fieldName.substring(0, 1);
		StringBuffer buffer = new StringBuffer("set");
		buffer.append(fieldName.replace(strHead, strHead.toUpperCase()));
		return buffer.toString();
	}
}

然后是我们定义的BeanFactory和其直接实现子类

package bean;

public interface BeanFactory {

	/** 根据别名获取Bean实例 */
	Object getBean(String name) throws Exception;

	/** 根据别名和指定类型获取Bean */
	 T getBean(String name, Class classType) throws Exception;
}
package bean;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Resource;
import bean.annotation.Bean;
import bean.annotation.ScanPath;

/** 基于注解的BeanFactory实现 */
public class AnnotationBeanFactory implements BeanFactory {

	final static ClassLoader loader = AnnotationBeanFactory.class.getClassLoader();

	/** Bean定义缓存 */
	private Map beanCache = new ConcurrentHashMap<>();

	/** 初始化BeanFactory,这里只根据@ScanPath 配置路径扫描@Bean注解的类 */
	public static synchronized AnnotationBeanFactory initFactory(Class mainClass) throws Exception {
		AnnotationBeanFactory beanFactory = new AnnotationBeanFactory();
		ScanPath scan = mainClass.getAnnotation(ScanPath.class);
		if (scan == null)
			throw new Exception(mainClass.getName() + "未配置@ScanPath");
		ExtUtil.getAllClassByPackage(scan.basePath()).forEach(p -> {
			try {
				// 加载类并获取注解配置
				Class cls = loader.loadClass(p);
				Bean anno = cls.getAnnotation(Bean.class);
				// 初始化@Bean注解类
				if (anno != null) {
					BeanInfo bi = beanFactory.initBeanInfo(cls, anno);
					for (String als : anno.name()) {
						beanFactory.beanCache.put(als, bi);
					}
				}
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
		});
		return beanFactory;
	}

	@Override
	public Object getBean(String name) throws Exception {
		return beanCache.get(name).getBean();
	}

	@Override
	public  T getBean(String name, Class classType) throws Exception {
		return beanCache.get(name).getBean(classType);
	}

	/** 初始化指定bean信息 */
	private BeanInfo initBeanInfo(Class cls, Bean anno) {
		try {
			return new BeanInfo(cls, Bean.PROTOTYPE.equals(anno.scope()));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/** Bean信息定义 */
	class BeanInfo {
		public final Class cls;
		/** 是否原型模式 */
		public final boolean isPrototype;
		/** 对象实例,如果是单例模式直接返回该对象 */
		public final Object inst;
		/** 需要注入的字段[字段名称,注入的对象别名] */
		public final List diField = new ArrayList();

		/** 构造函数 */
		public BeanInfo(Class cls, boolean isPrototype) throws Exception {
			this.cls = cls;
			this.isPrototype = isPrototype;
			this.inst = cls.newInstance();
			// 这里只解析一下@Resource注解,以便我们可以注入该信息
			for (Field next : cls.getDeclaredFields()) {
				Resource rs = next.getAnnotation(Resource.class);
				if (rs != null) {
					diField.add(new String[] { next.getName(), rs.name() });
				}
			}
		}

		public Object getBean() throws Exception {
			if (!isPrototype) {
				return inst;
			}
			// 这里需要注意,如果是原型模式,则检查是否有需要注入的字段
			Object pinst = cls.newInstance();
			for (String[] diInfo : diField) {
				String methodName = diInfo[0], ansName = diInfo[1];
				BeanInfo argBean = beanCache.get(ansName);
				Method mtd = cls.getMethod(ExtUtil.toSetMethodName(methodName), argBean.cls);
				mtd.invoke(pinst, argBean.getBean());
			}
			return pinst;
		}

		@SuppressWarnings("unchecked")
		public  T getBean(Class t) throws Exception {
			return (T) getBean();
		}
	}
}

最后是我们的测试类:

package bean;

import bean.annotation.ScanPath;
import bean.infos.Car;
import bean.infos.Train;

/** 测试基于注解的BeanFactory */
@ScanPath(basePath = { "bean" })
public class TestApp {

	public static void main(String[] args) throws Exception {
		// 启动解析注解
		AnnotationBeanFactory factory = AnnotationBeanFactory.initFactory(TestApp.class);
		if (factory != null) {
			factory.getBean("myCar", Car.class).showInfo();
			factory.getBean("yourCar", Car.class).showInfo();
			factory.getBean("myTrain", Train.class).showInfo();
			factory.getBean("myTrain", Train.class).showInfo();
		}
	}
}

运行结果如下:

beanInfobean.infos.Car@5fd0d5ae====>制造作人:会和去
beanInfobean.infos.Car@5fd0d5ae====>制造作人:会和去
beanInfobean.infos.Train@2d98a335==>操作人:【么可年,90岁】,目的地:那要国里那
beanInfobean.infos.Train@16b98e56==>操作人:【么可年,90岁】,目的地:看会学多要

运行结果分析:

两次Car的获取和输出信息是一致的,说明返回的是同一个实例,两次Train输出的对象信息不一致,而OptUser信息一致,说明Train返回的是单例,OptUser不是单例。

修改Train类的scope 为单例:

再次运行结果如下:

beanInfobean.infos.Car@5fd0d5ae====>制造作人:过下天
beanInfobean.infos.Car@5fd0d5ae====>制造作人:过下天
beanInfobean.infos.Train@2d98a335==>操作人:null,目的地:都自那就和
beanInfobean.infos.Train@2d98a335==>操作人:null,目的地:都自那就和

两次Train输出的对象信息一致,而OptUsernull,说明没有找到optUser别名的对象,其实这是本次BeanFactory的一个bug,因为在创建Train单例模式的时候OptUser对象还没有生成,所以去获取的时候为null。修复该bug其实也很简单,去掉final修饰,getBean()采用懒加载。

我们再次修改TrainOptUser都为原型模式做最后测试:

测试结果如下

beanInfobean.infos.Car@5fd0d5ae====>制造作人:要好对
beanInfobean.infos.Car@5fd0d5ae====>制造作人:要好对
beanInfobean.infos.Train@2d98a335==>操作人:【那就她,69岁】,目的地:得起年出和
beanInfobean.infos.Train@16b98e56==>操作人:【就看就,19岁】,目的地:以她过会会

最后可以看到TrainOptUser对象都是原型模式了。

你可能感兴趣的:(java开发)