代理

通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,扩展目标对象的功能。

代理对象拦截真实对象的方法调用,在真实对象调用前/后实现自己的逻辑调用

这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法。

动态代理的用途与装饰模式很相似,就是为了对某个对象进行增强。所有使用装饰者模式的案例都可以使用动态代理来替换。

/**
* subject(抽象主题角色):
* 真实主题与代理主题的共同接口。
*/
interface Subject {
    void sellBook();
} /**
* ReaISubject(真实主题角色):
* 定义了代理角色所代表的真实对象。
*/
public class RealSubject implements Subject {     @Override
    public void sellBook() {
        System.out.println("出版社卖书");
    }
} /**
* Proxy(代理主题角色):
* 含有对真实主题角色的引用,代理角色通常在将客户端调用传递给真实主题对象之前或者之后执行某些操 作,而不是单纯返回真实的对象。
*/ public class ProxySubject implements Subject {     private RealSubject realSubject;     @Override
    public void sellBook() {
        if (realSubject == null) {
            realSubject = new RealSubject();
        }
        sale();
        realSubject.sellBook();
        give();
    }     public void sale() {
        System.out.println("打折");
    }     public void give() {
        System.out.println("送优惠券");
    }
} public class Main {     public static void main(String[] args) {         //静态代理(我们自己静态定义的代理类)
        ProxySubject proxySubject = new ProxySubject();
        proxySubject.sellBook();         //动态代理(通过程序动态生成代理类,该代理类不是我们自己定义的。而是由程序自动生成)
        RealSubject realSubject = new RealSubject();
        MyHandler myHandler = new MyHandler();
        myHandler.setProxySubject(realSubject);
        Subject subject = (Subject)
Proxy.newProxyInstance(realSubject.getClass().getClassLoader(),
         realSubject.getClass().getInterfaces(), myHandler);
        subject.sellBook();
    }
} public class MyHandler implements InvocationHandler {
    private RealSubject realSubject;     public void setProxySubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }     /**
     * @param proxy   指代我们所代理的那个真实对象
     * @param method   指代的是我们所要调用真实对象的某个方法的Method对象
     * @param args    指代的是调用真实对象某个方法时接受的参数
     * @reurn
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        sale();
        proxy = method.invoke(realSubject, args);
        give();
        return proxy;
    }     public void sale() {
        System.out.println("打折");
    }     public void give() {
        System.out.println("送优惠券");
    }
}

HOOK

Hook(钩子): Android 操作系统中系统维护着自己的一套事件分发机制,那么Hook就是在事件传送到终点前截获并监控事件的传输,并修改事件流程的过程。

    public static void showToast(Context context, CharSequence cs, int length) {
Toast toast = Toast.makeText(context,cs,length);
hook(toast);
toast.show();
}

Hook 的选择点:

静态变量和单例,因为一旦创建对象,它们不容易变化,非常容易定位。

Hook 过程:

寻找 Hook 点,原则是静态变量或者单例对象,尽量 Hook public 的对象和方法。

选择合适的代理方式,如果是接口可以用动态代理。

偷梁换柱——用代理对象替换原始对象。

Android 的 API 版本比较多,方法和类可能不一样,所以要做好 API 的兼容工作

应用

  • Hook指定应用注入广告
  • 修复bug
  • App登录劫持

登录界面上面的用户信息都存储在EditText控件上,然后通过用户手动点击“登录”按钮才会将上面的信息发送至服务器端去验证账号与密码是否正确。这样就很简单了,黑客们只需要找到开发者在使用EditText控件的getText方法后进行网络验证的方法,Hook该方法,就能劫持到用户的账户与密码了

hook练习


AOP(AspectJ)

正如面向对象编程是对常见问题的模块化一样,面向切面编程是对横向的同一问题进行模块化,比如在某个包下的所有类中的某一类方法中都需要解决一个相似的问题,可以通过AOP的编程方式对此进行模块化封装,统一解决

关于AOP的具体解释,可以参照维基百科。而AspectJ就是面向切面编程在Java中的一种具体实现。

Join point:程序中执行代码插入的点,例如方法调用时或者方法执行时。

AOP编程的具体使用场景

日志记录

持久化

行为监测

数据验证

缓存

...

比如埋点,记录方法执行的时长。可以定义注解。aop可以过滤所有被"这个注解"标记的方法和构造器。然后可以可以根据他提供的方法(注解),讲我们想要埋点的日志插入进去。

注解和反射的区别

反射:对于任何一个对象,都能够调用它的任何一个方法和属性,包括私有的。这种动态获取的方法就叫反射。

注解:降低项目的耦合度;自动完成一些规律性的代码;自动生成java代码,减轻开发者的工作量。

而注解需要用到反射:

定义注解,使用注解,读取注解(用到反射)

Annotation Processing Tool(APT),注解处理器,javac中用于编译时扫描和解析Java注解的工具。

在编译阶段执行的,它的原理就是读入Java源代码,解析注解,然后生成新的Java代码。新生成的Java代码最后被编译成Java字节码,注解解析器不能改变读入的Java 类,比如不能加入或删除Java方法

//绑定一个View (View不能为private 或者static)否则就会通过反射去获取
@BindView(R.id.textview)

注解和反射效率问题

hook和aop类似

反射先new类class,然后在从类里面new对象。Class.getMethod(...)还要查找所有的方法。

而注解编译期间就完成了注解的反射工作, jvm只是读取。

反射的缺点

不安全

编译器没法对反射相关的代码做优化

慢的原因还有安全检查,访问控制等。比如说这个方法能不能获得,能不能执行等,传进的参数的类型检查等。

注解的用法

获取类的注解

URLBuilder.Path path = paramEntity.getClass().getAnnotation(URLBuilder.Path.class);

    Builder(Retrofit retrofit, Method method) {
this.retrofit = retrofit;
this.method = method;
this.methodAnnotations = method.getAnnotations();
this.parameterTypes = method.getGenericParameterTypes();
this.parameterAnnotationsArray = method.getParameterAnnotations();
}

获取方法的注解

Method.getAnnotations(); 是获取方法上面对应的注解。method.getGenericParameterTypes();获取的是方法参数的类型,里面带有实际的参数类型。

method.getParameterAnnotations();获取的是方法参数上面的注解,是一个二维数组,第一个维度代表的是方法参数对应的下标,比如,一个方法有3个参数,那0代表第一个参数,1代表第二个参数,2代表第三个参数。

@Retention:注解保留的生命周期

@Target:注解对象的作用范围。

创建一个注解遵循: public @interface 注解名 {方法参数}


@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface getViewTo {
int value() default -1;
} public class MainActivity extends AppCompatActivity { @getViewTo(R.id.textview)
private TextView mTv; @getViewTo(R.id.button)
private Button mBtn; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); //通过注解生成View;
getAllAnnotationView();
} /**
* 解析注解,获取控件
*/
private void getAllAnnotationView() {
//获得成员变量
Field[] fields = this.getClass().getDeclaredFields(); for (Field field : fields) {
try {
//判断注解
if (field.getAnnotations() != null) {
//确定注解类型
if (field.isAnnotationPresent(GetViewTo.class)) {
//允许修改反射属性
field.setAccessible(true);
GetViewTo getViewTo = field.getAnnotation(GetViewTo.class);
//findViewById将注解的id,找到View注入成员变量中
field.set(this, findViewById(getViewTo.value()));
}
}
} catch (Exception e) {
}
}
}
}

