偏门知识点

偏门知识点

Java相关

1、@CallerSensitive注解的作用是什么

这个注解是为了堵住漏洞用的。曾经有黑客通过构造双重反射来提升权限, 原理是当时反射只检查固定深度的调用者的类,看它有没有特权, 例如固定看两层的调用者(getCallerClass(2))。如果我的类本来没足够 权限群访问某些信息,那我就可以通过双重反射去达到目的:反射相关 的类是有很高权限的,而在 我->反射1->反射2 这样的调用链上,反射2 检查权限时看到的是反射1的类,这就被欺骗了,导致安全漏洞。 使用CallerSensitive后,getCallerClass不再用固定深度去寻找 actual caller(“我”),而是把所有跟反射相关的接口方法都标注上 CallerSensitive,搜索时凡看到该注解都直接跳过,这样就有效解决了 前面举例的问题

2、java如何通过绝对路径和相对路径读取文件

注:Java相对路径读取文件

3、Classloader的 getResource获取加载资源文件路径

注:探讨Classloader的 getResource
Thread.getContextClassLoader()

 public static void main(String[] args) {
    System.out.println(ClassLoaderWrapper.class.getResource("test.txt"));
    //ClassLoader 加载资源需要通过包路径 因为实在编译后的classpath根目录中查找 也就是classes目录
    System.out.println(ClassLoaderWrapper.class.getClassLoader().getResource("org/apache/ibatis/io/test.txt"));
    System.out.println(ClassLoaderWrapper.class.getClassLoader().getResource("/org/apache/ibatis/io/test.txt"));

    //如果是以/开头也会在classpath目录查找
    System.out.println(ClassLoaderWrapper.class.getResource("/org/apache/ibatis/io/test.txt"));

    //下面是上面 通过Class 和 ClassLoader的 getResource方法获取资源路径 ,Class内部也是调用ClassLoader的getResource方法
 /*   file:/D:/code/openSourcePro/mybatis-3/target/classes/org/apache/ibatis/io/
      file:/D:/code/openSourcePro/mybatis-3/target/classes/
      null
    file:/D:/code/openSourcePro/mybatis-3/target/classes/*/

//    JDK设置这样的规则,是很好理解的,path不以'/'开头时,我们就能获取与当前类所在的路径相同的资源文件,而以'/'开头时可以获取ClassPath根下任意路径的资源。

//    对于ClassLoader.getResource, 直接调用的就是ClassLoader 类的getResource方法,那么对于getResource(""),path不以'/'开头时,首先通过双亲委派机制,使用的逐级向上委托的形式加载的,最后发现双亲没有加载到文件,最后通过当前类加载classpath根下资源文件。对于getResource("/"),'/'表示Boot ClassLoader中的加载范围,因为这个类加载器是C++实现的,所以加载范围为null。

  }

开发时获取getResource和部署getResource不一致
在这里插入图片描述

4、JVM 中对Boolean类型的处理

JVM虚拟机中并没有对应的Boolean数据类型,其实是整数类型0和1对应false和true

5、JDBC实现原理

JDBC是Sun公司制定的一个可以用Java语言连接数据库的技术。JDK的rt.jar中定义了数据库操作了接口,如Driver、Connection、Statement、PreparedStatement、ResultSet等,对数据库操作做了统一。然后各数据库开发商基于JDK的这些接口实现了各自数据库操作的java jar包,如MySql的mysql-connector-java-8.0.19.jar,在jar包中实现了对数据的操作。

其中DriverManager是JDK通过配置调用数据库接口实现类的中枢纽,Java应用程序中通过 Class.forName(“com.mysql.jdbc.Driver”); 加载对应的类文件,并且再加载Driver类的时候会执行Driver中的静态代码块把对应的实现类Driver的实例注册到JDK的DriverManager类中。然后在业务代码中调用getConnection 方法会使用DriverManager中注册的Driver实例connect方法生成connection 实例。

代码实现如下图:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

JDBC查询数据库执行步骤:

  • 通过DriverManager.getConnection(url, user, passWord)返回了Connection
  • Connection prepareStatement createStatement 返回了Statement
  • 然后Statement executeQuery 调用数据库执行sql语句返回ResultSet
  • 然后ResultSet next 取出每行数据用户定义的Java对象即可。

其中Statement是真正执行数据库sql的核心类。
Statement和PreparedStatement区别是:

  • Statement直接通过传入的sql语句传入数据库进行执行,这样会有sql注入的风险。如下,通过Connection createStatement 并没有传入sql语句,在执行方法executeQuery executeUpdate中传入sql语句,也就是调用者程序直接传入sql语句。
    在这里插入图片描述
    在这里插入图片描述

  • PreparedStatement通过自身对sql语句的特殊入参处理,避免了sql注入的风险。如下,PreparedStatement是通过各种数据类型进行set的方法对传入参数中特殊字符进行转义处理,然后在参数外层加上单引号’ '处理,传入数据库中执行。这样就避免了分号和引号引起的sql注入。
    在这里插入图片描述
    在这里插入图片描述
    注:sql中null也算一种类型,通过setNull进行处理传入sql。

