自定义@Service,@Autowired,@Transactional注解

拉钩java高薪训练营的转账项目,自定义注解完成转账。下面是我代码的改造主要实现思路。

1.整个项目目录,核心改造就是增加自定义注解,改造BeanFactory和ProxyFactory。

 2.BeanFactory之前做的事情,主要是从bean.xml获取对象的配置信息,将其解析和装配到Map容器中,现在我们要做的是:

(1)通过反射技术,找到含有@Service注解的类,反射生成其实例,存放到Map容器中。

(2)找到含有@Autowired注解属性,反射获取对象,将其装配到与其对应的类的实例中。

(3)提供一个方法,将类名或者其实现的接口名的首字母小写作为key,在Map容器中找到这个装配好的实例对象。

如下代码:

public class BeanFactory {

    /**
     * xml配置bean做法
     * 任务一:读取解析xml,通过反射技术实例化对象并且存储待用(map集合)
     * 任务二:对外提供获取实例对象的接口(根据id获取)
     */

    /**
     * 注解配置bean的做法
     * 1.找到含有注解@service的类,通过反射技术实例化对象并且存储待用(map集合)
     * 2.对外提供获取实例对象的接口
     */

    private static Map<String,Object> map = new HashMap<>();  // 存储对象

    static {
        //获取包com.lagou.edu下面的所有的Java文件
        Reflections reflections = new Reflections("com.lagou.edu");
        //找出带有service注解的class
        Set<Class<?>> containServiceClass = reflections.getTypesAnnotatedWith(Service.class);
        containServiceClass.stream().forEach(beanServiceClass->{
            try {
                //将class实例化成对象
                Object obj = beanServiceClass.newInstance();
                //获取该类的注解信息
                Service serviceAnnotation = beanServiceClass.getAnnotation(Service.class);
                //获取注解值(注解括号的值,别名)
                String annotationValue = serviceAnnotation.value();
                //如果么有别名,取类名
                if(annotationValue==null || "".equals(annotationValue)){
                    String simpleName = beanServiceClass.getSimpleName();
                    //如果是接口实现类,即该类是实现接口的,比如TransferServiceImpl实现接口TransferService
                    Class<?>[] interfaces = beanServiceClass.getInterfaces();
                    if(interfaces!=null && interfaces.length>0){
                        simpleName = interfaces[0].getSimpleName();
                    }
                    //驼峰命名法,首字母小写
                    annotationValue = (new StringBuilder()).append(Character.toLowerCase(simpleName.charAt(0))).append(simpleName.substring(1)).toString();
                    /************将驼峰命名法的类名+该类的实例,以键值对的形式放到map中,备用*********/
                    System.out.println("开始加载对象名称:"+annotationValue+",值:"+obj);
                    map.put(annotationValue,obj);
                    /************将驼峰命名法的类名+该类的实例,以键值对的形式放到map中,备用*********/
                }
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        });
        //通过反射获取map中实例的属性信息:找到带有注解@Autowired
        if(!map.isEmpty()){
            for(Map.Entry<String,Object> entry:map.entrySet()){
                Object obj = entry.getValue();
                String key = entry.getKey();
                //获取该对象的class信息
                Class<?> clazz = obj.getClass();
                //通过反射,找到这个类的自己的全部属性(注意:父类的属性这里取不到)
                Field[] fields = clazz.getDeclaredFields();
                for(int i=0;i<fields.length;i++){
                    Field field = fields[i];
                    //判断该属性是否使用了注解@Autowired
                    if(field.isAnnotationPresent(Autowried.class)){
                        //设置访问权限
                        field.setAccessible(true);
                        //在map容器中找到属性的对象-一定存在,否则报错。注意:标注@Autowird的注解属性,这个类会加@Service或者@Mapper等等注解。
                        Object value = map.get(field.getName());
                        //为属性赋值
                        try {
                            System.out.println("对象名称:"+key+",开始加载装配对象,名称:"+field.getName()+",值:"+value);
                            field.set(obj,value);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    //通过类名的获取该对象(默认首字母小写)
    public static Object getBean(String name){
        Object o = map.get(name);
        return o;
    }

 3.改造ProxyFactory,现在通过找到类中或者方法上存在的@Transactional注解情况,本文是注解到类上为例,通过对象,生成它的动态代理对象,来执行转账操作。动态代理在这里其实做了一个增强操作:

(1)转账之前,开启事务(关闭事务的自动提交)

(2)正常业务执行操作

(3)提交事物

(4)异常回滚。异常回滚生效的原因,其实就是一系列数据库的操作,绑定到同一个数据库连接上,才能回滚。 

(5)两种动态代理方法,JDK动态代理和cglib动态代理。有接口实现的类,我们优先使用JDK动态代理(毕竟是JDK提供的,jdk8优化了实现,高效),注意,无接口实现的类,不能使用JDK的动态代理。cglib动态代理有无接口实现的类都行。

代码如下:

@Service
public class ProxyFactory {

    @Autowried
    private TransactionManager transactionManager;

    /**
     * JDK动态代理
     * @param obj
     * @return
     */
    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 {
                //获取Class对象
                Class<?> clazz = obj.getClass();
                Object result = null;
                //如果这个class对象包含@Tansaction注解,加入手动开启事物,手动提交事物的操作
                if(clazz.isAnnotationPresent(Transactional.class)){
                    try {
                        // 开启事务(关闭事务的自动提交)
                        transactionManager.beginTransaction();
                        //执行正常的业务逻辑,返回结果
                        result = method.invoke(obj,args);
                        // 提交事务
                        transactionManager.commit();

                    }catch (Exception e){
                        e.printStackTrace();
                        //异常操作,回滚事务
                        transactionManager.rollback();
                        //抛出异常
                        throw e;
                    }
                }else{
                    //没有@Tansaction注解,不使用事务,直接执行业务方法
                    result = method.invoke(obj, args);
                }
                return result;
            }
        });
    }

    /**
     * 使用cglib动态代理
     * @param obj
     * @return
     */
    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 {
                //获取Class对象
                Class<?> clazz = obj.getClass();
                //如果这个class对象包含@Tansaction注解,加入手动开启事物,手动提交事物的操作
                Object result = null;
                if(clazz.isAnnotationPresent(Transactional.class)){
                    try {
                        // 开启事务(关闭事务的自动提交)
                        transactionManager.beginTransaction();
                        //执行正常的业务逻辑,返回结果
                        result = method.invoke(obj,objects);
                        // 提交事务
                        transactionManager.commit();
                    }catch (Exception e){
                        e.printStackTrace();
                        //异常操作,回滚事务
                        transactionManager.rollback();
                        //抛出异常
                        throw e;
                    }
                }else{
                    //没有@Tansaction注解,不使用事务,直接执行业务方法
                    result = method.invoke(obj, objects);

                }
                return result;
            }
        });
    }

    //获取动态代理
    public Object getProxy(Object obj){
        try{
            Class<?> clazz = obj.getClass();
            Class<?>[] interfaces = clazz.getInterfaces();
            Object result;
            //有实现接口的类,优先使用jdk提供的动态代理
            if(interfaces!=null && interfaces.length>0){
                result = getJDKProxy(obj);
            }else{
                //没有接口的实现类,只能使用cglib动态代理
                result = getCglibProxy(obj);
            }
            return result;
        }catch (Exception e){
            e.printStackTrace();
            throw e;
        }
    }
}

4.注释掉bean.xml中的配置,这些都没有用了,删掉文件也行。

<?xml version="1.0" encoding="UTF-8" ?>
<!--跟标签beans,里面配置一个又一个的bean子标签,每一个bean子标签都代表一个类的配置-->
<beans>
    <!--id标识对象,class是类的全限定类名-->
<!--    <bean id="accountDao" class="com.lagou.edu.dao.impl.JdbcAccountDaoImpl">-->
<!--        <property name="ConnectionUtils" ref="connectionUtils"/>-->
<!--    </bean>-->
<!--    <bean id="transferService" class="com.lagou.edu.service.impl.TransferServiceImpl">-->
<!--        &lt;!&ndash;set+ name 之后锁定到传值的set方法了,通过反射技术可以调用该方法传入对应的值&ndash;&gt;-->
<!--        <property name="AccountDao" ref="accountDao"></property>-->
<!--    </bean>-->


    <!--配置新增的三个Bean-->
<!--    <bean id="connectionUtils" class="com.lagou.edu.utils.ConnectionUtils"></bean>-->

    <!--事务管理器-->
<!--    <bean id="transactionManager" class="com.lagou.edu.utils.TransactionManager">-->
<!--        <property name="ConnectionUtils" ref="connectionUtils"/>-->
<!--    </bean>-->

    <!--代理对象工厂-->
<!--    <bean id="proxyFactory" class="com.lagou.edu.factory.ProxyFactory">-->
<!--        <property name="TransactionManager" ref="transactionManager"/>-->
<!--    </bean>-->
</beans>

5.(1)给JdbcAccountDaoImpl实现类,TransferServiceImpl实现类,connectionUtils工具类,TransactionManager事物管理器,ProxyFactory代理工厂类加上@Service注解,给这些的属性加上@Autowired注解,注意,去掉之前的set注入。

(2)给TransferServiceImpl转账实现类,添加@Transactional事务注解。

如下图:

 6.改造完成以后,启动服务,我们可以看到效果。

(1)转账前:

(2)转账操作:

(3) 转账后:

(4)转账异常情况,回滚就不上图了,大家都需要可以参考代码:https://gitee.com/sznsky/lagou-transfer 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值