使用javassist实现简单的AOP

说起aop,大家都很熟悉。

①.Aop是什么呢?

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

②.Aop能做什么呢?

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

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

 

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

 

GOAL

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

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

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

 

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

第一步,收集资料!

145435_SQ6I_2438066.jpg

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

Context类:

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

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

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

150237_RH0W_2438066.png

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

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");
    }

结果:

153841_lHT3_2438066.png

接着干活:

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);

结果:

160653_Unys_2438066.png

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

第二步,头发的特技

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

面对这样无理的要求!!

我tmd竟然欣然接受了。

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

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

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

我们的目标是:

将这样的类

170610_6ZLu_2438066.png

通过@AopBefore 加上这样的特技

170610_4mWM_2438066.png

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

170612_6SJM_2438066.png

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

好了,在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("美女主播");
}

结果:

172830_91AJ_2438066.png

第三步,相亲大会

next……

转载于:https://my.oschina.net/weimo/blog/494102

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
高级使用Javassist包括一些高级功能,如修改已有的类、动态生成类、修改类的字节码等。这些功能可以实现一些有趣和复杂的场景。 首先,我们可以使用Javassist来修改已有的类。这可以用于添加、删除或修改类的字段、方法和构造函数。通过获取类的CtClass对象,我们可以使用Javassist提供的API对其进行操作,然后将修改后的字节码重新写回class文件。这在实现日志切面、权限切面等功能时非常有用。 其次,Javassist还可以用于动态生成类。通过调用ClassLoader的defineClass方法,我们可以将由Javassist生成的字节码转换为Class对象,并在运行时加载。这样,我们可以在程序运行时创建新的类,而不需要提前定义它们。这在某些情况下非常有用,比如动态代理、动态生成工厂类等场景。 最后,Javassist还提供了修改类的字节码的能力。通过获取类的CtClass对象,我们可以使用Javassist提供的API对其进行字节码操作,比如修改方法的实现、添加方法的拦截器等。这在实现一些高级的AOP功能时非常有用。 总结来说,Javassist是一个强大的字节码操作工具,可以用于修改已有的类、动态生成类和修改类的字节码。它在实现一些高级的功能时非常有用,比如日志切面、权限切面、动态代理等。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [javassist使用指南](https://blog.csdn.net/mChenys/article/details/122901039)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值