文档章节

使用javassist实现简单的AOP

 微默
发布于 2015/08/18 17:33
字数 1756
阅读 95
收藏 0

说起aop,大家都很熟悉。

①.Aop是什么呢?

通常来讲就是Aspect Oriented Programming的缩写,翻译成中文就是面向切面编程。

②.Aop能做什么呢?

我觉得,AOP能做的东西很多,最重要的就是在不动老结构的情况下增强原有的内容,或者是批量的对不同的功能进行统一加强。

当然啦,这不在今天的问题范围内。

 

在这里,我们只探讨如何使用javassist,完成简单的基于注解的AOP与依赖注入。

 

GOAL

1.通过@Component 注解结合包路径扫描装载bean,并暂只支持一种单例生命周期。

3.通过@AopBefore @AopAfter 两个注解 标注前置增强与后置增强。

2.通过@Autowired 注解(仅字段)声明根据名称的自动装配。

 

为了让我们项目里的可爱的对象们都找到喜欢的小伙伴,所以我们非常有必要创建一个婚姻介绍所 Context !而本猿则化身正义的婚姻介绍所所长!代表月亮撮合你们!哼哼哼~

第一步,收集资料!


嗯哼,让我们来收集第一大街的单身狗资料吧。

Context类:

    private final HashMap<String, ClassInfo> classMap = new HashMap<>();
    private final HashMap<String, Object> singletonMap = new HashMap<>();

首先我向伟大的jvm申请领取两个免费的档案夹,一个classMap用于存放单身狗的投递资料。另外一个singletonMap用来存放单例生命周期的单身狗的真身。

然后,我到黄勇大哥的商店偷了一把全自动高科技地毯式对象资料搜索神器(没错,就是这里)。有此神器,本猿定能所向睥睨,把所有对象的底摸得不要不要的。

就是这么无耻的把包名都改了,哼哼哼。

Context类:

static ClassScanner scanner = new DefaultClassScanner();


任性的扔到我的婚介所里。

接下来,本猿要出动了!

public void loadFromPackage(String packageName) {
        
        List<Class<?>> list = scanner.getClassListByAnnotation(packageName, Component.class);
        
        for (Class<?> c : list) {
            putBeansInfo(c);
        }
}

哼哼,不是所有的对象,本猿都接受的哦,脑门上没贴个死字儿,阎王爷也不会来找你,哦,说错了,脑门上没有@Component的,本猿懒得鸟你们。

Component注解:

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Component {
    String  value() default "";
    ContextType contextType() default ContextType.Singleton;
}


     生命周期类型:

public enum ContextType {
    Singleton;
}

   Context类里面添加一下,先简单打印一下:

private void putBeansInfo(Class<?> c) {
    System.out.println(c.getName());
}


 试试:

建立三个类:Wangnima.java Doubi.java Laowang.java 其中Doubi 和Laowang 加@Component,Wangnima不加,

简单粗暴的main函数测试:

public static void main(String[] args) {
        Context context=new Context();
        context.loadFromPackage("com.weimo.learn.aop.testobjects");
    }

结果:

接着干活:

private void putBeansInfo(Class<?> c) {//把类的信息存入两个map中
        Component component = c.getAnnotation(Component.class);
        String name = component.value();//获取bean名
        ContextType type = component.contextType();
        if (name.equals("")) {//默认值则把类名(不含包名)的第一个字母小写
            name = c.getSimpleName();
            Character fc = name.charAt(0);
            fc = (fc >= 'A' && fc <= 'Z') ? (char) (fc + 32) : fc;
            name = fc.toString() + name.substring(1);
        }
        ClassInfo ci = new ClassInfo();
        ci.setTheClass(c);
        ci.setType(type);
        classMap.put(name, ci);//存入类信息
        if (type == ContextType.Singleton) { //如果是声明单例,放入单例map中
            Object bean;
            try {
                bean = c.newInstance();//实例化
                singletonMap.put(name, bean);
            } catch (InstantiationException | IllegalAccessException ex) {
                Logger.getLogger(Context.class.getName()).log(Level.SEVERE, null, ex);
            }
            
        }
    }

 添加getBean方法。

public Object getBean(String name) {
        ClassInfo ci = classMap.get(name);
        ContextType type = ci.getType();
        if (type == ContextType.Singleton) {
            return singletonMap.get(name);
        }
        return null;
 }

    简单的测试代码:

 

Context context=new Context();
context.loadFromPackage("com.weimo.learn.aop.testobjects");
Doubi doubi=(Doubi) context.getBean("doubi");
System.out.println(doubi);

结果:

这样就实现了我们的第一个目标啦,简单吧?

第二步,头发的特技

这些单身狗对象们,可不是那么好伺候的,个个都是大爷,要求本猿的婚介所给打扮打扮,不然就要求退所!

面对这样无理的要求!!

我tmd竟然欣然接受了。