Java操作数据库基本方法:

public class DBConnection {
    private Connection connection;
    private PreparedStatement preparedStatement;
    private String user = "root";
    private String passWord = "root";
    private String driverName = "com.mysql.jdbc.Driver";
    private String url = "jdbc:mysql://mysql.springfans.org/springfans?useSSL=false&characterEncoding=utf-8";

    public DBConnection() {

    }

    public Connection getConnection() {
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            System.out.println("加载数据库驱动失败!");
            e.printStackTrace();
        }

        try {
            connection = DriverManager.getConnection(url, user, passWord);
        } catch (SQLException e) {
            System.out.println("创建数据库连接失败!");
            connection = null;
            e.printStackTrace();
        }

        return connection;
    }

    public static void main(String[] args) {

        List<AppUpdateVersion> list = new ArrayList<>();
        Connection con = new DBConnection().getConnection();
        if (con != null) {
            try {
                String sql = "select * from fortune_app_version";
                PreparedStatement preparedStatement = con.prepareStatement(sql);

                ResultSet rs = preparedStatement.executeQuery();
                while (rs.next()) {
                    AppUpdateVersion version = new AppUpdateVersion();
                    version.setId(rs.getInt("id"));
                    version.setVersionId(rs.getString("version_id"));
                    version.setUpdateVersionDate(rs.getDate("update_version_date"));
                    version.setUpdateUrl(rs.getString("update_url"));
                    version.setUpdateType(rs.getString("update_type"));
                    version.setUpdateTime(rs.getDate("update_time"));
                    version.setCreateTime(rs.getDate("create_time"));
                    version.setUpdateContent(rs.getString("update_content"));
                    list.add(version);
                }

                list.forEach(System.out::println);

            } catch (SQLException e) {
                e.printStackTrace();

            } finally {
                try {
                    if (con != null) {
                        con.close();
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }

            }
        }

    }

}

JDBC工作原理主要分3个步骤:

  • 加载数据库驱动。
  • 获取数据库连接。
  • 发送sql语句访问数据库 。
  1. 加载数据驱动:使用Class.forName方法,如调用这个方法加载mysql数据库驱动com.MySQL.jdbc.driver。
    关于数据库驱动的理解,其实是sun公司给了一个Driver的接口,然后各个数据厂商根据自己的数据库
    来实现这个接口。当要访问数据库的时候,需要引入这个第三方类库。类的加载主要分为5个部分,加载、验证、准备、解析、初始化。在初始化的部分用到了DriverManager.registerDriver()方法,将
    自己注册给DriverManager的Driver接口。这个地方体现了多态。这个时候就可以使用Driver了。
  2. 获取数据库连接DriverManager.getConnection()。这个方法主要调用driver的connect()方法
    返回一个实现了Connection接口的对象。
  3. 然后利用Connection对象创建Statement,发送sql语句访问数据库
    延伸:JDBC基础知识
6、java字节码动态修改

我们知道Java是静态语言,而python、ruby是动态语言,Java程序一旦写好很难在运行时更改类的行为,而python、ruby可以。

不过基于bytecode层面上我们可以做一些手脚,来使Java程序多一些灵活性和Magic,ASM就是这样一个应用广泛的开源库。

在有些场景中我们需要在程序运行过程中修改class字节码并且重新加载然后运行,如:线上项目出现紧急问题,需要在某个方法中加入日志代码进行问题定位,或则需要修改方法进行临时处理等。这个场景和动态代码比较类似,动态代理也是在JVM运行过程中动态生成代理类的字节码,然后加载运行。同样是在程序运行过程中操作字节码,完成既定功能。

存在相关框架来实现字节码的动态修改:

  • ASM
  • Btrace
  • cglib
  • AspectJ

其中Btrace和cglib都是基于ASM的。

注:反射、Proxy和元数据是Java最强的三个特征,再加上CGLib (Code Generation Library) 和ASM,使得Java虽然没有Ruby,Python般后生可畏,一样能做出强悍的框架。

ASM
注:Java动态追踪技术探究

ASM is a Java bytecode manipulation framework. It can be used to dynamically generate stub classes or other proxy classes, directly in binary form, or to dynamically modify classes at load time, i.e., just before they are loaded into the Java Virtual Machine.

ASM是一个Java字节码操作框架。它可以用于直接以二进制形式动态生成子类或其他代理类,也可以用于在装入时(即在装入JVM之前)动态修改字节码。总的来说就是ASM可以基于当前类生成子类或则代理类的class文件,或则修改已有的class文件。

Arthas

