手写ioc和aop容器

  在前几篇文章中,分别介绍了实现pointcut表达式的功能从xml中获取信息创建一个简单的ioc容器,如何解析注解利用cglib生成新代理类该如何获取字段注解根据这些知识点,就可以实现一个简易版的ioc容器。

IOC

创建bean过程

在这里插入图片描述

  在扫描xml中的配置时可能会有通过注解配置bean,由于在配置bean时可能会有相互引用bean的情况,因此需要xml中配置的bean信息和注解bean的信息加载完之后再来创建bean;

  在创建bean时需要考虑到以下几个方面:

  • 1.单例,多例bean应该在什么时候创建
  • 2.bean的循环引用检测
  • 3.在什么时候填充bean的属性

  在创建bean的时候考虑到bean可能是单例或者是多例;那么在根据bean信息创建bean时多例bean就不需要在这个时候创建了,只需要在使用到多例bean时再创建;而单例bean则需要在这个阶段创建,因为只需要创建一次可供多次使用;

  在创建bean时,如果在构造方法中引用了其他bean就可能会造成循环引用,因此在创建bean时还需要检测是否构成了循环引用;那如何检测呢?其实也比较简单,只需要在创建bean时,将正在创建的beanName放入到一个set集合中,bean创建完成就将beanName从集合中删除;

在这里插入图片描述

  在初始化bean之后,应该立即将bean的属性填充吗?

  答案是否定的,因为可能会有aop切面的配置;aop代理有2种方式:JDK动态代理,cglib代理;cglib代理会导致代理后的对象属性值消失;因而为了统一处理,应该在aop代理完成之后再填充bean的属性;

在这里插入图片描述
  cglib代理会生成一个新class,而target只是提供class信息;新代理生成的cglibObject对象与原target对象没有关联;因而如果创建target对象时给target对象属性赋值经cglib代理之后,新生成的对象cglibObject的属性值仍然是null;

在这里插入图片描述
  jdk动态代理的生成的类,proxyObject对原target对象保持引用;因此对target对象的属性赋值之后,经aop代理生成的类不会丢失原target的属性值;

如何获取bean?

  在创建bean时只创建了单例bean,如果获取的bean是多例,那么就需要重新创建;
在这里插入图片描述

AOP

流程:

在这里插入图片描述

  从配置中获取到所有的advices,容器中的bean去匹配advices;这样bean就得到了匹配的advice,通过bean+advice由aop代理生成代理类对象;

advice的类型

  advice一共有5中类型,其中after,around可以看作是复合类型,因此5种adviceType,可以分解为3种;
在这里插入图片描述

AOP代理

  由于同一个目标类,可能同时被多个切面类代理;如果每被一个切面代理都要生成一个新的类型,这样效率就比较低;因此要将这些作用到同一个类的切面整合到一起,一次代理整合多个不同切面的advice;

  切面类的整合实际是advice的整合,advice分为3类:before,afterReturning,afterThrowing;将所有切面配置的advice,按照类型分别存放到这3种类型中;

在这里插入图片描述

advice去重

  advice加入到MethodAdvice时,是根据type类型分别加入到对应的XXXadviceInterceptor中;before,afterReturning,afterThrowing这三种advice对应:BeforeAdviceInterceptor,AfterReturningAdviceInterceptor,AfterThrowingAdviceInterceptor;
  而advice,around与其他三种类型不同;

  • after要将其分别加入到:AfterReturningAdviceInterceptor,AfterThrowingAdviceInterceptor;
  • around要将其加入到:BeforeAdviceInterceptor,AfterReturningAdviceInterceptor,AfterThrowingAdviceInterceptor;

  每个advice可以分成三部分:adviceType + pointcut + method;假设现在有2个advice:

  • before:<before pointcut = " execution( * * * test*())" method="before" />
  • around: <around pointcut = " execution( * * * test*())" method="before" />

  这2个advice是同一个切面,那么before与around的:pointcut ,method,都相同;而around可以分解为:before + afterReturning + afterThrowing;around生成的advice会加入到这3个XXXadviceInterceptor中;在beforeAdviceInterceptor中就有2个advice对象,而这2个advice调用的切面方法相同都是before,就会造成调用相同切面的before方法2次;那如何去重也就简单了,只需要在XXXadviceInterceptor中判断:新加入的advice在该XXXadviceInterceptor中是否存在;advice是否存在由:pointcut ,method决定;

  这里需要注意的是,Advice是放入到Set中,需要重写Advice的hashcode和equals方法;而Advice包含Pointcut对象,因此还需要重写Pointcut的hashcode和equals方法;

