关于spring:注解方式自定义实现Spring-Ioc容器-事务-动态代理

5次阅读

共计 11276 个字符,预计需要花费 29 分钟才能阅读完成。

@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

正文完
 0