反射

反射的基本使用与封装

  • User.java ```java package com.cy.hu;

public class User { private String name; private Integer age; //name和age的get和set方法不显示了,占位置 @Override public String toString() { return “User{“ + “name=’” + name + ‘\’’ + “, age=” + age + ‘}’; } }

  1. - 封装的通过反射获得类的实例的Util
  2. ```java
  3. package com.cy.util;
  4. public class ReflectUtil {
  5. /**
  6. * 基于传入的类型构建对象实例
  7. * packageClass表示 包路径.类名
  8. */
  9. public static Object newInstance(String packageClass,
  10. Object args[],Class<?>...parameterTypes) throws Throwable {
  11. Class<?> aClass = Class.forName(packageClass);
  12. Constructor<?> constructor = aClass.getDeclaredConstructor(parameterTypes);
  13. constructor.setAccessible(true);//不是同包下,设置权限
  14. Object o = constructor.newInstance(args);
  15. return o;
  16. }
  17. }
  • 反射使用 ```java package com.cy.hu; import com.cy.util.ReflectUtil; import javax.jws.Oneway; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method;

public class Test { public static void main(String[] args) throws Throwable { //构建对象(传统方式),编译时确定构建对象方式(对象构建时需要构造方法) Object oo = new Object();

    //构建对象(反射方式-API->不能预知未来,但可以驾驭未来)
    //反射的起点是谁?Class类型的对象
    //如何获取Class类型的对象呢?(Class类型的对象又称之为字节码对象,一个JVM只能有一份,单例)
    //方式1,通过类名.class方式
    Class<?> c1 = Object.class;//需要在编译时就知道Object类
    //方式2,通过对象实例.getClass方式
    Class<?> c2 = new Object().getClass();//需要在编译时就知道Object类
    //方式3,通过Class.forName的方式,优先选择这种
    Class<?> c3 = Class.forName("java.lang.Object");
    System.out.println(c1==c2);//true
    System.out.println(c2==c3);//true

    //通过字节码对象获取无参构造方法对象
    Constructor<?> constructor = c3.getDeclaredConstructor();
    //通过构造方法对象构建类的实例对象
    Object o1 = c1.newInstance(); //o1和o3获得的对象方式都可以
    Object o3 = constructor.newInstance();
    System.out.println("----------------------------------");
    //1.通过反射构建User类型的实例对象
    //1.1获得反射应用的起点对象(类的字节码对象)
    Class<?> aClass = Class.forName("com.cy.hu.User");
    //1.2基于字节码对象获取类的构造方法对象
    Constructor<?> con = aClass.getDeclaredConstructor(String.class, Integer.class);
    //1.3获得实例对象
    Object o = con.newInstance("iu", 18);
    System.out.println(o);

    //通过封装的ReflectUtil获得实例对象
    Object o2 = ReflectUtil.newInstance("com.cy.hu.User", new Object[]{"iu", 23}, String.class, Integer.class);
    System.out.println(o2);

    //2.通过反射为User类实例的属性直接赋值
    Field name = aClass.getDeclaredField("name");
    name.setAccessible(true);
    name.set(o,"hu");
    Field age = aClass.getDeclaredField("age");
    age.setAccessible(true);
    age.set(o, 19);
    System.out.println(o);
    //3.通过反射调用User类的set方法为属性赋值
    Method setAge = aClass.getDeclaredMethod("setAge", Integer.class);
    setAge.setAccessible(true);
    setAge.invoke(o, 23);
    System.out.println(o);
    //4.通过反射调用User类的get方法获取属性值.
    Method getAge = aClass.getDeclaredMethod("getAge");
    Object getResult = getAge.invoke(o);
    System.out.println(getResult);

}

}

<a name="JzJv5"></a>
### 注解与反射对象

- 判断类上是否有指定注解,有就给它反射实例对象
```java
package com.cy.java.api.reflect;

import java.lang.annotation.*;

/**自定义注解
 * @Retention 注解用于描述定义的注解何时有效(运行时,编译时)
 * @Target 注解用于描述这个注解可以描述哪些成员(类,属性,方法)
 * */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Component{ //@interface 描述的类型为注解类型(本质上是一个特殊的class)
}
@Component
class classA{
}

public class AnnotationTests {
    public static void main(String[] args) throws Exception {
        //检查classA 上是否有@Component注解描述
        //1.获取类的字节码对象
        Class<?> aClass = Class.forName("com.cy.java.api.reflect.classA");
        //2.判定对象上是否有@Component注解
        boolean flag = aClass.isAnnotationPresent(Component.class);
        System.out.println(flag);//true
        if(flag){
            Object o = aClass.newInstance();
            System.out.println(o);
        }
        //还可以采用如下方式判定类上是否有指定注解,Annotation类型是所有注解的父类类型
        Annotation annotation= aClass.getAnnotation(Component.class);
        if(annotation!=null){
            Object oo = aClass.newInstance();
            System.out.println(oo);
        }
    }
}

