注解方式自定义实现Spring Ioc容器 + 事务 + 动态代理

@TOC

前言

上一篇点击查看使用xml来实现自定义IOC以及依赖注入关系维护,动态代理,以及事务操作;

这次使用注解来实现IOC以及依赖关系维护

步骤以及思路分析

基于xml实现方式时,仅仅只需要在xml里面配置好bean的id以及bean的权限定命名,然后反射实例化对象,最后加入到ioc容器中
依赖注入时候仅仅需要获取property标签以及父级标签,根据property名从ioc容器中获取到需要注入的bean示例即可;

如果是基于注解实现呢!

  • 1 首先需要自定义注解
  • 2 如何获取自定义到的注解
  • 3 实例化打了注解的实例
  • 4 在指定bean成员变量上是否包含需要注入的注解,然后依赖注入
  • 5 生成代理对象,基于接口判断是否选择JDK动态代理或者CGLIB代理

代码实现

首先自定义注解

实例bean的注解 @Repository@Service

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
    String value() default "";
}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Repository {
    String value() default "";
}

自动装配的注解

@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    String name() default "";
}

事务注解 Transactional

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Transactional {
}

然后 在类上标注注解,以及依赖注入


在这里插入图片描述

事务注解以及DI 以及Bean自动装配


在这里插入图片描述

项目结构

在这里插入图片描述

配置信息

版本 JDK8 , tomcat 7 , IDEA 2019 03

所需依赖

    <!-- servlet -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    
    <!--引入cglib依赖包-->
    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>2.1_2</version>
    </dependency>

tomcat插件

   <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.2</version>
        <configuration>
          <port>8080</port>
          <path>/</path>
        </configuration>
      </plugin>

这里我们使用一个StartApplication类来表示当前的顶级包下的启动类,相当于SpringBoot里的Main方法所在的类(目的仅仅是指定包,也可以在xml里面配置包名)

在Web.xml里面进行配置一下这个启动类

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
    <display-name>tomcat启动时候启动IOC容器</display-name>
    <listener>
        <!-- 启动容器-->
        <!--      注解实现-->
        <listener-class>com.udeam.edu.factory.impl.AnnotationBeanFactory</listener-class>
        <!--    xml实现ioc-->
        <!--    <listener-class>com.udeam.edu.factory.impl.ClassPathXmlBeanFactory</listener-class>-->
    </listener>
</web-app>

定义BeanFactory接口类

/**
 * 底层BeanFactory工厂接口
 * @author Pilgrim
 */
public interface BeanFactory {

    /**
     * 存储bean单例
     */
    public final static Map<String, Object> IOC_MAP = new HashMap<>();


    /**
     * 对外提供获取bean接口
     *
     * @param id
     * @return bean对象
     */
    public Object getBean(String id);


    /**
     * 根据类型对外提供获取bean示例
     *
     * @param classz
     * @return bean
     */
    public Object getBean(Class<?> classz);

    /**
     * 获取容器中所有的bean名字
     *
     * @return
     */
    public Object getAllBeanName();
}

抽象类AbstractBeanFactory扩展一些属性

public abstract class AbstractBeanFactory implements BeanFactory {

    /**
     * 存储bean单例
     */
    public final static Map<String, Object> IOC_MAP = new HashMap<>();

    /**
     * 容器执行一次 标识
     */
    public static boolean isTag = false;

    public static final String CLASS_STR = ".class";

}

然后编写bean工厂实现类AnnotationBeanFactory

定义初始化方法
initBeanFactory(String packageName);

初始化方法包含以下方法

文件扫描路径
    /**
     * 递归处理路径下文件夹是否包含文件夹,如不包含则获取当前类的权限定命名存入set中
     *
     * @param packName
     * @param classNameSet
     * @param path
     */
    public static void parseFilePackName(String packName, Set<String> classNameSet, String path) 
bean实例化方法
 private void setBean();
bean依赖注入方法
 beanAutoWired()
事务处理注解方法
doScanTransactional()
/**
 * 注解方式 实现 Bean工厂
 *
 * @author Pilgrim
 */
public class AnnotationBeanFactory extends AbstractBeanFactory {

    /**
     * 2  注解 + 扫描包 方式实现 ioc 容器
     * tomcat启动的时候去初始化容器
     */
    public AnnotationBeanFactory() {
            if (isTag) {
            return;
        }
        try {
            String packageName = StartApplication.class.getPackage().getName();
            //扫描启动类的包名
            System.out.println("------------------- [容器]正在初始化 ------------ ");
            System.out.println(String.format("------------------- 扫描当前包是%s  ------------ ", packageName));
            initBeanFactory(packageName);
            System.out.println("------------------- [容器]初始化完成 ------------ ");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        isTag = true;
    }


}


包扫描

递归扫描包下的文件
com.xxx.xxx 包名需要转换成磁盘目录 c:/xxx/xx这样的形式

获取包名

   String packageName = StartApplication.class.getPackage().getName();

转换包名以及扫描包得到所有的class文件名