测试

  • AOP 的advice去重测试
<beans>
    <bean  id="aspect" class="tt.AOPtest"/>
    <bean class ="tt.Aspected"/>
    <aopConfig>
        <aspect ref = "aspect">
            <before pointcut="execution (* * tt.inr.impl.* *())"  method="before" />
            <!--<after pointcut="execution (* * * *())"  method="after" />-->
            <around pointcut="execution (* * tt.inr.impl.*  *())"  method="before" />
        </aspect>
    </aopConfig>
    <propertyPlaceholder load="test.properties"/>
    <componentScan /><!--- 扫描注解-->
</beans>

test.properties

t1=dfgdfg
t2=ty
t3=name1
name=78
fg=456

bean

@Component
public class QAZ implements QWE {

    @Value(" ${t1} ")
    String tt;
    @Value("${ fg }")
    String gh;
    @Value("${name}")
    String nb;
    @Value("my addr")
    String addr;

    @Override
    public String toString() {
        return "QAZ{" +
                "tt='" + tt + '\'' +
                ", gh='" + gh + '\'' +
                ", nb='" + nb + '\'' +
                ", addr='" + addr + '\'' +
                '}';
    }


    @Override
    public void print() {
        System.out.println(toString());
    }
}

测试代码:

    @Test
    public void test() throws Throwable {
        Map<String , XmlNode> labelParse  = new HashMap<>();
        labelParse.put("aopConfig", AdvisorParse.getInstance());
        ApplicationContext context =
                new XmlApplicationContext("aop.xml",labelParse,null);
        Aspected aspected = (Aspected) context.getBean(Aspected.class);
        QWE qaz = (QWE) context.getBean(QWE.class);
        qaz.print();

    }
==============================result===============================================================
aspect before test
QAZ{tt='dfgdfg', gh='456', nb='78', addr='my addr'}
aspect before test

  • 循环引用测试

只有在构造方法中引用其他bean,才可能造成循环引用;

public class A {
    B b;
    public A(B b){
        this.b = b;
    }
}

public class B {
	public A a;
	public B(A a){
	    this.a = a;
	}
}

xml配置


<beans >
    <bean id="test2" class="tt.B" >
        <constructor type="tt.A" ref="test1" />
    </bean>

    <bean id="test1" class="tt.A">
        <constructor type="tt.B" ref="test2" />
    </bean>

</beans>

结果

exception.CycleDependencyException:  tt.B->tt.A->tt.B
	at Context.BeanFactory.creatBean(BeanFactory.java:75)
	at Context.BeanFactory.getConstructorParamRefValue(BeanFactory.java:136)

  • @Autowired测试
@Component
public class QAZ implements QWE {

    @Autowired
    AutowiredTest autowiredTest;
    ....
    ..
}


@Component
public class AutowiredTest {
    @Value("${autowired }")
    String q1;
    @Value("dgd")
    String q2;
    @Value("1")
    int yy;
    @Value("true")
    boolean rr;
    @Override
    public String toString() {
        return "AutowiredTest{" +
                "q1='" + q1 + '\'' +
                ", q2='" + q2 + '\'' +
                ", yy=" + yy +
                ", rr=" + rr +
                '}';
    }
}

结果


aspect before test
QAZ{autowiredTest=AutowiredTest{q1='testAutowire', q2='dgd', yy=1, rr=true}, tt='dfgdfg', gh='456', nb='78', addr='my addr'}
aspect before test

代码地址:ioc-aop

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值