BTrace脚本在使用上有一定的学习成本,如果能把一些常用的功能封装起来,对外直接提供简单的命令即可操作的话,那就再好不过了。阿里的工程师们早已想到这一点,就在去年(2018年9月份),阿里巴巴开源了自己的Java诊断工具——Arthas。Arthas提供简单的命令行操作,功能强大。究其背后的技术原理,和本文中提到的大致无二。Arthas的文档很全面,想详细了解的话可以戳这里。

本文旨在说明Java动态追踪技术的来龙去脉,掌握技术背后的原理之后,只要愿意,各位读者也可以开发出自己的“冰封王座”出来。

cglib

cglib is a powerful, high performance and quality Code Generation Library, It is used to extend JAVA classes and implements interfaces at runtime.

cglib是一个功能强大、高性能和高质量的代码生成库,用于在运行时扩展JAVA类和实现接口。cglib是Code Generation Library的缩写。cglib依赖于ASM库。

Hibernate主要是利用cglib生成pojo的子类并override get方法来实现lazy loading机制,Spring则是利用cglib来实现动态代理。而JDK的动态代理机制要求有接口才行,这样就强制我们的pojo实现某个接口

AspectJ

AspectJ不同于上面三个框架,AspectJ是一个AOP框架,和Spring-AOP类似。
AspectJ是Java中流行的AOP编程扩展框架(AspectJ和Spring-AOP是对立的,他们就相当于两个不同AOP框架,其中Spring-AOP用到的是jdk的动态代理和cglib),从底层实现上来看,AspectJ内部使用的是BCEL(相对来说ASM更小巧方便)框架来完成,在使用上来看,AspectJ框架有自己的一定优势:

  • 成熟稳定
  • 使用简单
    我们完全不用关心底层Java字节码的处理流程,就可以轻松实现编译插桩功能。但是它的功能无法满足某些场景的需求,如果想针对所有函数都做插桩,AspectJ会带来不少的性能影响。

注:我们知道JDK的动态代理是针对接口的,在运行期生成代理类。

CGLIB动态代理 是可以针对接口与普通类(继承方式),底层使用ASM框架生成字节码完成代理功能

我在网上查资料说AspectJ是静态代理 在编译期间就生成了class文件完成了代理。这点容易理解一种编译的技术。

尾声:三生万物

现在,让我们试着站在更高的地方“俯瞰”这些问题。

Java的Instruments给运行时的动态追踪留下了希望,Attach API则给运行时动态追踪提供了“出入口”,ASM则大大方便了“人类”操作Java字节码的操作。

基于Instruments和Attach API前辈们创造出了诸如JProfiler、Jvisualvm、BTrace、Arthas这样的工具。以ASM为基础发展出了cglib、动态代理,继而是应用广泛的Spring AOP。

Java是静态语言,运行时不允许改变数据结构。然而,Java 5引入Instruments,Java 6引入Attach API之后,事情开始变得不一样了。虽然存在诸多限制,然而,在前辈们的努力下,仅仅是利用预留的近似于“只读”的这一点点狭小的空间,仍然创造出了各种大放异彩的技术,极大地提高了软件开发人员定位问题的效率。这跟反射不一样,反射是通过已经编译过的字节码类型,通过jvm进行动态加载和执行方法,跳过了必须要实例化的步骤。而Instruments是跳过了定义和编译,直接在运行时动态修改class字节码并加载执行。

计算机应该是人类有史以来最伟大的发明之一,从电磁感应磁生电,到高低电压模拟0和1的比特,再到二进制表示出几种基本类型,再到基本类型表示出无穷的对象,最后无穷的对象组合交互模拟现实生活乃至整个宇宙。

两千五百年前,《道德经》有言:“道生一,一生二,二生三,三生万物。”

两千五百年后,计算机的发展过程也大抵如此吧。

编程

编程语言

在介绍编译和反编译之前,我们先来简单介绍下编程语言(Programming Language)。编程语言(Programming Language)分为低级语言(Low-level Language)和高级语言(High-level Language)

机器语言(Machine Language)和汇编语言(Assembly Language)属于低级语言,直接用计算机指令编写程序。

而C、C++、Java、Python等属于高级语言,用语句(Statement)编写程序,语句是计算机指令的抽象表示。

举个例子,同样一个语句用C语言、汇编语言和机器语言分别表示如下:
在这里插入图片描述

计算机只能对数字做运算,符号、声音、图像在计算机内部都要用数字表示,指令也不例外,上表中的机器语言完全由十六进制数字组成。最早的程序员都是直接用机器语言编程,但是很麻烦,需要查大量的表格来确定每个数字表示什么意思,编写出来的程序很不直观,而且容易出错,于是有了汇编语言,把机器语言中一组一组的数字用助记符(Mnemonic)表示,直接用这些助记符写出汇编程序,然后让汇编器(Assembler)去查表把助记符替换成数字,也就把汇编语言翻译成了机器语言。

但是,汇编语言用起来同样比较复杂,后面,就衍生出了Java、C、C++等高级语言。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值