阶段一模块二

本文介绍了如何实现基于注解的IoC容器和声明式事务控制,包括自定义注解、类扫描、以及核心组件如Autowired、Component、Repository、Service和Transactional。文章对比了Spring的实现方式,指出了当前实现的不足,如@Autowired和@Transactional功能的局限性,以及固定包路径扫描和Servlet入口写死的问题。此外,还提到了在Tomcat运行时遇到的问题和Spring源码的相关知识点。

作业要求:

学员自定义 @Service@Autowired@Transactional 注解类,完成基于注解的 IoC 容器(Bean 对象创建及依赖注入维护)和声明式事务控制,写到转账工程中,并且可以实现转账成功和转账异常时事务回滚。
注意考虑以下情况:

  1. 注解有无 value 属性值【@service(value="") @Repository(value="")

  2. service 层是否实现接口的情况【 JDK 还是 CGLib

分析:

  1. 按照课程里面的项目,实现以 XML 形式的 IoC 容器,同时实现 AOP 功能。这里多说一句, XML 解析我是使用 DOM 搭配 XPath 实现的,与 MyBatis 中类似,但是没有进一步封装。

  2. 编写注解,替换注解中对应的标签,并且修改其解析代码,实现作业要求。

核心部分:

其实这个作业最核心的部分就是扫描对应包路径下的所有类。在 Java 当中,目前我所知几种实现方式:

  1. 纯 Java 方式,利用文件系统。
  2. 使用 Spring 中的扫描工具类 MetadataReaderFactory
  3. 使用外部反射工具,reflections

作业中,我是使用的 Spring 的扫描工具类实现对应功能,这种方式更加贴近 Spring 源码。纯 Java 的方式实现比较复杂,而且有可能出现问题。而使用外部反射工具,这种方式非常简单,其内部封装了许多方便快捷的方法。

代码:

注解部分:
  1. Autowired

    package cn.worstone.annotation;
    
    import java.lang.annotation.*;
    
    @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Autowired {
    }
    
  2. Component

    package cn.worstone.annotation;
    
    import java.lang.annotation.*;
    
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Component {
        String value() default "";
    }
    
  3. Repository

    package cn.worstone.annotation;
    
    import java.lang.annotation.*;
    
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Repository {
        String value() default "";
    }
    
  4. Service

    package cn.worstone.annotation;
    
    import java.lang.annotation.*;
    
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Repository {
        String value() default "";
    }
    
  5. Transactional

    package cn.worstone.annotation;
    
    import java.lang.annotation.*;
    
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface Transactional {
    }
    
