反射
反射的基本使用与封装
- 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
```java
package 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);//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初始化
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
*/
@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
- 添加数据库连接配置
- 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();
}
}
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属性。
![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语句操控数据库
- 会话对象有着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;
@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);
}
}
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)
- ……………………………..