        if (Objects.isNull(packName) || packName.length() == 0) {
            throw new RuntimeException("无效的包路径");
        }
        packName = packName.replace(".", File.separator);
        URL resource = AnnotationBeanFactory.class.getClassLoader().getResource(packName);
        String path = resource.getPath();
        //解析中文
        String filePath = URLDecoder.decode(path, "UTF-8");

递归处理

    /**
     * 递归处理路径下文件夹是否包含文件夹,如不包含则获取当前类的权限定命名存入set中
     *
     * @param packName
     * @param classNameSet
     * @param path
     */
    public static void parseFilePackName(String packName, Set<String> classNameSet, String path) {

        File packNamePath = new File(path);

        if (!packNamePath.isDirectory() || !packNamePath.exists()) {
            return;
        }
        //递归路径下所有文件和文件夹
        for (File file : packNamePath.listFiles()) {
            boolean directory = file.isDirectory();
            String classNamePath = packName + File.separator + file.getName().replace(File.separator, ".");
            if (directory) {
                parseFilePackName(classNamePath, classNameSet, file.getPath());
            } else if (file.isFile() && file.getName().endsWith(CLASS_STR)) {
                //存入set
                classNameSet.add(classNamePath.replace(File.separator, ".").replace(CLASS_STR, ""));
            }
        }

    }

bean实例化

得到所有的java文件名,然后去实例化bean

判断是否包含我们刚才自定义的注解

