<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Javassist &#8211; ChaBug安全</title>
	<atom:link href="/tags/javassist/feed" rel="self" type="application/rss+xml" />
	<link>/</link>
	<description>一个分享知识、结识伙伴、资源共享的博客</description>
	<lastBuildDate>Mon, 20 Apr 2020 04:43:34 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=5.5.5</generator>
	<item>
		<title>Javassist 学习</title>
		<link>/audit/1511.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Mon, 20 Apr 2020 04:43:34 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[Javassist]]></category>
		<category><![CDATA[字节码]]></category>
		<guid isPermaLink="false">/?p=1511</guid>

					<description><![CDATA[前言 Java中所有的类都被编译为class文件来运行，在编译完class文件之后，类不能再被显示修改，而Javassist就是用来处理编译后的class文件，它可以用来修改方法或...]]></description>
										<content:encoded><![CDATA[<h2>前言</h2>
<p>Java中所有的类都被编译为class文件来运行，在编译完class文件之后，类不能再被显示修改，而<code><span class="wpcom_tag_link"><a href="/tags/javassist" title="Javassist" target="_blank">Javassist</a></span></code>就是用来处理编译后的class文件，它可以用来修改方法或者新增方法，并且不需要深入了解<span class="wpcom_tag_link"><a href="/tags/%e5%ad%97%e8%8a%82%e7%a0%81" title="字节码" target="_blank">字节码</a></span>，还可以生成一个新的类对象。</p>
<h2>创建class</h2>
<p>创建maven项目，引入Javassist库</p>
<pre data-language=XML><code class="language-markup ">&lt;!-- https://mvnrepository.com/artifact/javassist/javassist --&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;javassist&lt;/groupId&gt;
            &lt;artifactId&gt;javassist&lt;/artifactId&gt;
            &lt;version&gt;3.12.1.GA&lt;/version&gt;
        &lt;/dependency&gt;
</code></pre>
<p>使用<span class="wpcom_tag_link"><a href="/tags/java" title="java" target="_blank">java</a></span>ssist来创建一个Person类</p>
<pre><code class="language-java ">package com.y4er.learn;

import javassist.*;


public class CreateClass {
    public static void main(String[] args) throws Exception {
        // 获取javassist维护的类池
        ClassPool pool = ClassPool.getDefault();

        // 创建一个空类com.y4er.learn.Person
        CtClass ctClass = pool.makeClass("com.y4er.learn.Person");

        // 给ctClass类添加一个string类型的字段为name
        CtField name = new CtField(pool.get("java.lang.String"), "name", ctClass);

        // 设置private权限
        name.setModifiers(Modifier.PRIVATE);

        // 初始化name字段为zhangsan
        ctClass.addField(name, CtField.Initializer.constant("zhangsan"));

        // 生成get、set方法
        ctClass.addMethod(CtNewMethod.getter("getName",name));
        ctClass.addMethod(CtNewMethod.setter("setName",name));

        // 添加无参构造函数
        CtConstructor ctConstructor = new CtConstructor(new CtClass[]{}, ctClass);
        ctConstructor.setBody("{name="xiaoming";}");
        ctClass.addConstructor(ctConstructor);

        // 添加有参构造
        CtConstructor ctConstructor1 = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, ctClass);
        ctConstructor1.setBody("{$0.name=$1;}");
        ctClass.addConstructor(ctConstructor1);

        // 创建一个public方法printName() 无参无返回值
        CtMethod printName = new CtMethod(CtClass.voidType, "printName", new CtClass[]{}, ctClass);
        printName.setModifiers(Modifier.PUBLIC);
        printName.setBody("{System.out.println($0.name);}");
        ctClass.addMethod(printName);

        // 写入class文件
        ctClass.writeFile();
        ctClass.detach();
    }
}
</code></pre>
<p>执行完之后生成了Person.class<br />
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/bd087a27-b715-166e-6d57-531c662738af.png" alt="image.png" /></p>
<h2>使用方法</h2>
<p>从上文的demo中可以看到部分使用方法，在javassist中CtClass代表的就是类class，ClassPool就是CtClass的容器，ClassPool维护了所有创建的CtClass对象，需要注意的是当CtClass数量过大会占用大量内存，需要调用CtClass.detach()释放内存。</p>
<p>ClassPool重点有以下几个方法：<br />
1. getDefault() 单例获取ClassPool<br />
2. appendClassPath() 将目录添加到ClassPath<br />
3. insertClassPath() 在ClassPath插入jar<br />
4. get() 根据名称获取CtClass对象<br />
5. toClass() 将CtClass转为Class 一旦被转换则不能修改<br />
6. makeClass() 创建新的类或接口</p>
<p>更多移步官方文档：http://www.javassist.org/html/javassist/ClassPool.html</p>
<p>CtClass需要关注的方法：<br />
1. addConstructor() 添加构造函数<br />
2. addField() 添加字段<br />
3. addInterface() 添加接口<br />
4. addMethod​() 添加方法<br />
5. freeze() 冻结类使其不能被修改<br />
6. defrost() 解冻使其能被修改<br />
7. detach() 从ClassPool中删除类<br />
8. toBytecode() 转字节码<br />
9. toClass() 转Class对象<br />
10. writeFile() 写入.class文件<br />
11. setModifiers​() 设置修饰符</p>
<p>移步：http://www.javassist.org/html/javassist/CtClass.html</p>
<p>CtMethod继承CtBehavior，需要关注的方法：<br />
1. insertBefore 在方法的起始位置插入代码<br />
2. insterAfter 在方法的所有 return 语句前插入代码<br />
3. insertAt 在指定的位置插入代码<br />
4. setBody 将方法的内容设置为要写入的代码，当方法被 abstract修饰时，该修饰符被移除<br />
5. make 创建一个新的方法</p>
<p>更多移步：http://www.javassist.org/html/javassist/CtBehavior.html</p>
<p>在setBody()中我们使用了<code>$</code>符号代表参数</p>
<pre><code class="language-java ">// $0代表this $1代表第一个传入的参数 类推
printName.setBody("{System.out.println($0.name);}");
</code></pre>
<h2>使用CtClass生成对象</h2>
<p>上文我们生成了一个ctClass对象对应的是Person.class，怎么调用Person类生成对象、调用属性或方法？</p>
<p>三种方法：<br />
1. 反射方式调用<br />
2. 加载class文件<br />
3. 通过接口</p>
<h3>反射调用</h3>
<pre><code class="language-java ">// 实例化
Object o = ctClass.toClass().newInstance();
Method setName = o.getClass().getMethod("setName", String.class);
setName.invoke(o,"Y4er");
Method printName1 = o.getClass().getMethod("printName");
printName1.invoke(o);
</code></pre>
<h3>加载class文件</h3>
<pre><code class="language-java ">ClassPool pool = ClassPool.getDefault();
pool.appendClassPath("E:\code\java\javassist-learn\com\y4er\learn");
CtClass PersonClass = pool.get("com.y4er.learn.Person");
Object o = PersonClass.toClass().newInstance();
//接下来反射调用
</code></pre>
<h3>通过接口调用</h3>
<p>新建一个接口IPerson，将Person类的方法全部抽象出来</p>
<pre><code class="language-java ">package com.y4er.learn;

public interface IPerson {
    String getName();

    void setName(String name);

    void printName();
}
</code></pre>
<pre><code class="language-java ">ClassPool pool = ClassPool.getDefault();
pool.appendClassPath("E:\code\java\javassist-learn\com\y4er\learn\Person.class");

CtClass IPerson = pool.get("com.y4er.learn.IPerson");
CtClass Person = pool.get("com.y4er.learn.Person");
Person.defrost();
Person.setInterfaces(new CtClass[]{IPerson});

IPerson o = (IPerson) Person.toClass().newInstance();
o.setName("aaa");
System.out.println(o.getName());
o.printName();
</code></pre>
<p>将Person类实现IPerson接口，然后创建实例时直接强转类型，就可以直接调用了。</p>
<h2>修改现有的类</h2>
<p>javassist大多数情况下用户修改已有的类，比如常见的日志切面。我仍然使用Person类来讲解：</p>
<pre><code class="language-java ">//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.y4er.learn;

public class Person implements IPerson {
    private String name = "zhangsan";

    public String getName() {
        return this.name;
    }

    public void setName(String var1) {
        this.name = var1;
    }

    public Person() {
        this.name = "xiaoming";
    }

    public Person(String var1) {
        this.name = var1;
    }

    public void printName() {
        System.out.println(this.name);
    }
}

</code></pre>
<p>此时我想在printName方法的执行效果如下</p>
<pre><code class="language-text ">------ printName start ------
xiaoming
------ printName  over ------
</code></pre>
<p>写一下代码：</p>
<pre><code class="language-java ">pool.appendClassPath("E:\code\java\javassist-learn\com\y4er\learn\Person.class");
CtClass Person = pool.get("com.y4er.learn.Person");
Person.defrost();

CtMethod printName1 = Person.getDeclaredMethod("printName", null);
printName1.insertBefore("System.out.println("------ printName start ------");");
printName1.insertAfter("System.out.println("------ printName  over ------");");

Object o = Person.toClass().newInstance();
Method printName2 = o.getClass().getMethod("printName");
printName2.invoke(o, null);
</code></pre>
<p>很轻松实现了切面<br />
<img src="/wp-content/uploads/2020/04/de0e623b-7f11-77a1-0cb8-f38c2c76c5ea.png" alt="image.png" /></p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
