註解方式自定義實現Spring Ioc容器 + 事務 + 動態代理

@TOCjava

前言

上一篇點擊查看使用xml來實現自定義IOC以及依賴注入關係維護,動態代理,以及事務操做;web

此次使用註解來實現IOC以及依賴關係維護apache

步驟以及思路分析

基於xml實現方式時,僅僅只須要在xml裏面配置好bean的id以及bean的權限定命名,而後反射實例化對象,最後加入到ioc容器中
依賴注入時候僅僅須要獲取property標籤以及父級標籤,根據property名從ioc容器中獲取到須要注入的bean示例便可;api

若是是基於註解實現呢!tomcat

  • 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 "";
}

自動裝配的註解app

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

事務註解 Transactionalmaven

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

而後 在類上標註註解,以及依賴注入
在這裏插入圖片描述
事務註解以及DI 以及Bean自動裝配
在這裏插入圖片描述ide

項目結構

在這裏插入圖片描述

配置信息

版本 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
在這裏插入圖片描述

相關文章
相關標籤/搜索