反射获得当前类所在包下全部类

  • 可以应用在包扫描,给所有包下类建立实例对象

    package com.cy.java.api.reflect;
    import java.io.File;
    import java.net.URL;
    public class PackTests {
      public static void main(String[] args) throws Exception {
          //获取类的字节码对象
          Class<?> aClass = Class.forName("com.cy.java.api.reflect.PackTests");
          //获取类所在的包对象
          Package pack = aClass.getPackage();
          //获得包名
          String packName = pack.getName();
          System.out.println(packName);// com.cy.java.api.reflect
          //将包结构转换为目录结构
          String dirName = packName.replace(".", "/");
          System.out.println(dirName);// com/cy/java/api/reflect
          //通过类加载器获取目录结构dirName对应的绝对路径
          URL url = ClassLoader.getSystemClassLoader().getResource(dirName);
          System.out.println(url); // file:/E:/TCGBIll/CODE/Pro1/out/production/01-javase/com/cy/java/api/reflect
          //获取路径对应的文件File对象
          System.out.println(url.getPath());// /E:/TCGBIll/CODE/Pro1/out/production/01-javase/com/cy/java/api/reflect
          File file = new File(url.getPath());
          String[] lists = file.list();
          for (String list : lists) {
              System.out.print(list+"===");
          }// AnnotationTests.class===classA.class===Component.class===PackTests.class===
    
      }
    }
    

    反射具体应用?

    1)获取构造方法对象(Constructor),基于构造方法对象构建类的实例对象?
    为什么不直接new呢?
    2)获取类中属性(Field),进而获取属性值或为属性赋值.
    3)获取类中的方法(Method),并通过反射技术调用方法.
    4)获取类上的注解(Annotation),进而获取注解中内容.
    5)获取类所在包,基于包找到指定包下的类,
    6)……………….

    SpringBoot项目

  • 基于spring技术实现的一个脚手架

  • 为了快速构建项目开发环境
  • springboot提供了开箱即用的特性-依赖,自动配置,…

    SpringBoot初始化

    image.png

  • src/main/java下 java业务代码

  • src/main/resource下 项目配置文件,静态资源
  • srctest/java下 单元测试代码
  • pom.xml 服务端依赖,maven插件配置
  • pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <!--parent工程中提供了很多项目的基础依赖-->
      <parent>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-parent</artifactId>
          <version>2.3.8.RELEASE</version>
          <relativePath/> <!-- lookup parent from repository -->
      </parent>
    
      <!--我们自己的当前项目的坐标-->
      <groupId>com.cy</groupId>
      <artifactId>04-springboot-notice</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      <name>04-springboot-notice</name>
      <description>Demo project for Spring Boot</description>
    
      <!--项目属性的配置(jdk的版本,将来还可以自己定义依赖的版本)-->
      <properties>
          <java.version>1.8</java.version>
      </properties>
    
      <dependencies>
          <!--springboot 工程的启动依赖-->
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter</artifactId>
          </dependency>
    
          <!--springboot 工程中的单元测试依赖-->
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-test</artifactId>
              <scope>test</scope>
              <exclusions>
                  <!--排除一些不需要的依赖-->
                  <exclusion>
                      <groupId>org.junit.vintage</groupId>
                      <artifactId>junit-vintage-engine</artifactId>
                  </exclusion>
              </exclusions>
          </dependency>
      </dependencies>
    
      <build>
          <!--项目构建或打包时候需要的一些依赖-->
          <plugins>
              <!--假如当前插件显示红色,可以为其指定version-->
              <plugin>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-maven-plugin</artifactId>
                  <version>2.3.8.RELEASE</version>
              </plugin>
          </plugins>
      </build>
    </project>
    

主类项目启动

  1. 通过ClassLoader(类加载器-负责将磁盘中的类读到内存中)将指定包中的类加载到内存
  2. 通过线程(thread)调用io(InputStream)从磁盘(Disk)读取文件(File)信息
  3. 如果读取到某个类是在主类的包或者子包下,且存在基于类上的描述(@Component,@Service,@Controller,…..)
    • 通过前面的反射获取当前类所在包下的全部类、注解与反射现象得到两个条件都具备的类
    • 然后读取类上的描述(@Component,@Service,@Controller,…..),并基于描述构建配置对象(BeanDefinition),存储类的配置信息(类全名,作用域,….).
  4. 基于类的配置信息通过Bean工厂反射构建类的实例(对象),并进行存储(对象池-用时从池中取)

