山寨一下spring的ioc和aop

说到spring,java开发人员完全不会陌生,因为开发spring已经整合了各种主流框架,可以说是垄断了java程序员的所有框架,说到spring,就离不开它的俩大核心IOC和AOP,IOC控制反转,顾名思义就是把创建对象的控制权从程序员手里反转到了框架,利用java反射特性去构建对象,然后自动注入到属性里,spring实现IOC用三种方式配置xml、annotation、javaconfig。AOP面向切面编程,就是在不改函数的基础上在调用函数或者方法开始、中间、结束当切入点,将切面逻辑放进去。简单来说就是对函数进行增强。spring 实现AOP主要技术是用JDK动态代理和Cglib动态代理,就是在java编译期不知道目标类是什么,而再执行的时候给目标类生成代理子类,子类会重写目标类中所有非final的方法。

既然要山寨spring的俩大核心技术,就先制定山寨方案,因为spring实现这俩大技术有多种方式,山寨就选择一种即可。ioc的实现用annotation,aop就用cglib动态代理。

首先需要定义俩个annotation,一个是用来实现IOC的BeanAnnoation(山寨@Bean),一个是用来实现AOP的LogAnnotation(模拟方法调用打印日志的注解)。然后@Autowried就用java原生的@Resource去实现

//@Retention(value = RetentionPolicy.RUNTIME)必写,否则在运行的时候无法读取到该注解
@Documented
@Retention(value = RetentionPolicy.RUNTIME)
public @interface LogAnnotation {
}
@Documented
@Retention(value = RetentionPolicy.RUNTIME)
public @interface BeanAnnoation {
}

然后需要一个可以创建cglib动态代理对象的类

public class CGlibProxy implements MethodInterceptor {
    private Object obj;
    public CGlibProxy(String className){
        try {
            this.obj=Class.forName(className).newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
    public CGlibProxy(Object obj){
        this.obj=obj;
    }
    public Object buildProxyObject(){
        Enhancer enhancer = new Enhancer();  
        enhancer.setSuperclass(obj.getClass());
        enhancer.setCallback(this);
        return enhancer.create();

    }
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        String className=obj.getClass().getName().split("\\$")[0];
        if(method.isAnnotationPresent(LogAnnotation.class)){
            System.out.println(className+"."+method.getName()+"方法开始执行");
        }
        Object returnObj=proxy.invokeSuper(obj,args);
        if(method.isAnnotationPresent(LogAnnotation.class)){
            System.out.println(className+"."+method.getName()+"方法结束执行");

        }

        return returnObj;
    }
}

obj是想生成动态代理对象的原对象。再构造器中加上className参数,支持用字符串反射构建obj对象。

需要实现MethodInterceptor接口,实现intercept方法。

buildProxyObject方法是通过obj原对象构建动态代理对象的方法,并将构建好的代理对象返回。

intercept是动态代理对象的拦截方法,再调用动态代理对象的方法都会该调用该方法,第一个参数是原对象,第二个参数是调用的方法(原对象的方法),第三个参数是调用的参数数组,第四个参数是调用的动态代理方法。咱需要再该方法中加上开始调用的日志和结束的日志。用method.isAnnotationPresent()去判断是该方法否存在LogAnnotation注解(要用method不要用proxy,否则读取不到注解)。如果存在该注解。则再方法调用前后输出日志。 Object returnObj=proxy.invokeSuper(obj,args);是调用代理对象的方法,然后得到返回值,一定要注意这里用proxy不要用method,如果用mehod则又会进入该拦截方法,然后无限递归报错。

好了,动态代理解决了,AOP也就实现了。然后咱开始实现IOC。第一步是需要扫描包里所有的类,第二部是利用反射去构建类中所有带@Bean注解的类对象,然后再循环遍历所有的对象,找到带有@Resource的属性。然后将相同类型的对象set到属性里。

public class MyContainer {
    private List<Object> objectList = new ArrayList<>();

