一个C#开发编写Java框架的心路历程
<p style="width: 100%; background: rgba(65, 105, 225, 1); color: rgba(255, 255, 255, 1); height: 50px; font-size: 30px; line-height: 50px">前言</p><p class="md-end-block md-p"><span class="md-plain">这一篇絮絮叨叨,逻辑不太清晰的编写Java框架的的一个过程,主要描述我作为一个java初学者,在编写Java框架时的一些心得感悟。</span></p>
<p class="md-end-block md-p"><span class="md-plain">因为我是C#的开发者,所以,在编写Java框架时,或多或少会带入一些C#的固有观念,所以,这也是一个C#观念与Java观念碰撞的一个框架。</span></p>
<p style="width: 100%; background: rgba(65, 105, 225, 1); color: rgba(255, 255, 255, 1); height: 50px; font-size: 30px; line-height: 50px">Java与C#的一些小区别</p>
<p class="md-end-block md-p"><span class="md-plain">命名空间:在C#中命名空间+类名是类,在Java中命名空间+类名是命名空间,即,Java中会出现Import某一个类的完全限定名。</span></p>
<p class="md-end-block md-p md-focus"><span class="md-plain md-expand">反射:在C#中反射可以只用类名反射,Java中必须是完全限定名;在C#中反射是在内存或DLL类库中查找文件,一个方法就搞定了,在Java中则需要手写扫描文件夹或扫描Jar包的文件,然后找到名称一样的文件再反射。</span></p>
<p class="md-end-block md-p"><span class="md-plain">for循环:在C#中有for循环和foreach循环,在Java中for循环支持foreach模式,如:</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">for(Kiba_User u : ul)</pre>
</div>
<p style="width: 100%; background: rgba(65, 105, 225, 1); color: rgba(255, 255, 255, 1); height: 50px; font-size: 30px; line-height: 50px">Java之Spring脉络简介</p>
<p class="md-end-block md-p"><span class="md-plain">对于C#开发而言,Java开发的脉络实在是清奇的不得了,因为Java使用了大量的依赖注入和控制反转,从而让它的结构非常的反人类。但这也是有一定的历史原因的,因为它的开源语言,所以,大家在扩展框架时,都等于在做二次开发,因为依赖注入和控制反转是二次开发最好的模式,所以,它就越积累越多,最后它彻底的变成了控制反转的完全体,也就说,它在反人类的路上一去不反复了(注意,Java开发者通常认为他们才是正常开发,为了避免冲突,请不要当面说他们反人类)。</span></p>
<p class="md-end-block md-p"><span class="md-plain">下面我使用C#的描述的方式来勾勒一下Java之Spring的脉络,如下图:</span></p>
<p class="md-end-block md-p"><img src="https://img2020.cnblogs.com/blog/243596/202103/243596-20210315094030816-628581077.png" alt="" loading="lazy"></p>
<p class="md-end-block md-p"><span class="md-plain">因为,java很多对象都是用注解标识,然后在解析时实例化的,为了统一代码,所以,java形成了一种新的标准,实例化对象都用注解。</span></p>
<p style="width: 100%; background: rgba(65, 105, 225, 1); color: rgba(255, 255, 255, 1); height: 50px; font-size: 30px; line-height: 50px">准备工作</p>
<p class="md-end-block md-p"><span class="md-plain">本框架因为是学习框架,所以有些设计会常规的java不同,框架中不会使用类似@Service这样的注解,但会使用@Data,因为Java中写属性确实有点费劲。</span></p>
<p class="md-end-block md-p"><span class="md-plain">下面我们进行准备工作。</span></p>
<p class="md-end-block md-p"><span class="md-plain">开发工具:IDEA。</span></p>
<p class="md-end-block md-p"><span class="md-plain">项目框架:Spring。</span></p>
<p class="md-end-block md-p"><span class="md-plain">JDK:1.8。</span></p>
<p class="md-end-block md-p"><span class="md-plain">ORM:Mybatis。</span></p>
<p class="md-end-block md-p"><span class="md-plain">首先我们创建一个Spring的Web项目——k_framework,C#开发可以参考:<span class=" md-link"><span class="md-plain">一个C#开发者重温Java的心路历程</span><span class="md-plain">。(这里只做WebApi的介绍)</span></span></span></p>
<p class="md-end-block md-p"><span class="md-plain">然后我们编辑Pom.xml引入所需的Jar包,依赖如下:</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;"> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc8</artifactId>
<version>12.1.0.2.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
</dependencies></pre>
</div>
<p class="md-end-block md-p"><span class="md-plain">PS:这里使用的数据库是Oracle,然后我们的包管理工具Maven居然不能下载oracle的jar包。。。所以我们只能去官网下载,然后在CMD里,使用Maven提供的命令安装这个jar包。</span></p>
<p class="md-end-block md-p"><span class="md-plain">然后结合Java的Spring框架的特质,设计一个项目结构,并在包k_framework下面实现。</span></p>
<p class="md-end-block md-p"><span class="md-plain">项目结构设计如下:</span></p>
<p class="md-end-block md-p"><img src="https://img2020.cnblogs.com/blog/243596/202103/243596-20210315094211197-1168250501.png" alt="" loading="lazy"></p>
<p class="md-end-block md-p"><span class="md-plain">系统约定如下:</span></p>
<p class="md-end-block md-p"><span class="md-plain">DTO类名后缀需为Command和Query,标记命令用于处理的业务为增删改、或查询。</span></p>
<p class="md-end-block md-p"><span class="md-plain">DTO类必须在同一包下,且类名不得重复。</span></p>
<p class="md-end-block md-p"><span class="md-plain">前台页面必须定义一个同名的,属性一致的Javascript的DTO类。</span></p>
<p class="md-end-block md-p"><span class="md-plain">业务域类名=DTO的类名+Handler。</span></p>
<p class="md-end-block md-p"><span class="md-plain">业务域类使用Excute函数处理业务。</span></p>
<h4 class="md-end-block md-heading"><span class="md-plain">关于结构</span></h4>
<p class="md-end-block md-p"><span class="md-plain">关于配置类与工具类:设计时,我们尽量让控制器使用配置类,让业务域使用工具类。当然,特殊情况下也可以一起使用。</span></p>
<p class="md-end-block md-p"><span class="md-plain">关于业务域:Java中通常使用Service来命名处理业务的包,但因为有时候我们会把部署的Web项目也称为服务,比如微服务项目里每个WebApi都是服务,所以,这里为了避免歧义,使用域来命名处理业务的包。</span></p>
<p class="md-end-block md-p"><span class="md-plain">关于数据库映射:在C#项目里,我们是先建立映射,然后用仓储通过泛型来处理数据库数据,但在Mybatis里,需要使用映射的对象来处理数据库数据,即,每处理一个表,就要建立一个这个表的映射对象实例。</span></p>
<p class="md-end-block md-p"><span class="md-plain">关于数据库实体和数据库扩展实体:顾名思义,数据库扩展实体是数据库实体的扩展,可以的简单把它理解为视图实体。</span></p>
<p class="md-end-block md-p"><span class="md-plain">注:在C#中,图中的这些大类的结构,通常会搞一个类库项目来单独处理,因为在C#中共享使用一个启动项目的配置文件,并且C#的项目文件在VS中管理起来非常简单便捷,但Java的项目文件pom.xml并不是特别灵活,所以,这里我们就在一个项目里做结构。</span></p>
<p class="md-end-block md-p"><span class="md-plain">整体结构图如下:</span></p>
<p class="md-end-block md-p"><img src="https://img2020.cnblogs.com/blog/243596/202103/243596-20210315094226186-1948595354.png" alt="" loading="lazy"></p>
<p style="width: 100%; background: rgba(65, 105, 225, 1); color: rgba(255, 255, 255, 1); height: 50px; font-size: 30px; line-height: 50px">代码实现——逻辑</p>
<h4 class="md-end-block md-heading"><span class="md-plain">工具类</span></h4>
<p class="md-end-block md-p"><span class="md-plain">首先,我们先建立工具类。</span></p>
<p class="md-end-block md-p"><span class="md-plain">因为是简单实现,所以我们只建立三个最基础的工具类,ReflexHelper、StringHelper、FileHelper。(在java中通常工具类命名会以util结尾,这里我保持c#的命名风格)</span></p>
<h4 class="md-end-block md-heading"><span class="md-plain">控制器</span></h4>
<p class="md-end-block md-p"><span class="md-plain">定义CommandController类,Get和Post两个函数,用于处理全部的Get和Post请求。函数接受两个参数,命令类型和命令的Json内容,然后通过命令类型发射调用业务域。</span></p>
<p class="md-end-block md-p"><span class="md-plain">代码如下:</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">@RestController
@RequestMapping("/Command")
public class CommandController
{
@Autowired
private SqlSession sqlSession;
@RequestMapping(value = "/Get", method = RequestMethod.GET)
@ResponseBody
public BaseResult Get(String commandName,String commandJson) throws Exception {
Set<Class<?>> classes = ReflexHelper.getClasses("com.kiba.k_framework.dto");
String newName ="";
for(Class c:classes){
String fullName = c.getName();
System.out.println(fullName);
String className = fullName.substring(fullName.lastIndexOf(".")+1,fullName.length());
System.out.println(className);
if(className.equals(commandName)) {
newName = fullName.replace(".dto.", ".domain.") + "Handler";
System.out.println(newName);
break;
}
}
Class<?> clazz = Class.forName(newName);
Method method = clazz.getMethod("Excute", String.class,SqlSession.class);
BaseResult ret = (BaseResult)method.invoke(clazz.newInstance(), commandJson,sqlSession);
return ret;
}
@PostMapping(value = "/Post")
@ResponseBody
public BaseResult Post(String commandName,String commandJson) throws Exception {
System.out.println(commandName);
Set<Class<?>> classes = ReflexHelper.getClasses("com.kiba.k_framework.dto");
String newName ="";
for(Class c:classes){
String fullName = c.getName();
System.out.println(fullName);
String className = fullName.substring(fullName.lastIndexOf(".")+1,fullName.length());
System.out.println(className);
if(className.equals(commandName)) {
newName = fullName.replace(".dto.", ".domain.") + "Handler";
System.out.println(newName);
break;
}
}
Class<?> clazz = Class.forName(newName);
Method method = clazz.getMethod("Excute", String.class,SqlSession.class);
BaseResult ret = (BaseResult) method.invoke(clazz.newInstance(), commandJson,sqlSession);
return ret;
}
}</pre>
</div>
<p class="md-end-block md-p"><span class="md-plain">如上代码所示,Controller接受到的请求,会被直接发送到业务域处理,也就是说,理论上,这里写完了,就再也不用关注了。</span></p>
<p class="md-end-block md-p"><span class="md-plain">注1:代码一开始使用注解@Autowired实例化了sqlSession,这个对象是mybatis的内部对象,后面会把它发送到业务域,业务域里通过它获取mapper对象,这是因为,我们的业务域是反射调用的,所以在业务里@Autowired注解将失效,它将无法对继承BaseMapper的接口进行实例化。</span></p>
<p class="md-end-block md-p"><span class="md-plain">注2:使用这种结构,我们的AOP除了可以使用@Aspect注解,还可以直接写在Controller里了。</span></p>
<p class="md-end-block md-p"><span class="md-plain">注3:并不是所有项目和团队组成都适用这个的框架。</span></p>
<p style="width: 100%; background: rgba(65, 105, 225, 1); color: rgba(255, 255, 255, 1); height: 50px; font-size: 30px; line-height: 50px">代码实现——数据库</p>
<p class="md-end-block md-p"><span class="md-plain">在本框架中,数据库链接使用Mybatis开源包。 </span></p>
<h4 class="md-end-block md-heading"><span class="md-plain">Mybatis学习</span></h4>
<p class="md-end-block md-p"><span class="md-plain">在使用mybatis之前需要先学习一些知识,搞懂mybatis的一些类库的关系,不然用起来会很迷茫。</span></p>
<p class="md-end-block md-p"><span><strong>mybatis:</strong><span class="md-plain">一个java的orm包。</span></span></p>
<p class="md-end-block md-p"><span><strong>mybatis-spring-boot-starter:</strong><span class="md-plain">一个mybatis工作组为了spring单独开发的包,他让spring框架使用mybatis更简单,springBoot,springCloud等框架都可以用(映射使用注解@mapper,最新版 2.1.2)。</span></span></p>
<p class="md-end-block md-p"><span><strong>mybatis-plus:</strong><span class="md-plain">一个基于mybatis的扩展包,拥有一些在mapper创建后,会自带一些基础的增删改查的方法。</span></span></p>
<p class="md-end-block md-p"><span><strong>mybatis-plus-boot-starter:</strong><span class="md-plain">mybatis-plus工作组为了spring单独开发的包,,他让spring框架使用mybatis-plus更简单,springBoot,springCloud等框架都可以用(映射使用继承BaseMapper,最新版3.42,mybatisplus-springboot-starter是mybatis-plus-boot-starter的增强包)。</span></span></p>
<p class="md-end-block md-p"><span class="md-plain">了解了以上概念后,我们可得知,在springboot项目中使用mybatis,我们有两个选择,即使用mybatis-spring-boot-starter或mybatis-plus-boot-starter。</span></p>
<p class="md-end-block md-p"><span class="md-plain">因为我是C#出身,所以,映射我更倾向于继承,所以下面代码使用的是mybatis-plus-boot-starter。</span></p>
<h4 class="md-end-block md-heading"><span class="md-plain">Mybatis配置</span></h4>
<p class="md-end-block md-p"><span class="md-plain">在resources/application.yml下输入配置代码如下:</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">server:
port: 8088
spring:
servlet:
multipart:
max-file-size: 5000MB
max-request-size: 5000MB
datasource:
driver-class-name: oracle.jdbc.OracleDriver
url: jdbc:oracle:thin:@192.168.1.1:1521/orcl
username: abc
password: 123
# mybatis
mybatis:
mapper-locations: classpath:mapper/**/*.xml</pre>
</div>
<p class="md-end-block md-p"><span class="md-plain">代码中配置了Spring节点下的数据源,配置为Oracle并设置链接账户密码;还配置了mybatis节点下的映射路径。该映射路径下面会用到。</span></p>
<p class="md-end-block md-p"><span class="md-plain">然后配置启动类,增加注解@MapperScan("com.kiba.k_framework.mapper"),如下图:</span></p>
<p class="md-end-block md-p"><img src="https://img2020.cnblogs.com/blog/243596/202103/243596-20210315094415064-1537282092.png" alt="" loading="lazy"></p>
<h4 class="md-end-block md-heading"><span class="md-plain">数据库实体</span></h4>
<p class="md-end-block md-p"><span class="md-plain">接着我们建立数据库实体,属性跟数据库表字段一样即可。但Java里写属性太麻烦,所以这里使用了@Data注解,被注解的类下,只要写私有字段即可,编译时会为我们生成首字母大写的属性,并且编写代码时,还可以点出【getName()/setName()】这样的方法来获取或设置属性的值。代码如下:</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">package com.kiba.k_framework.entity;
import lombok.Data;
@Data
public class Kiba_User {
private int id;
private String name;
}</pre>
</div>
<p class="md-end-block md-p"><span class="md-plain">如果是第一次使用Idea,我们编写代码时,在对象的后面是点不出【getName()/setName()】这样的方法的,这是因为,我们没有安装lombok插件,安装插件在File—Setting中,如下图所示。</span></p>
<p class="md-end-block md-p"><img src="https://img2020.cnblogs.com/blog/243596/202103/243596-20210315094546477-1514961249.png" alt="" loading="lazy"></p>
<h4 class="md-end-block md-heading"><span class="md-plain">映射类</span></h4>
<p class="md-end-block md-p"><span class="md-plain">映射类,顾名思义,就是建立实体与数据库关系的类,在这里类中会指定实体类与数据库表的关系,和实体字段和表字段的关系(通常情况是同名映射)。不过在Java里,映射类除了要处理映射关系,还要担任数据库访问的角色,而C#的映射类就是处理映射关系,访问数据库则有数据库上下文实体负责,说实话,Java这种模式是有点奇怪,不过用久了也就无所谓了。</span></p>
<p class="md-end-block md-p"><span class="md-plain">映射类代码如下:</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">public interface Kiba_UserMapper extends BaseMapper<Kiba_User> {
@Select("select * from Kiba_User")
List<Kiba_User> test();
@Select("select * from Kiba_User where id=#{value}")
List<Kiba_User> test2(Integer id);
List<Kiba_User> test3(Integer id);
}</pre>
</div>
<p class="md-end-block md-p"><span class="md-plain">如上代码所示,映射类通过继承泛型BaseMapper<Kiba_User> 实现了数据库实体和表的映射。</span></p>
<p class="md-end-block md-p"><span class="md-plain">然后代码里定义了三个方法,都是查询数据库数据。</span></p>
<p class="md-end-block md-p"><span class="md-plain">第一个方法—test:在方法上加了@Select注解,并且在注解里编写sql语句,这样调用这个方法时,就会执行注解里的语句。</span></p>
<p class="md-end-block md-p"><span class="md-plain">第二个方法—test2:方法2多了一个入参,注解里多了一个查询条件, 注解里通过#{value}的方式使用了入参的值。看到这,我们可以发现,注解里有自己的方言,即注解里还有一套自己的语法,这显然明目张胆的增加了开发者的学习内容,我表示反对,但无效。</span></p>
<p class="md-end-block md-p"><span class="md-plain">第二个方法—test3:这个方法没有注解,但有对应的XML配置文件,什么是XML配置文件?</span></p>
<p class="md-end-block md-p"><span class="md-plain">如下图所示,里面有两个同名,但后缀名不同的文件,下方的Kiba_UserMapper.xml文件就是,Kiba_UserMapper.java的xml配置文件,这两个文件编译的时候会被捏成一个类。系统根据什么把他们捏一起的呢?还记得我们上面的配置吗?我们配置了一个映射扫描包和一个映射配置路径,系统就是根据它俩的扫描文件结果,然后把同名的捏到一起的。</span></p>
<p class="md-end-block md-p"><img src="https://img2020.cnblogs.com/blog/243596/202103/243596-20210315094628975-32777199.png" alt="" loading="lazy"></p>
<p class="md-end-block md-p"><span class="md-plain">现在我们看一下Kiba_UserMapper.xml的内容。</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;"><?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.kiba.k_framework.mapper.Kiba_UserMapper">
<select id="test3" resultType="com.kiba.k_framework.entity.Kiba_User" parameterType="Integer">
select * from Kiba_User where id=#{value}
</select>
</mapper></pre>
</div>
<p class="md-end-block md-p"><span class="md-plain">如代码所示,在mapper标签里配置了命名空间——com.kiba.k_framework.mapper.Kiba_UserMapper,即它明确指定了这个XML要和谁捏在一起。(java里命名空间+类名还是命名空间)</span></p>
<p class="md-end-block md-p"><span class="md-plain">然后在mapper标签里配置了一个select标签,【id="test3"】标记了它对应的函数名,resultType和parameterType标签标识这他们对应的这个函数传入传出类型,然后内容是一个带方言的sql语句。</span></p>
<p class="md-end-block md-p"><span class="md-plain">看到这里,我们可以得出,这个xml的select标签是等于@select注解的,即为函数设置sql语句有两种方式,一种是注解一种是xml文件配置,因为上面的映射类中的前两个方法已经有注解了,所以,xml配置文件中并没有重复配置。</span></p>
<p class="md-end-block md-p"><span class="md-plain">这个模式嘛,是有点,呃。。。是比较特别。</span></p>
<h4 class="md-end-block md-heading"><span class="md-plain">业务域</span></h4>
<p class="md-end-block md-p"><span class="md-plain">现在我们在业务域里使用一下映射类来获取数据。</span></p>
<p class="md-end-block md-p"><span class="md-plain">代码如下:</span></p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">public class GetUserQueryHandler implements IHandler
{
@Override
public GetUserQueryReuslt Excute(String commandJson, SqlSession sqlSession) {
Kiba_UserMapper mapper = sqlSession.getMapper(Kiba_UserMapper.class);
List<Kiba_User> users = mapper.test();
GetUserQueryReuslt ret=new GetUserQueryReuslt();
ret.setUsers(users);
ret.setSuccess(true);
return ret;
}
}</pre>
</div>
<p class="md-end-block md-p"><span class="md-plain">这里使用sqlSession.getMapper(Kiba_UserMapper.class)来获取我们的maper实例,然后下面就可以正常调用他下面的方法了。</span></p>
<h4 class="md-end-block md-heading"><span class="md-plain">测试</span></h4>
<p class="md-end-block md-p"><span class="md-plain">现在我们启动项目,用postman测试一下。</span></p>
<p class="md-end-block md-p"><span class="md-plain">输入<span class="md-link">http://localhost:8088/Command/Get?commandName=GetUserQuery&commandJson=<span class="md-plain">{}进行测试,得到结构如下图所示:</span></span></span></p>
<p class="md-end-block md-p"><img src="https://img2020.cnblogs.com/blog/243596/202103/243596-20210315094714497-1895731028.png" alt="" loading="lazy"></p>
<p class="md-end-block md-p"><span class="md-plain">测试成功,我们成功的通过发送DTO实体实现了业务查询。</span></p>
<p style="width: 100%; background: rgba(65, 105, 225, 1); color: rgba(255, 255, 255, 1); height: 50px; font-size: 30px; line-height: 50px">结语</p>
<p class="md-end-block md-p"><span class="md-plain">在使用Java的时候,我总感觉像回到了旧社会,错误提示、开发工具的使用、工程文件的管理等等都很不友好。Spring框架看上去很简洁,但因为这些不友好的朋友在中间阻碍着,整体的开发进度,并没有想象中那么快速。也许,我们都被微软宠坏了吧。</span></p>
<p>----------------------------------------------------------------------------------------------------</p>
<p class="md-end-block md-p">到此,到此Java框架的开发就已经介绍完了。</p>
<p>代码已经传到Github上了,欢迎大家下载。</p>
<p style="border: 2px solid rgba(115, 191, 0, 1); padding: 10px 40px; background: rgba(204, 255, 128, 1); border-radius: 15px; -moz-border-radius: 15px">Github地址: https://github.com/kiba518/Kiba_Java_Framework</p>
<p>----------------------------------------------------------------------------------------------------</p>
<p>注:此文章为原创,任何形式的转载都请联系作者获得授权并注明出处!<br>若您觉得这篇文章还不错,请点击下方的<span style="color: rgba(255, 0, 0, 1)">【<strong>推荐】</strong></span>,非常感谢!</p>
<p>https://www.cnblogs.com/kiba/p/14518906.html</p>
<p> <img src="https://img2018.cnblogs.com/blog/243596/201909/243596-20190904083750507-629449790.png" alt=""></p>
<p> </p>
</div>
<div id="MySignature" role="contentinfo">
https://www.cnblogs.com/kiba/<br><br>
来源:https://www.cnblogs.com/kiba/p/14518906.html
頁:
[1]