image.png

package com.cy;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.util.Arrays;
/**
 * 启动类:由@SpringBootApplication注解描述
 * 启动类在运行时都会做什么呢?
 * 1)通过ClassLoader(类加载器-负责将磁盘中的类读到内存中)将指定包中的类加载到内存.
 * 2)通过线程(thread)调用io(InputStream)从磁盘(Disk)读取文件(File)信息.
 * 3)读取类上的描述(@Component,@Service,@Controller,.....),并基于描述构建
 * 配置对象(BeanDefinition),存储类的配置信息(类全名,作用域,....).
 * 4)基于类的配置信息通过Bean工厂反射构建类的实例(对象),并进行存储(对象池-用时从池中取)
 * 5)当我们需要一个类的实例时可以从对象池(Bean池)获取即可.
 * JVM 参数分析
 * 1)检测类的加载:-XX:+TraceClassLoading
 */
@SpringBootApplication
public class NoticeApplication {

    public static void main(String[] args) {
        //System.out.println(Arrays.toString(args));
        SpringApplication.run(NoticeApplication.class, args);
    }
/**
 * 记住(规则):我们要交给spring容器管理的对象,一定要放在启动类所在包或子包中,
 * 然后使用特性注解进行描述(@Component,@Service,....)
 *
 * FAQ?
 * Spring是一个资源管理框架,请问资源是谁?对象
 */
}

Mybatis

HikariCP连接池测试链接数据库

  • Java中连接池规范 (DataSource)

1)为什么要创建连接池?(连接的创建和销毁非常耗时-TCP/IP-3次握手,4次挥手)
2)java中连接池的规范?(javax.sql.DataSource)
3)java中连接池的江湖?(c3p0,dbcp,Druid,hikariCP,…)
4)连接池设计思考?(数据存储结构,算法,线程安全,参数设计,…)

  • 池化思想
    • 所有池的设计都会用到一种设计模式:享元模式 (通过池复用对象)
  • 添加依赖 ```xml mysql mysql-connector-java runtime

org.springframework.boot spring-boot-starter-data-jdbc


- 添加数据库连接配置
   - spring.datasource.url=jdbc:mysql:///sys_notice?serverTimezone=GMT%2B8
   - spring.datasource.username=root
   - spring.datasource.password=tarena
- 测试连接池HikariCP连接
- 测试连接池HikariCP插入数据
- 测试连接池HikariCP查询数据

- 连接池的测试应用流程?
   - 添加依赖?(mysql驱动,spring-jdbc依赖)
   - 连接数据库的配置(url,username,password)
   - 构建单元测试类及方法对连接池进行单元测试?(获取连接)
   - 连接获取原理分析?(@Test->DataSource->HikariDataSource->HikariPool->.....
```java
package com.cy.pj.sys.dao;

/**
 * 通过此单元测试类获取数据源对象,并且通过数据对象获取数据库连接
 * @SpringBootTest 注解描述的类,为springboot中的单元测试类
 * 说明:
 * 1)springboot中的单元测试类必须放在启动类所在包,或子包中
 * 2)springboot中的单元测试类必须使用@SpringBootTest注解描述
 */
@SpringBootTest
public class DataSourceTests {
    /**
     * 在项目中添加了数据库相关依赖以后,springboot底层会自动帮我们配置一个数据源(DataSource)对象,此对象是连接池的规范.
     * @Autowired注解描述属性时,是告诉spring框架,要基于反射机制为属性赋值(依赖注入)
     * 赋值时,首先会基于属性类型从spring容器查找相匹配的对象, 假如只有一个则直接注入,
     * 有多个相同类型的对象时,还会比较属性名(检测属性名是否与bean名字相同),有相同的则直接注入(没有相同的直接抛出异常.)
     */
    @Autowired
    private DataSource dataSource; //HikariDataSource (类),依赖注入

    @Test
    void testGetConnection() throws SQLException {
        //获取链接时,会基于dataSource规范获取连接池对象,进而从池中获取连接
        Connection conn = dataSource.getConnection();
        System.out.println("conn="+conn);
    }