代码修改部分:
  1. TransferServlet

        // private ProxyFactory proxyFactory = (ProxyFactory) XMLBeanFactory.getBean("proxyFactory");
        // private AccountService accountService = (AccountService) proxyFactory.getJdkProxy(XMLBeanFactory.getBean("accountService"));
        private AccountService accountService = (AccountService) AnnotationBeanFactory.getBean("accountService");
    

    这里说明一下,在 Spring 中,因为其通过 SpringMVC 中的 DispatchServlet 对所有请求进行拦截,然后统一处理分发,所以可以使用对应的 Controller 作为每次请求的入口。而这实在是太复杂了,实在是没有时间精力。/(ㄒoㄒ)/~~。所以这里只能和老师一样直接写死入口,其次,因为事务修改为注解实现的声明式事务,所以这块不需要显式获取代理对象,直接从 BeanFactory 中获取 Bean 即可。

  2. AnnotationBeanFactory

    package cn.worstone.factory;
    
    import cn.worstone.annotation.*;
    import cn.worstone.service.AccountService;
    import org.springframework.core.io.Resource;
    import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
    import org.springframework.core.io.support.ResourcePatternResolver;
    import org.springframework.core.type.classreading.MetadataReader;
    import org.springframework.core.type.classreading.MetadataReaderFactory;
    import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
    
    import java.lang.reflect.Field;
    import java.util.HashMap;
    import java.util.Map;
    
    public class AnnotationBeanFactory {
    
        private static Map<String, Object> beansMap = new HashMap<>();
    
        static {
            beansMap.putAll(XMLBeanFactory.getBeansMap());
            String packageName = "cn.worstone";
            try {
    
                ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
                Resource[] resources = resourcePatternResolver.getResources(packageHandler(packageName));
                MetadataReaderFactory metadata = new SimpleMetadataReaderFactory();
                for (Resource resource : resources) {
                    MetadataReader metadataReader = metadata.getMetadataReader(resource);
                    String className = metadataReader.getClassMetadata().getClassName();
                    Class<?> aClass = Class.forName(className);
                    Map<String, Object> componentMap = componentAnnotation(aClass);
                    if ((boolean) componentMap.get("isComponent")) {
                        className = getName(aClass, (String) componentMap.get("value"));
                        Object o = aClass.getDeclaredConstructor().newInstance();
                        beansMap.put(className, o);
                    }
                }
                Map<String, Object> transactionMap = new HashMap<>();
                for (String s : beansMap.keySet()) {
                    Object bean = beansMap.get(s);
                    // 处理 Autowired 注解
                    Field[] fields = bean.getClass().getDeclaredFields();
                    for (Field field : fields) {
                        field.setAccessible(true);
                        if (field.getAnnotation(Autowired.class) != null) {
                            Object o = getByType(field.getType());
                            // 正常来说需要对基础类型进行判断
                            if (o == null) {
                                throw new RuntimeException("Not have the type bean: " + field.getType());
                            }
                            field.set(bean, o);
                        }
                    }
                    beansMap.put(s, bean);
    
                    // 处理 Transactional 注解
                    Transactional transactional = bean.getClass().getAnnotation(Transactional.class);
                    if (transactional != null) {
                        transactionMap.put(s, bean);
                    }
                }
    
                for (String s : transactionMap.keySet()) {
                    Object bean = beansMap.get(s);
                    Class<?>[] interfaces = bean.getClass().getInterfaces();
                    ProxyFactory proxyFactory = (ProxyFactory) beansMap.get("proxyFactory");
                    Object proxy = null;
                    if (proxyFactory == null) {
                        throw new RuntimeException("Proxy Factory is null");
                    }
                    if (interfaces.length == 0) {
                        proxy = proxyFactory.getJdkProxy(bean);
                    } else {
                        proxy = proxyFactory.getCglibProxy(bean);
                    }
                    beansMap.put(s, proxy);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public static Object getBean(String id) {
            return beansMap.get(id);
        }
    
        private static Map<String, Object> componentAnnotation(Class<?> aClass) {
            Map<String, Object> result = new HashMap<>(4);
            result.put("isComponent", false);
            result.put("value", "");
            if (aClass.isAnnotation()) return result;
            Component component = aClass.getAnnotation(Component.class);
            Service service = aClass.getAnnotation(Service.class);
            Repository repository = aClass.getAnnotation(Repository.class);
            if (component != null) {
                result.put("isComponent", true);
                result.put("value", component.value());
                return result;
            }
            if (service != null) {
                result.put("isComponent", true);
                result.put("value", service.value());
                return result;
            }
            if (repository != null) {
                result.put("isComponent", true);
                result.put("value", repository.value());
                return result;
            }
            return result;
        }
    
        private static String toLowerCaseFirstChar(String word) {
            if (Character.isUpperCase(word.charAt(0))) {
                return word.replace(word.charAt(0), (char) (word.charAt(0) + 32));
            }
            return word;
        }
    
        private static String getName(Class<?> aClass, String value) {
            if (!"".equals(value)) return value;
            return toLowerCaseFirstChar(aClass.getSimpleName());
        }
    
        private static String packageHandler(String packageName) {
            packageName = packageName.replaceAll("\\.", "/");
            if (!packageName.endsWith("/")) {
                packageName += "/";
            }
            return packageName + "**/*.class";
        }
    
        private static Object getByType(Class<?> aClass) {
            Object result = null;
            for (String s : beansMap.keySet()) {
                Object o = beansMap.get(s);
                if (aClass.isAssignableFrom(o.getClass())) {
                    if (result != null) {
                        throw new RuntimeException("Finding the type bean is not only one: " + aClass.getName());
                    }
                    result = o;
                }
            }
            return result;
        }
    
        public static Map<String, Object> getBeansMap() {
            return beansMap;
        }
    }
    

    这个类为了与 XML 解析进行区分,所以重新创建了一个 BeanFactory,同时,XML 解析的 BeanFactory 让我修改为 XMLBeanFactory。

  3. ProxyFactory

    package cn.worstone.factory;
    
    import cn.worstone.annotation.Autowired;
    import cn.worstone.annotation.Component;
    import cn.worstone.utils.TransactionManager;
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.sql.SQLException;
    
    @Component
    public class ProxyFactory {
    
        @Autowired
        private TransactionManager transactionManager;
    
        public void setTransactionManager(TransactionManager transactionManager) {
            this.transactionManager = transactionManager;
        }
    
        // JDK 动态代理
        public Object getJdkProxy(Object obj) {
    
            // 获取代理对象
            return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    return getObject(method, args, obj);
                }
            });
        }
    
        // CGLib 动态代理
        public Object getCglibProxy(Object obj) {
            return Enhancer.create(obj.getClass(), new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    return getObject(method, objects, obj);
                }
            });
        }
    
        private Object getObject(Method method, Object[] args, Object obj) throws SQLException, IllegalAccessException, InvocationTargetException {
            Object result = null;
            try {
                // 开启事务 (关闭事务的自动提交)
                transactionManager.beginTransaction();
                result = method.invoke(obj, args);
                // 提交事务
                transactionManager.commit();
            } catch (Exception e) {
                e.printStackTrace();
                // 回滚事务
                transactionManager.rollback();
                // 抛出异常便于上层 Servlet 捕获
                throw e;
            }
            return result;
        }
    }
    

    这里将两个动态代理方法内部获取代理对象以及织入事务部分的代码抽象了出来,因为两者代码几乎一致。

目前存在的缺点:(对比 Spring)

  1. Autowired 注解目前功能实现比较单一,在 Spring 中可以放置在构造方法上。
  2. Transactional 注解功能实现比较单一,在 Spring 中有一整套非常完成的属性设置。
  3. 这里直接写死解析的包路径,在 Spring 中有相关的注解。
  4. 入口 Servlet 直接写死,在 Spring 是通过在 web.xml 中配置配置信息,将其加载到 ApplicationContext 中。

问题:

  1. 使用 Tomcat 的 Maven 插件没办法运行。
  2. 异常信息没有在控制台打印。
  3. Web 项目中,如何定义 Spring 入口
  4. Tomcat 没有找到类
  5. Spring 源码构建
  6. 如何通过注解的 Class 获取注解中的属性值
  7. 为什么无需要配置 Servlet 就可以直接使用

扩展:

什么鬼?弃用JDK动态代理,Spring5 默认使用 CGLIB 了?

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值