反射机制

JAVA反射机制是在运行状态中,对于任意一个类 (class文件),都能够知道这个类的所有属性和方法;

对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。 

动态获取类中信息,就是java反射 。可以理解为对类的解剖。

Person

public class Person {

	private int age;
private String name; public Person(String name,int age) {
super();
this.age = age;
this.name = name; System.out.println("Person param run..."+this.name+":"+this.age); }
public Person() {
super(); System.out.println("person run"); } public void show(){
System.out.println(name+"...show run..."+age);
} private void privateMethod(){
System.out.println(" method run ");
} public void paramMethod(String str,int num){
System.out.println("paramMethod run....."+str+":"+num); }
public static void staticMethod(){
System.out.println(" static method run......");
}
}

要想要对字节码文件进行解剖,必须要有字节码文件对象.

如何获取字节码文件对象呢?

获取Class对象的三种方式

public class ReflectDemo {

	/**
* @param args
* @throws ClassNotFoundException
*/
public static void main(String[] args) throws ClassNotFoundException {
getClassObject_3();
} /*
* 获取字节码对象的方式:
* Object类中的getClass()方法的。
* 想要用这种方式,必须要明确具体的类,并创建对象。
* 麻烦 .
*
*/
public static void getClassObject_1(){ Person p = new Person();
Class clazz = p.getClass(); Person p1 = new Person();
Class clazz1 = p1.getClass(); System.out.println(clazz==clazz1);
} /*
* 方式二:
* 任何数据类型都具备一个静态的属性.class来获取其对应的Class对象。
* 相对简单,但是还是要明确用到类中的静态成员。
* 还是不够扩展。
*
*/
public static void getClassObject_2() { Class clazz = Person.class; Class clazz1 = Person.class;
System.out.println(clazz==clazz1);
} /*
* 方式三:
* 只要通过给定的类的 字符串名称就可以获取该类,更为扩展。
* 可是用Class类中的方法完成。
* 该方法就是forName.
* 这种方式只要有名称即可,更为方便,扩展性更强。
*/
public static void getClassObject_3() throws ClassNotFoundException { String className = "cn.test.bean.Person"; Class clazz = Class.forName(className); System.out.println(clazz);
} }