    @Test
    void testSaveNotice() throws SQLException {
        //获得连接HikariCP连接池提供的连接,Hikari底层封装了注册驱动,将连接返回
        Connection connection = dataSource.getConnection();
        //建立sql语句
        String sql = "insert into sys_notice(title,type,content,status,createdUser,createTime,modifiedUser,modifiedTime,remark) values (?,?,?,?,?,?,?,?,?) ";
        //创建处理sql对象,设置占位符的值
        PreparedStatement preStmt = connection.prepareStatement(sql);//预编译方式创建Statement
        preStmt.setObject(1, "放假通知");
        preStmt.setObject(2, "1");
        preStmt.setObject(3,"放假100天");
        preStmt.setObject(4,0);
        preStmt.setObject(5,"hu");
        preStmt.setObject(6,new Date(System.currentTimeMillis()));
        preStmt.setObject(7,"iu");
        preStmt.setObject(8,new Date(System.currentTimeMillis()));
        preStmt.setObject(9,"100天以后去见IU");
           //将sql语句送到数据库执行
        boolean flag = preStmt.execute();
        //方法执行返回值为true表示处理的是一个不为null的结果集,为false表示增删改,或者结果集为null
        System.out.println(flag);
        //释放资源
        preStmt.close();
        connection.close();
    }

    @Test
    void testSelectNotices() throws SQLException {
        Connection connection = dataSource.getConnection();
        String sql = "select * from sys_notice where id>=?";
        PreparedStatement ptStmt = connection.prepareStatement(sql);
        ptStmt.setObject(1, 2);
        boolean flag = ptStmt.execute();
        ResultSet rs = null;
        if(flag){
            rs = ptStmt.getResultSet();
            List<Map<String,Object>> list = new ArrayList<>();
            //获取结果集中的元数据(表名、字段名)
            ResultSetMetaData rsmd = rs.getMetaData();
            while (rs.next()){
                //一行记录为一个map对象
                Map<String,Object> map = new HashMap<>();//将来也可以使用pojo
                /*for (int i = 1; i <=rsmd.getColumnCount() ; i++) {
                    map.put(rsmd.getColumnLabel(i), rs.getObject(i));
                } 这种方式也可以*/
                //取出类中的数据存储到map(key为字段名,值是字段value)
                for (int i = 1; i <=rsmd.getColumnCount() ; i++) {
                    map.put(rsmd.getColumnName(i),rs.getObject(i));
                }
                System.out.println(map);
                list.add(map);
            }
            //System.out.println(list);
        }
        //关闭资源
        rs.close();
        ptStmt.close();
        connection.close();
    }
}

image.png

Mybatis框架简介

  • mybatis是一个用于简化jdbc操作的持久层框架
  • mybatis:简单,灵活,开源,稳定,功能强大-的SQL定制,参数映射,结果映射
    • JDBC编程过程的问题分析
      • 步骤固定(写几次过后,谁都会,没有必要反复写)
      • 占位符参数赋值代码简单,但需要重复编写.
      • 行映射过程代码比较简单,但是需要的代码量比较大,需要反复编写
      • SQL语句不够灵活(不支持分支、循环结构,很难基于条件进行动态SQL的定制)
    • mybatis支持标签、支持分支和循环结构,更加灵活,大大减少了重复代码的编写
  • SpringBoot工程中的mybatis应用环境搭建

    • 添加依赖(mybatis):官网http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/
    • 初始配置(事务超时,驼峰命名,映射文件路径)
    • mybatis.mapper-locations=classpath:/mapper//.xml
    • 对环境进行单元测试(重点检查通过sqlsession对象是否可以获取数据库的链接)
      • 说明:SqlSession对象获取的链接其实来自于连接池(HikariCP)
    • 添加持久层增删改查操作,并对操作进行测试,以后面若依公告子系统项目进行mybatis的操作理解

      Mybatis使用流程

  • 添加依赖

    <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.3</version>
    </dependency>
    
  • Mybatis 简易配置application.properties ```java

    spring mybatis

    配置sql超时

    mybatis.configuration.default-statement-timeout=30

驼峰命名规则

mybatis.configuration.map-underscore-to-camel-case=true

映射文件路径

mybatis.mapper-locations=classpath:/mapper//.xml

日志

logging.level.com.cy=debug


- 流程
   - mybatis封装了jdbc和数据库连接池Hikari

   - 在对持久层数据插入的测试中,SysNoticeDaoTests类里面定义了一个属性SysNoticeDao,且属性上有@Mapper注解,那么这个属性的值是交给Spring的IOC容器管理,它的实现类是一个代理类,然后利用反射创建的代理对象放到IOC容器中,在属性定义时,spring框架从IOC容器的bean池中DL(依赖查找)与属性对应的对象,  并DI(依赖注入)到添加了@Autowired注解的属性属性....代理对象底层有SqlSession属性。

![image.png](https://cdn.nlark.com/yuque/0/2021/png/358364/1611843801513-98fff9e4-daf1-40bb-9079-f4f4abfdbd67.png#align=left&display=inline&height=398&margin=%5Bobject%20Object%5D&name=image.png&originHeight=463&originWidth=867&size=46653&status=done&style=none&width=746)

   - 代理对象的SqlSession会话对象通过命名空间.方法Id找到mapper映射文件具体的操作,也就获得了sql语句,操作sql
```java
package com.cy.pj.sys.dao;
import com.cy.pj.sys.pojo.SysNotice;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;

