这个工程旨在练习Java注解和反射,以及体会依赖注入的原理、过程,不以追求可靠、可用为目的,且阅读此博客前应当熟练掌握Java且有一定的Spring使用经验
模拟Spring中的部分注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Bean { /** * 指定Bean的名字 */ String name() default ""; }
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Configuration { }
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Import { /** * 指定要引入的配置类 */ Class<!--?-->[] classes(); }
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Qualifier { /** * 自动装配时指定需要的Bean的名字 */ String value() default ""; }
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Autowired { /** * 指定自动装配的方式,通过类型还是名字 */ AutoWiredType type() default AutoWiredType.BY_TYPE; }
public enum AutoWiredType { /** * ByType自动装配 */ BY_TYPE, /** * ByName自动装配 */ BY_NAME }
只要继承Exception类即可,异常类如下
首先我们需要一个静态HashMap变量充当单例Spring容器
/** * Spring容器 */ private static HashMap<String, Object> container = null;
然后创建一个向容器中增加Bean的方法addBean
,通过synchronized
关键字确保线程安全
/** * 添加Bean到容器 * @param name 名字 * @param o 对象 * @throws BeanExistException 异常 */ private synchronized static void addBean(String name, Object o) throws BeanExistException { //不允许重复添加 if (container.containsKey(name)) { throw new BeanExistException("already exist bean with name:'" + name + "'"); } container.put(name, o); }
编写获取Bean的方法getBean
,并进行重载,分别使用字符串、Class对象做参数
public Object getBean(String name) throws NoSuchNameBeanException { if (!container.containsKey(name)) { //如果容器中没有这个名字的Bean就丢出异常 throw new NoSuchNameBeanException("there is no bean with name:" + name); } return container.get(name); } @SuppressWarnings("unchecked") public <T> T getBean(Class<T> requiredType, String name) throws NoSuchTypeBeanException, NoQualifiedBeanException, MultipleQualifiedBeanException { Set<Map.Entry<String, Object>> entries = container.entrySet(); boolean byType = false;// ByType自动装配的结果标志 T bean=null; for (Map.Entry<String, Object> entry : entries) { if (requiredType.isAssignableFrom(entry.getValue().getClass())) { byType = true;// 只要有对应类型的Bean就认为ByType成功 if (name == null || name.trim().isEmpty() || name.equals(entry.getKey())) {// ByType成功后进一步判断有没有指定Bean的名字 if (bean != null) { //如果能找到多个满足条件的Bean就抛出异常 throw new MultipleQualifiedBeanException("there is more than one qualified bean with type:"+requiredType.getName()); } bean = ((T) entry.getValue()); } } } if (bean!=null){// 如果找到了符合条件的Bean就返回 return bean; } if (!byType) {// 如果ByType失败,就抛出ByType失败的异常 throw new NoSuchTypeBeanException("there is no bean with type:" + requiredType.getName()); } else {// 如果ByType成功而根据指定name筛选失败则抛出没有满足条件Bean异常 throw new NoQualifiedBeanException("there is no qualified bean with type:" + requiredType.getName() + ",and with name:" + name); } }
编写初始化容器的方法initContainer
,使用synchronized
关键字保证线程安全,另外由于我们有一个@Import
注解,需要递归调用这个初始化方法,所以需要一个HashSet用于记录已经被解析过的配置类,防止两个配置类同时在@Import
中引用对方导致无限递归和重复定义Bean导致抛出异常
private synchronized void initContainer(String name) throws Exception { if (this.alreadyInitClassName==null){ this.alreadyInitClassName=new HashSet<>(); } if (this.alreadyInitClassName.contains(name)){ return; }else { this.alreadyInitClassName.add(name); } // 反射加载 Class<?> aClass = Class.forName(name); // 判断所选类是否存在@Configuration注解 if (aClass.isAnnotationPresent(Configuration.class)) { // 创建一个配置类对象 Object config = aClass.newInstance(); // 获取配置类中所有的方法 Method[] declaredMethods = aClass.getDeclaredMethods(); if (container == null) { container = new HashMap<>(declaredMethods.length*4/3+1); } // 遍历 for (Method declaredMethod : declaredMethods) { // 判断此方法是否存在@Bean注解 if (declaredMethod.isAnnotationPresent(Bean.class)) { // 如果没有指定Bean的名字 if ("".equals(declaredMethod.getAnnotation(Bean.class).name())) { // 就使用方法名作为bean的名字并执行这个方法初始化bean并注入到容器 addBean(declaredMethod.getName(), declaredMethod.invoke(config)); } else { // 否则使用指定的名字初始化bean并注入到容器 addBean(declaredMethod.getAnnotation(Bean.class).name(), declaredMethod.invoke(config)); } } } } if (aClass.isAnnotationPresent(Import.class)){ Class<?>[] classes = aClass.getAnnotation(Import.class).classes(); for (Class<?> aClass1 : classes) { initContainer(aClass1.getName()); } } }
然后定义自动装配方法autowiredInit
,并进行重载,一个接受字符串参数,一个接受Class对象参数
public synchronized Object autowiredInit(String name) throws Exception { // 加载这个要装配的类的class对象 Class<?> aClass = Class.forName(name); return autowiredInit(aClass); } public synchronized <T> T autowiredInit(Class<T> clazz) throws Exception{ // 构造这个类的实例对象 T o = clazz.newInstance(); // 获取这个类所有的声明的变量 Field[] declaredFields = clazz.getDeclaredFields(); // 遍历这些变量 for (Field declaredField : declaredFields) { // 判断这个变量是否有@Autowired修饰 if (declaredField.isAnnotationPresent(Autowired.class)) { // 修改访问权限,可以修改private的变量 declaredField.setAccessible(true); AutoWiredType type = declaredField.getAnnotation(Autowired.class).type(); if (type==AutoWiredType.BY_TYPE){// 判断自动装配的方式 if (declaredField.isAnnotationPresent(Qualifier.class)){ String qualifyName = declaredField.getAnnotation(Qualifier.class).value(); declaredField.set(o,this.getBean(declaredField.getType(),qualifyName)); }else{ declaredField.set(o,this.getBean(declaredField.getType(),null)); } }else { // 获取变量的名字 String name1 = declaredField.getName(); // 获取对应的Bean Object bean = getBean(name1); // 设置值 declaredField.set(o, bean); } } } // 返回这个被装配完毕的实例对象 return o; }
最后编写构造函数,同样具有两个重载
public Container(String name) throws Exception { initContainer(name); } public Container(Class<?> configClass) throws Exception { initContainer(configClass.getName()); }
随便编写几个JavaBean即可,只要具有一些属性并且能够正常输出即可
示例:
public class DataSource { private String url; private String username; private String password; @Override public String toString() { return "DataSource{" + "url='" + url + '\'' + ", username='" + username + '\'' + ", password='" + password + '\'' + '}'; } public DataSource() { } public void setUrl(String url) { this.url = url; } public String getUrl() { return url; } public String getUsername() { return username; } public String getPassword() { return password; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } }
其他Bean如下:
编写service层接口和实现类,验证控制反转和依赖倒转
public interface IDemoService { /** * 输出一段话 */ void demo(); }
实现类一:
public class DemoServiceImpl implements IDemoService { @Override public void demo() { System.out.println("这是第一种实现"); } }
实现类二:
@Override public void demo() { System.out.println("这是第二种实现"); } }
编写多个配置文件
@Configuration @Import(classes = {Config2.class,Config3.class}) public class Config { @Bean(name = "mysql") public DataSource dataSource(){ DataSource dataSource = new DataSource(); dataSource.setUrl("jdbc:mysql://localhost:3306"); dataSource.setUsername("root"); dataSource.setPassword("123456"); return dataSource; } @Bean public SqlSessionFactory sqlSessionFactory(){ SqlSessionFactory sqlSessionFactory = new SqlSessionFactory(); sqlSessionFactory.setDataSource(dataSource()); return sqlSessionFactory; } @Bean public DataSourceTransactionManager transactionManager(){ DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); dataSourceTransactionManager.setDataSource(dataSource()); return dataSourceTransactionManager; } @Bean public Encoding encoding(){ Encoding encoding = new Encoding(); encoding.setEncoding("UTF-8"); return encoding; } }
@Configuration // 由于容器类做了判断,此处不会出现无限递归及重复定义Bean的异常 @Import(classes = {Config.class,Config3.class}) public class Config2 { @Bean public IDemoService iDemoService2(){ // 注册第二个实现类 return new AnotherDemoServiceImpl(); } }
@Configuration public class Config3 { @Bean public IDemoService iDemoService(){ // 注册第一个实现类 return new DemoServiceImpl(); } }
编写启动类
public class Client { @Autowired private DataSource mysql; @Autowired private DataSourceTransactionManager transactionManager; @Autowired @Qualifier("iDemoService")// 从多个IDemoService类型Bean中通过name指定 private IDemoService service; public static void main(String[] args) throws Exception { //使用指定配置文件初始化容器 Container container = new Container(Config.class); // 进行自动装配 Client client = container.autowiredInit(Client.class); // 调用方法 client.service.demo(); System.out.println(client.mysql); } }