    public MyContainer(String packageName) throws Exception {
        List<String>classPaths= new ArrayList<String>();
        //先把包名转换为路径,首先得到项目的classpath
        String classpath = this.getClass().getResource("/").getPath();
        //然后把我们的包名basPach转换为路径名
        packageName = packageName.replace(".", File.separator);
        //然后把classpath和basePack合并
        String searchPath = classpath + packageName;
        doPath(new File(searchPath),classPaths);
        //这个时候我们已经得到了指定包下所有的类的绝对路径了。我们现在利用这些绝对路径和java的反射机制得到他们的类对象
        List<String>classPaths1= new ArrayList<String>();
        for (String s : classPaths) {
            //把 D:\work\code\20170401\search-class\target\classes\com\baibin\search\a\A.class 这样的绝对路径转换为全类名com.baibin.search.a.A
            s = s.replace(classpath.replace("/","\\").replaceFirst("\\\\",""),"").replace("\\",".").replace(".class","");
            if(Class.forName(s).isAnnotationPresent(BeanAnnoation.class)){
                objectList.add(new CGlibProxy(s).buildProxyObject());
            }

        }
        objectList.stream().forEach(e->{
            try {
                ioc(e);
            } catch (Exception ex) {
                throw new RuntimeException(ex);
            }
        });


        //System.out.println(classPaths1.size());

    }
    private  void ioc(Object o) throws Exception{
        Class c = Class.forName(o.getClass().getName().split("\\$")[0]);
        for(Field field:c.getDeclaredFields()){

            if(field.isAnnotationPresent(Resource.class)){
                field.setAccessible(true);
                field.set(o,getObjectByClassName(field.getType().getName()));
            }
        }

    }
    public  Object getObjectByClassName(String className){
        for(Object o:objectList){
            if(o.getClass().getName().split("\\$")[0].equals(className)){
                return o;
            }

        }
        return null;

    }
    private static void doPath(File file, List<String>classPaths) {
        if (file.isDirectory()) {//文件夹
            //文件夹我们就递归
            File[] files = file.listFiles();
            for (File f1 : files) {
                doPath(f1,classPaths);
            }
        } else {//标准文件
            //标准文件我们就判断是否是class文件
            if (file.getName().endsWith(".class")) {
                //如果是class文件我们就放入我们的集合中。
                classPaths.add(file.getPath());
            }
        }

    }

    public List<Object> getObjectList() {
        return objectList;
    }

    public void setObjectList(List<Object> objectList) {
        this.objectList = objectList;
    }
}

编写一个MyContainer去实现所有对象的IOC

再MyContainer的构造器中加入包名参数,在构造器里去实现对包的扫描和objectList的创建。

构建完objectList再遍历objectList去调用ioc,寻找Resource注解的属性,然后用反射将相同类型的object set到属性里。

好了,现在AOP和IOC已经实现了。然后写个controller和service和dao去测试吧。

 


@BeanAnnoation
public class TestService {
    @LogAnnotation
    public void test(){
        System.out.println("执行service过程");
        testDao.test();
    }
    @Resource
    private TestDao testDao;

}


@BeanAnnoation
public class TestDao {
    @LogAnnotation
    public void test(){
        System.out.println("数据库执行");
    }
}

@BeanAnnoation
public class TestController {

    @Resource
    private TestService testService;

    public void test(){
        testService.test();
    }
/**
执行结果:
    aop.test.TestService.test方法开始执行
    执行service过程
    aop.test.TestDao.test方法开始执行
    数据库执行
    aop.test.TestDao.test方法结束执行
    aop.test.TestService.test方法结束执行
*/
    public static void main(String[] args) throws Exception {
       String packagemName=TestController.class.getPackage().getName();
        MyContainer myContainer =new MyContainer(packagemName);
        TestController testController =(TestController)myContainer.getObjectByClassName(TestController.class.getName());
        testController.test();
    }

}

再三个类上加入@BeanAnnoation告诉容器这三个类需要被反射构建,@Resource告诉容器该属性需要被set,@LogAnnotation告诉生成的代理对象该方法需要打印日志。

main函数中再TestController的test里调用TestService的test,然后TestService的test在调用TestDao的test。main函数的执行结果已经在注释里写好。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值