/**@Repository注解由spring框架提供,用于描述数据逻辑对象,
 * 同样这样的对象会交给spring去管理,是spring容器中的一个bean*/
//@Repository
public class SysNoticeDaoProxy {//模拟mybatis为Dao接口产生的实现类
    @Autowired
    private SqlSession sqlSession;
    public int insertNotice(SysNotice notice){
        String statement = "com.cy.pj.sys.dao.SysNoticeDao.insertNotice";
        //数据持久化(底层会基于statement找到对象的sql)
        int count = sqlSession.insert(statement, notice);
        return count;
    }
}
  • 依靠映射文件、mybatis配置文件、数据源(连接池),SqlSessionFactoryBuild会创建接口SqlSessionFactory的实现类来创建操作数据库的会话对象,这个会话对象同样是实现了接口SqlSession,会话对象通过调用JDBC的API来通过sql语句操控数据库

image.png

  • 会话对象有着DateSource数据源属性(数据库连接池规范),此时mybatis封装的HikariCP连接池对象会被注入到这个属性(规范)中,如果连接池没有连接,那么就会去连接数据库获得连接,会去找JDBC规范,此时Mysql Driver数据库驱动会被注入到jdbc规范中,然后就去与数据库连接,就可以获得数据库的连接了,就可以操控数据库了…………………sqlsession对象获得数据库连接

image.png

package com.cy.pj.sys.dao;

import org.apache.ibatis.session.SqlSession;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.sql.Connection;

@SpringBootTest
public class MyBatisTests {
    /**
     * SqlSession是mybatis框架中实现与数据库进行会话的入口对象。
     * 假如我们可以通过此对象获取与数据库的连接,表示可以通过
     * mybatis框架实现与数据库会话。
     */
    @Autowired
    private SqlSession sqlSession;//这里的sqlSession指向的对象是谁?(SqlSessionTemplate)
    @Test
    void testGetConnection(){
        //连接来自哪里?(来自于连接池-底层会自动将连接池注入给mybatis框架)
        Connection connection = sqlSession.getConnection();
        System.out.println("conn="+connection);
    }
}

jdbc-select.png

mybatis总结

  • mybatis是一个用于简化jdbc操作的持久层框架
  • mybatis:简单,灵活,开源,稳定,功能强大-的SQL定制,参数映射,结果映射
    • JDBC编程过程的问题分析
      • 步骤固定(写几次过后,谁都会,没有必要反复写)
      • 占位符参数赋值代码简单,但需要重复编写.
      • 行映射过程代码比较简单,但是需要的代码量比较大,需要反复编写
      • SQL语句不够灵活(不支持分支、循环结构,很难基于条件进行动态SQL的定制)
    • mybatis支持标签、支持分支和循环结构,更加灵活,大大减少了重复代码的编写
  • MyBatis 框架核心API?(SqlSessionFactoryBuilder,SqlSessionFactory,SqlSession….)
  • MyBatis 框架API会话应用的执行过程?
    • SqlSession->DataSource-Connection-JDBCAPI-SQL
  • SpringBoot工程中的mybatis应用环境搭建
  • 初始配置(事务超时,驼峰命名,映射文件路径)
  • MyBatis 中的动态SQL应用,参数表达式#{}

FAQ

  • @Mapper注解的作用是什么?
    • 告诉spring框架,这个接口的实现类对象由IOC容器创建管理
  • DAO中方法参数为什么有时需要使用@Param注解描述?
    • 版本低了,可能需要这个注解传递参数
    • 如接口方法中fun(@Param(“list”) List list )
      • 传递参数list集合到xml中操作
  • 什么情况下将sql映射写到dao方法上?(推荐简单sql)
    • 简单sql可以通过注解的方式直接写在方法上
  • SQL映射中什么请求下会使用resultType?(简单查询)
    • 简单查询,实体类与数据表里的字段相对于的,都可以用resultType
    • 复杂的一对一,多对多查询,可以采用resultMap
  • 动态sql的语法结构是怎样的?(参考官网-https://mybatis.org/mybatis-3)
  • ……………………………..