获取Class中的构造函数

public class ReflectDemo2 {

	/**
* @param args
* @throws Exception
* @throws InstantiationException
* @throws ClassNotFoundException
*/
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, Exception {
createNewObject_2();
} public static void createNewObject_2() throws Exception { // cn.test.bean.Person p = new cn.test.bean.Person("小强",39); /*
* 当获取指定名称对应类中的所体现的对象时,
* 而该对象初始化不使用空参数构造该怎么办呢?
* 既然是通过指定的构造 函数进行对象的初始化,
* 所以应该先获取到该构造函数。 通过字节码文件对象即可完成。
* 该方法是:getConstructor(paramterTypes);
*
*/
String name = "cn.test.bean.Person";
//找寻该名称类文件,并加载进内存,并产生Class对象。
Class clazz = Class.forName(name);
//获取到了指定的构造函数对 象。
Constructor constructor = clazz.getConstructor(String.class,int.class); //通过该构造器对象的newInstance方法进行对象的初始化。
Object obj = constructor.newInstance("小明",38); } public static void createNewObject() throws ClassNotFoundException, InstantiationException, IllegalAccessException{ //早期:new时候,先根据被new的类的名称找寻该类的字节码文件,并加载进内存,
// 并创建该字节码文件对象,并接着创建该字节文件的对应的Person对象.
// cn.test.bean.Person p = new cn.test.bean.Person(); //现在:
String name = "cn.test.bean.Person";
//找寻该名称类文件,并加载进内存,并产生Class对象。
Class clazz = Class.forName(name);
//如何产生该类的对象呢?
Object obj = clazz.newInstance();
}
}