    private void setBean() {
        stringSet.forEach(x -> {
            try {
                //排除指定包 servlet 类不能被实例化 这儿排除
                if (!x.contains("servlet")) {
                    Class<?> aClassz = Class.forName(x);
                    serviceAnnotation(aClassz);
                    repositoryAnnotation(aClassz);
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        });
    }

判断是否含有ServiceRepository注解

获取bean的名字 并且判断当前类是否有 Service 注解,如有则存入Ioc 如包含属性value不为空,则设置value属性为bean的key

    public void serviceAnnotation(Class aClass1) throws InstantiationException, IllegalAccessException {
        Service annotation = (Service) aClass1.getAnnotation(Service.class);
        if (Objects.nonNull(annotation)) {
            setIocNameMap(annotation.value(), aClass1.getSimpleName(), aClass1);
        }
    }

Repository 同理

    public void repositoryAnnotation(Class aClass) throws InstantiationException, IllegalAccessException {
        Repository annotation = (Repository) aClass.getAnnotation(Repository.class);
        if (Objects.nonNull(annotation)) {
            setIocNameMap(annotation.value(), aClass.getSimpleName(), aClass);
        }
    }

获取bean的name setIocNameMap方法然后实例化bean加入到容器
这儿判断一下是否是单例bean 这儿的单例指的是是否已经有一个bean了

    public void setIocNameMap(String value, String className, Class clasz) throws IllegalAccessException, InstantiationException {
        String iocNameString = value;
        Object beanDefinition =  clasz.newInstance() ;
        if (value.length() > 0) {
            if (IOC_MAP.containsKey(value)) {
                throw new RuntimeException("the named" + className + ",  had one ... ");
            }
        } else {
            //默认设置bean首字母小写的
            iocNameString = getIocNameString(className);
            if (IOC_MAP.containsKey(iocNameString)) {
                throw new RuntimeException("the named  " + className + ",  had one ... ");
            }
        }

        // 根据父接口类型注入
        Class<?>[] interfaces = clasz.getInterfaces();
        if (interfaces != null) {
            for (Class<?> anInterface : interfaces) {
                IOC_MAP.put(anInterface.getSimpleName(), beanDefinition);
            }
        }
        IOC_MAP.put(iocNameString, beanDefinition);
    }

设置首字母小写

   public static String getIocNameString(String className) {
        return (String.valueOf(className.toCharArray()[0])).toLowerCase() + className.substring(1, className.length());
    }

依赖注入

依赖注入方法,获取成员变量上有 Autowired 注解的字段,然后根据当前类类型去自动装配

    public static void beanAutoWired() throws ClassNotFoundException {
        //获取成员变量上有 Autowired 注解的字段,然后根据当前类类型去自动装配
        for (Map.Entry<String, Object> stringObjectEntry : IOC_MAP.entrySet()) {

            Object beanDefinition = stringObjectEntry.getValue();
            Class<?> aClass = beanDefinition.getClass();
            Field[] declaredFields = aClass.getDeclaredFields();

            if (Objects.isNull(declaredFields) && declaredFields.length == 0) {
                continue;
            }

            for (Field field : declaredFields) {
                //字段含有 Autowired 注解的需要被自动装配对象
                Autowired autowired = field.getAnnotation(Autowired.class);

                if (Objects.nonNull(autowired)) {
                    //根据当前key获取需要注入示例对象
                    //先根据名字注入,如果名字获取不到,再根据类型去注入
                    String beanName = autowired.name();

                    if (StringUtils.isEmpty(beanName)) {
                        beanName = field.getType().getSimpleName();
                    }

                    //反射设置值
                    try {
                        field.setAccessible(true);
                        //自动装配 线程不安全,Spring中默认单例
                        field.set(stringObjectEntry.getValue(), IOC_MAP.get(beanName));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }

扫描事务注解

    public void doScanTransactional() throws IllegalAccessException, InstantiationException, ClassNotFoundException {

        for (Map.Entry<String, Object> classBeanDefinitionEntry : IOC_MAP.entrySet()) {
            Object beanDefinition = classBeanDefinitionEntry.getValue();
            //判断生成代理对象
            Object proxy = getProxy(beanDefinition);
            if (proxy==null){
                proxy = beanDefinition;
            }
            //更新bean
            IOC_MAP.put(classBeanDefinitionEntry.getKey(), proxy);
        }


    }

判断选择哪个代理实现方式 根据是否实现接口

    public Object getProxy(Object aClass) {
        Object jdkProxy = null;

        Transactional annotation =  aClass.getClass().getDeclaredAnnotation(Transactional.class);
            if (Objects.nonNull(annotation)) {
                //有接口使用jdk动态代理
                 if (aClass.getClass().getInterfaces() == null || aClass.getClass().getInterfaces().length <= 0) {

                    //cglib动态代理
                    jdkProxy = ProxyFactory.getCglibProxy(aClass);
                } else {
                    /*for (Class anInterface : aClass.getClass().getInterfaces()) {
                        System.out.println(anInterface.getSimpleName());
                    }*/
                    jdkProxy = ProxyFactory.getJdkProxy(aClass);
                }
        }
      return jdkProxy;

    }

代理对象实现 以及方法执行前后处理事务

/**
 * 代理类工厂
 *
 * @author Pilgrim
 */
public class ProxyFactory {

    /**
     * 事务管理器
     */
  private final TransferServiceManager t = TransferServiceManager.get();

    /**
     * Jdk动态代理
     *
     * @param obj 被代理的对象
     * @return 返回代理对象
     */
    public static Object getJdkProxy(Object obj) {

        Object o = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {

                Object invoke = null;
                try {
                    // 开启事务(关闭事务的自动提交)
                    TransferServiceManager.get().start();
                    invoke = method.invoke(obj, objects);
                    // 提交事务
                    TransferServiceManager.get().commit();
                } catch (Exception e) {
                    e.printStackTrace();
                    // 回滚事务
                    TransferServiceManager.get().rowback();
                    throw e;
                }

                return invoke;
            }
        });

        return o;

    }


    /**
     * cglib动态代理
     *
     * @param object 被代理的对象
     * @return 返回代理对象
     */
    public static Object getCglibProxy(Object object) {

        //生成代理对象
        return Enhancer.create(object.getClass(), new MethodInterceptor() {

            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                Object result = null;
                try {

                    //开启事务
                    TransferServiceManager.get().start();

                    result = method.invoke(object, objects);

                    //提交事务
                    TransferServiceManager.get().commit();
                } catch (Exception e) {
                    //回滚事务
                    TransferServiceManager.get().rowback();
                    throw e;
                }
                return result;

            }
        });

    }

初始化方法步骤

    /**
     * 初始化bean
     * 1 递归扫描包获取类权限定命名
     * 2 实例化bean
     * 3 依赖注入
     * 4 扫描事务注解 生成代理对象
     *
     * @param packName
     * @throws UnsupportedEncodingException
     */
    public void initBeanFactory(String packName) throws UnsupportedEncodingException, InstantiationException, IllegalAccessException, ClassNotFoundException {

        if (Objects.isNull(packName) || packName.length() == 0) {
            throw new RuntimeException("无效的包路径");
        }
        packName = packName.replace(".", File.separator);
        URL resource = AnnotationBeanFactory.class.getClassLoader().getResource(packName);
        String path = resource.getPath();
        //解析中文
        String filePath = URLDecoder.decode(path, "UTF-8");

        //解析包成java权限定命名com
        parseFilePackName(packName, stringSet, filePath);
        //实例化bean
        setBean();

        //System.out.println(String.format("获取到的bean : %s ", IOC_MAP));

        //自动装配
        beanAutoWired();

        //扫描事务注解
        doScanTransactional();

    }

对外提供getBean(方法)

实现getBean方法

    @Override
    //根据id名获取
    public Object getBean(String id) {
        if (Objects.nonNull(id) && id.length() > 0) {
            Object beanDefinition = IOC_MAP.get(id);
            return beanDefinition;
        }
        return null;
    }


    @Override
    //根据类型获取
    public Object getBean(Class<?> aClass) {
        if (Objects.isNull(aClass)) {
            return null;
        }
        return IOC_MAP.get(aClass.getSimpleName());
    }

    @Override
    public Object getAllBeanName() {
        return IOC_MAP.keySet();
    }

测试

启动tomcat可以看到启动成功,bean实例化完成


在这里插入图片描述

在servlet init方法里可以调用看一下

    private TransferService transferService  ;

    @Override
    public void init() throws ServletException {
        BeanFactory  beanFactory = new AnnotationBeanFactory();
        transferService= (TransferService)beanFactory.getBean("transferServiceImpl");
        TransferService transferService2  = (TransferService) beanFactory.getBean(TransferService.class);
        super.init();
    }

debug可以看到 根据类型还是id都可以获取到代理之后的bean


在这里插入图片描述
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容