但是这群屌丝都已经成形了,咋改啊,要是一个一个改他们DNA(源代码),我还不得疯啊,而且这些屌丝一天一个想法, 万一哪天不想玩特技了咋整啊。

哼哼,这得用上俺的JAVA神功之动态代理大法。

动态代理就是动态的在运行时为目标类型创建代理类型。在这里我们将屌丝们声明的特技通过这种动态代理方式植入屌丝约会技能之中,从而在屌丝约会技能施法之前或之后执行。

我们的目标是:

将这样的类

通过@AopBefore 加上这样的特技

最后动态的创建Doubi的子类代理类,让在原方法调用之前先执行@AopBefore 指定类的特定方法。

最后让父类的变量指向代理类的对象。

好了,在Context里面创建getAopBean方法。

使用javassist,创建传入类的子类。

遍历传入类的所有声明方法,检查是否含有我们期望的含AopAfter或AopBefore的注解,将含有这两种注解之一的方法们,在新创建类型中重写,覆盖父类的方法。

    private Object getAopBean(Class c) {
        Random random = new Random();
        String className = c.getName();
        try {
            CtClass ctClass = cp.get(className);
            CtClass newClass = cp.makeClass(className + "_child" + random.nextInt(10));//创建新类型,起个名字
            newClass.setSuperclass(ctClass);设置新创建类型的父类为传入的类
            CtMethod[] ctMethods = ctClass.getDeclaredMethods();//获取所有父类声明的方法
            for (CtMethod ctMethod : ctMethods) {
                if (ctMethod.hasAnnotation(AopAfter.class) || ctMethod.hasAnnotation(AopBefore.class)) {
                    newMethod(ctMethod, newClass);//为新创建的类新建方法,代理父类对象的方法
                }
            }
            newClass.writeFile();//保存
            return newClass.toClass().newInstance();//
        } catch (NotFoundException | CannotCompileException | ClassNotFoundException | IOException | InstantiationException | IllegalAccessException ex) {
            Logger.getLogger(Context.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }

将putBeansInfo中,存放单例生命周期的bean从newInstance创建改为:bean = getAopBean(c);

这里是重写方法的实现:

private void newMethod(CtMethod ctMethod, CtClass newClass) throws NotFoundException, ClassNotFoundException, CannotCompileException {
        StringBuilder codeBody = new StringBuilder("{");
        String returnTypeName = ctMethod.getReturnType().getName();
        CtClass[] parameterTypes = ctMethod.getParameterTypes();
        boolean returnTypeNotVoid = !returnTypeName.equals("void");
        AopBefore before = (AopBefore) ctMethod.getAnnotation(AopBefore.class);
        if (before != null) {
            Class<? extends AopInterceptor>[] value = before.value();
            for (Class<? extends AopInterceptor> beforeDoClass : value) {
                writeBeforeCode(beforeDoClass, codeBody, returnTypeNotVoid, returnTypeName);
            }
        }
        writeTrueDoCode(ctMethod, codeBody, returnTypeNotVoid, parameterTypes);
        
        AopAfter after = (AopAfter) ctMethod.getAnnotation(AopAfter.class);
        if (after != null) {
            Class<? extends AopInterceptor>[] afterInterceotors = after.value();
            for (Class<? extends AopInterceptor> afterDoClass : afterInterceotors) {
                writeAfterCode(ctMethod, afterDoClass, codeBody, returnTypeNotVoid, returnTypeName);
            }
        }
        
        codeBody.append("}");
        CtMethod newMethod = CtNewMethod.make(ctMethod.getReturnType(), ctMethod.getName(), parameterTypes, ctMethod.getExceptionTypes(), codeBody.toString(), newClass);
        newClass.addMethod(newMethod);
        codeBody = null;

    }

下面是三部分代码编写的字符串拼接方法:

//关于前置的代码片段
private void writeBeforeCode(Class beforeDoClass,StringBuilder codeBody,boolean returnTypeNotVoid,String returnTypeName){
        String beforeDoClassName = beforeDoClass.getName();
                String beforeDoObjectName = beforeDoClass.getSimpleName().toLowerCase();
                codeBody.append(beforeDoClassName).append(" ").append(beforeDoObjectName).append("= new ").append(beforeDoClassName).append("();");
                codeBody.append(beforeDoObjectName).append(".invoke($args);");
                codeBody.append("if(").
                        append(beforeDoObjectName)
                        .append(".isReturned()){");
                if (returnTypeNotVoid) {
                    codeBody.append("return (").append(returnTypeName).append(")")
                            .append(beforeDoObjectName).append(".getReturnObject();");
                } else {
                    codeBody.append("return;");
                }
                codeBody.append("}");
    }
//调用父类方法执行实际要执行的内容
private void writeTrueDoCode(CtMethod ctMethod,StringBuilder codeBody,boolean returnTypeNotVoid,CtClass[] parameterTypes) throws NotFoundException{
            
        if (returnTypeNotVoid) {
            codeBody.append(ctMethod.getReturnType().getName())
                    .append(" result= ");
        }
        codeBody.append("super.")
                .append(ctMethod.getName())
                .append("(");

        int len = parameterTypes.length;
        for (int i = 0; i < len; i++) {
            codeBody.append("$").append(i + 1);
            if (i != len - 1) {
                codeBody.append(",");
            }
        }
        codeBody.append(");");
        }
//关于后置的代码片段
 private void writeAfterCode(CtMethod ctMethod,Class afterDoClass,StringBuilder codeBody,boolean returnTypeNotVoid,String returnTypeName){
        String afterDoClassName = afterDoClass.getName();
                String afterDoObjectName = afterDoClass.getSimpleName().toLowerCase();

                codeBody.append(afterDoClassName).append(" ").append(afterDoObjectName).append("=").append("new  ").append(afterDoClassName).append("();");
                if (returnTypeNotVoid) {
                    codeBody.append(afterDoObjectName).append(".setReturnObject(result);");
                }
                codeBody.append(afterDoObjectName).append(".setMethodName(\"").append(ctMethod.getName()).append("\");");
                codeBody.append(afterDoObjectName).append(".invoke($args);");
                if (returnTypeNotVoid) {
                    codeBody.append("return result;");
                }

    }

好了,写完了,我们来测试一下吧!

public static void main(String[] args) {
        Context context=new Context();
        context.loadFromPackage("com.weimo.learn.aop.testobjects");
        Doubi doubi=(Doubi) context.getBean("doubi");
        doubi.BeMyDate("美女主播");
}

结果:

第三步,相亲大会

next……

© 著作权归作者所有

共有 人打赏支持
粉丝 2
博文 4
码字总数 3007
作品 1
丰台
私信 提问
java字节码开源软件

asm:ASM 是一个 Java 字节码操纵框架。它可以直接以二进制形式动态地生成 stub 类或其他代理类,或者在装载时动态地修改类。ASM 提供类似于 BCEL 之类的工具包的功能,但是被设计得更小巧、更...

hc24
2016/06/13
107
0
Java动态编程初探——Javassist

最近需要通过配置生成代码,减少重复编码和维护成本。用到了一些动态的特性,和大家分享下心得。 我们常用到的动态特性主要是反射,在运行时查找对象属性、方法,修改作用域,通过方法名称调...

真爱2015
2016/07/25
71
0
Javassist

Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京技术学院的数学和计算机科学系的 Shigeru Chiba 所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist...

匿名
2008/09/24
13.3K
1
[转] Java Instrument 初探

java在1.5引入java.lang.instrument,你可以由此实现一个java agent,通过此agent来修改类的字节码即改变一个类。本文中,会通过java instrument 实现一个简单的profiler。当然instrument并不...

小编辑
2011/01/05
2.6K
1
Android AOP 实践笔记

本文同步自wing的地方酒馆 最近博客更新越来越慢了,有两方面原因: 1.没啥好写的。 2.应该沉下心好好沉淀自己,积累一些东西,博客写的太频繁有”刷博客“之嫌,还容易浮躁。 浮躁是大忌 ,...

wingichoy
2017/05/13
0
0

没有更多内容

加载失败,请刷新页面

加载更多

当S8遇上边缘计算:谈阿里云ENS对直播业务场景的支撑

摘要: 类似S8赛事这样的大型活动直播的特点和技术挑战是什么?为什么业务要下沉到边缘?自建边缘节点和与云服务厂商合作到底该如何选择?边缘节点服务(ENS)又是如何进行技术支撑?提供的针...

阿里云官方博客
7分钟前
1
0
supervisor安装配置

supervisor安装配置 安装 wget -c https://files.pythonhosted.org/packages/44/60/698e54b4a4a9b956b2d709b4b7b676119c833d811d53ee2500f1b5e96dc3/supervisor-3.3.4.tar.gztar -zxvf su......

jackmanwu
17分钟前
1
0
laravel定时器

民间高手: https://blog.csdn.net/zhezhebie/article/details/79205414 官方文档: https://laravel-china.org/docs/laravel/5.5/scheduling/1325...

vio小黑
40分钟前
0
0
Apache Zeppelin安装及使用

Apache Zeppelin官网:http://zeppelin.apache.org/ Apache Zeppelin介绍:A web-based notebook that enables interactive data analytics. You can make beautiful data-driven, interacti......

GordonNemo
41分钟前
4
0
关于python开发多个项目环境配置Anaconda

关于Anaconda用来管理Python的包和环境 下载并安装Anaconda 创建项目:windows键+R ==> 进入CMD => 输入conda create -n 项目名称 python=3 conda info -e 查看项目以及项目所在的位置,默认...

上官清偌
43分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部