获取Class中的字段

	/*
* 获取字节码文件中的字段。
*/
public static void getFieldDemo() throws Exception { Class clazz = Class.forName("cn.test.bean.Person");
Field field = null;//clazz.getField("age");//只能获取公有的,
field = clazz.getDeclaredField("age");//只获取本类,但包含私有。
//对私有字段的访问取消权限检查。暴力访问。
field.setAccessible(true);
Object obj = clazz.newInstance();
field.set(obj, 89); Object o = field.get(obj);
System.out.println(o);
// cn.test.bean.Person p = new cn.test.bean.Person();
// p.age = 30; }

获取Class中的方法

	public static void getMethodDemo_3() throws Exception {
Class clazz = Class.forName("cn.test.bean.Person");
Method method = clazz.getMethod("paramMethod", String.class,int.class);
Object obj = clazz.newInstance();
method.invoke(obj, "小强",89); } public static void getMethodDemo_2() throws Exception { Class clazz = Class.forName("cn.test.bean.Person"); Method method = clazz.getMethod("show", null);//获取空参数一般方法。 // Object obj = clazz.newInstance();
Constructor constructor = clazz.getConstructor(String.class,int.class);
Object obj = constructor.newInstance("小明",37); method.invoke(obj, null); } /*
* 获取指定Class中的所有公共函数。
*/
public static void getMethodDemo() throws Exception { Class clazz = Class.forName("cn.test.bean.Person"); Method[] methods = clazz.getMethods();//获取的都是公有的方法。
methods = clazz.getDeclaredMethods();//只获取本类中所有方法,包含私有。
for(Method method : methods){
System.out.println(method);
}
}

如何获得泛型类的真实类型

通过Class类上的 getGenericSuperclass() 或者 getGenericInterfaces() 获取父类或者接口的类型,然后通过ParameterizedType.getActualTypeArguments()

public class RealType<T>{

	private Class<T> clazz;
// 使用反射技术得到T的真实类型
public Class getRealType(){
// 获取当前new的对象的泛型的父类类型
ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
// 获取第一个类型参数的真实类型
this.clazz = (Class<T>) pt.getActualTypeArguments()[0];
return clazz;
} }

代理、反射、注解、hook的更多相关文章

  1. 【Java EE 学习 24 下】【注解在数据库开发中的使用】【反射+注解+动态代理在事务中的应用service层】

    一.使用注解可以解决JavaBean和数据库中表名不一致.字段名不一致.字段数量不一致的问题. 1.Sun公司给jdbc提供的注解 @Table.@Column.@Id.@OneToMany.@One ...

  2. 为bookStore添加权限【动态代理和注解】

    前言 目前为止,我们已经学习了动态代理技术和注解技术了.于是我们想要为之前的bookStore项目添加权限控制-.. 只有用户有权限的时候,后台管理才可以进行相对应的操作-.. 实现思路 之前我们做权 ...

  3. 【译】8. Java反射——注解

    原文地址:http://tutorials.jenkov.com/java-reflection/annotations.html ================================== ...

  4. Javaweb学习笔记——(二十七)——————泛型、泛型的通配符、反射泛型信息、反射注解、注解

    泛型     1.泛型类:具有一个或多个类型变量的类,称之为泛型类 class A<T>{ } 2.在创建泛型实例时,需要为其类型变量赋值 A<String> a = new ...

  5. cglib动态代理导致注解丢失问题及如何修改注解允许被继承

    现象 SOAService这个bean先后经过两个BeanPostProcessor,会发现代理之后注解就丢失了. 开启了cglib代理 @SpringBootApplication @EnableA ...

  6. 杨晓峰-Java核心技术-6 动态代理 反射 MD

    目录 第6讲 | 动态代理是基于什么原理? 典型回答 考点分析 知识扩展 反射机制及其演进 动态代理 精选留言 Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAnd ...

  7. JAVA-注解(2)-自定义注解及反射注解

    自定义注解开发 1.开发一个注解类 开发一个注解类的过程,非常类似于开发一个接口,只不过需要通过@interface关键字来声明 2.使用元注解修饰注解的声明 所谓的原注解是用来修饰注解声明的注释,可 ...

  8. Java通过反射注解赋值

    前段时间,领导分配一个统计销售区域汇总的数据,解决方案使用到了反射获取注解,通过注解获取属性或者设置字段属性. 问题描述 查询公司列表,分别是公司id.区域id.区域名称: 公司id 区域id 区域名 ...

  9. Java的反射和代理以及注解

    最近接触到java的反射和代理(接触的有点迟了...),还是有必要总结下 1. Java的反射 有的时候我们需要在程序运行的时候获取类.方法等信息用于动态运行,这个时候反射就能够帮我们找到类.方法.成 ...

  10. JDK动态代理+反射实现动态修改注解属性值

    这是最近朋友的一个需求,正好闲来无聊有些时间,跟着研究一下,如有不正确的地方,欢迎大家指正~ 一.准备自定义注解 注:如何实现自定义注解,请移步百度. 二.实现 1.实现方式1:通过反射+动态代理动态 ...

随机推荐

  1. Go语言编程中字符串切割方法小结

    1.func Fields(s string) []string,这个函数的作用是按照1:n个空格来分割字符串最后返回的是[]string的切片 复制代码代码如下: import ( "fm ...

  2. java TimeUnit 的使用

    主要作用 时间颗粒度转换 延时 1.时间颗粒度转换 public long toMillis(long d) //转化成毫秒 public long toSeconds(long d) //转化成秒 ...

  3. JVM----双亲委派模型

    加载类的开放性 我们在了解双亲委派模型之前,不得不先了解一下什么是类加载器.虚拟机设计团队之初是希望类加载过程“通过一个类的全限定名来获取描述该类的二进制字节流”这个动作能放到虚拟机外部实现,以便于让 ...

  4. linux环境下使用jmeter进行压力测试

    linux环境下使用jmeter进行压力测试 linux环境下使用就meter进行压力测试: linux环境部署: 在Linux服务器先安装jdk: 2.以jdk-8u172-linux-x64.ta ...

  5. git push and git pull

    原文链接 git push 通常对于一个本地的新建分支,例如git checkout -b develop, 在develop分支commit了代码之后,如果直接执行git push命令,develo ...

  6. git与github建立仓库连接步骤(纯小白教程)

    一.先对git 进行用户设置 首先你得在网上下载git软件并且安装,一路默认安装就好了,然后就可以开始本地仓库的建立了.打开你安装好的git, 在开始菜单里面找到git文件夹里面的git bash端 ...

  7. Kafka 概述

    Kafka 是一个分布式的基于发布/订阅模式的消息队列(Message Queue),主要应用于大数据实时处理领域. Kafka 中,客户端和服务器之间的通信是通过 TCP 协议完成的. 一.传统消息 ...

  8. 提高组刷题班 DAY 1 上午

    低仿机器人(robo,1s,64M) 题解 大模拟 代码 #include <cstdio> #include <cstring> #include <iostream& ...

  9. 用key管理可复用元素

    先看看不用key管理可复用元素的代码.Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染.这么做,除了使 Vue 变得非常快之外,还有一些有用的好处,那就是在切换input时不会清楚 ...

  10. Hibernate3核心API简介-Transaction接口

    代表一次原子操作,它具有数据库事务的概念.所有持久层都应该在事务管理下进行,即使是只读操作.    Transaction tx = session.beginTransaction();常用方法:c ...