反射
反射的基本使用与封装
- 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 + ‘}’; } }
- 封装的通过反射获得类的实例的Util```javapackage com.cy.util;public class ReflectUtil {/*** 基于传入的类型构建对象实例* packageClass表示 包路径.类名*/public static Object newInstance(String packageClass,Object args[],Class<?>...parameterTypes) throws Throwable {Class<?> aClass = Class.forName(packageClass);Constructor<?> constructor = aClass.getDeclaredConstructor(parameterTypes);constructor.setAccessible(true);//不是同包下,设置权限Object o = constructor.newInstance(args);return o;}}
- 反射使用 ```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);//trueSystem.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>### 注解与反射对象- 判断类上是否有指定注解,有就给它反射实例对象```javapackage com.cy.java.api.reflect;import java.lang.annotation.*;/**自定义注解* @Retention 注解用于描述定义的注解何时有效(运行时,编译时)* @Target 注解用于描述这个注解可以描述哪些成员(类,属性,方法)* */@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@interface Component{ //@interface 描述的类型为注解类型(本质上是一个特殊的class)}@Componentclass 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);//trueif(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/reflectFile 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初始化

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

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*/@SpringBootApplicationpublic 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
- 添加数据库连接配置- 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->.....```javapackage com.cy.pj.sys.dao;/*** 通过此单元测试类获取数据源对象,并且通过数据对象获取数据库连接* @SpringBootTest 注解描述的类,为springboot中的单元测试类* 说明:* 1)springboot中的单元测试类必须放在启动类所在包,或子包中* 2)springboot中的单元测试类必须使用@SpringBootTest注解描述*/@SpringBootTestpublic class DataSourceTests {/*** 在项目中添加了数据库相关依赖以后,springboot底层会自动帮我们配置一个数据源(DataSource)对象,此对象是连接池的规范.* @Autowired注解描述属性时,是告诉spring框架,要基于反射机制为属性赋值(依赖注入)* 赋值时,首先会基于属性类型从spring容器查找相匹配的对象, 假如只有一个则直接注入,* 有多个相同类型的对象时,还会比较属性名(检测属性名是否与bean名字相同),有相同的则直接注入(没有相同的直接抛出异常.)*/@Autowiredprivate DataSource dataSource; //HikariDataSource (类),依赖注入@Testvoid testGetConnection() throws SQLException {//获取链接时,会基于dataSource规范获取连接池对象,进而从池中获取连接Connection conn = dataSource.getConnection();System.out.println("conn="+conn);}@Testvoid 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);//预编译方式创建StatementpreStmt.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表示增删改,或者结果集为nullSystem.out.println(flag);//释放资源preStmt.close();connection.close();}@Testvoid 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();}}
Mybatis框架简介
- mybatis是一个用于简化jdbc操作的持久层框架
- mybatis:简单,灵活,开源,稳定,功能强大-的SQL定制,参数映射,结果映射
- JDBC编程过程的问题分析
- 步骤固定(写几次过后,谁都会,没有必要反复写)
- 占位符参数赋值代码简单,但需要重复编写.
- 行映射过程代码比较简单,但是需要的代码量比较大,需要反复编写
- SQL语句不够灵活(不支持分支、循环结构,很难基于条件进行动态SQL的定制)
- mybatis支持标签、支持分支和循环结构,更加灵活,大大减少了重复代码的编写
- JDBC编程过程的问题分析
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属性。- 代理对象的SqlSession会话对象通过命名空间.方法Id找到mapper映射文件具体的操作,也就获得了sql语句,操作sql```javapackage 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*///@Repositorypublic class SysNoticeDaoProxy {//模拟mybatis为Dao接口产生的实现类@Autowiredprivate 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语句操控数据库

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

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;@SpringBootTestpublic class MyBatisTests {/*** SqlSession是mybatis框架中实现与数据库进行会话的入口对象。* 假如我们可以通过此对象获取与数据库的连接,表示可以通过* mybatis框架实现与数据库会话。*/@Autowiredprivate SqlSession sqlSession;//这里的sqlSession指向的对象是谁?(SqlSessionTemplate)@Testvoid testGetConnection(){//连接来自哪里?(来自于连接池-底层会自动将连接池注入给mybatis框架)Connection connection = sqlSession.getConnection();System.out.println("conn="+connection);}}
mybatis总结
- mybatis是一个用于简化jdbc操作的持久层框架
- mybatis:简单,灵活,开源,稳定,功能强大-的SQL定制,参数映射,结果映射
- JDBC编程过程的问题分析
- 步骤固定(写几次过后,谁都会,没有必要反复写)
- 占位符参数赋值代码简单,但需要重复编写.
- 行映射过程代码比较简单,但是需要的代码量比较大,需要反复编写
- SQL语句不够灵活(不支持分支、循环结构,很难基于条件进行动态SQL的定制)
- mybatis支持标签、支持分支和循环结构,更加灵活,大大减少了重复代码的编写
- JDBC编程过程的问题分析
- 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)
- ……………………………..
