Flutter_web加Java Spring 整站前后台开发经验梳理

Flutter_web加Java Spring 整站前后台开发经验梳理

这段时间开发了一个公司内部使用的网站,本着提前探索熟悉(踩坑)未来的全栈UI框架-Flutter的愿望,使用了Flutter for Web作为前端框架,后台部分则循规蹈矩的用了java Spring。目前已经开发完成,在此简要记录一下开发过程中积累的一些经验。

突然发现,我现在不仅会android,又会java后端,又能用flutter写web+android+ios代码,一下子变成了全栈工程师喽!

前端部分——Flutter

官网链接

Flutter的官方入门文档是 https://flutter.dev/docs/get-started/install
现在有中文版可以看,中文链接是: https://flutterchina.club/setup-macos/
Flutter的Github地址是: https://github.com/flutter/flutter
Stack OverFlow上flutter分类地址: https://stackoverflow.com/questions/tagged/flutter
为 Java 开发人员准备的 Dart 教程: https://codelabs.flutter-io.cn/codelabs/from-java-to-dart-cn/index.html#0

问题排查方式

很多时候遇到问题,去github的issue里搜一下,都能找到解决方案。在我的开发过程中,大多数问题都是通过翻issue来找到解决方案的,少部分能在Stack Overflow上找到。这两个地方都找不到的时候,一般就代表着该问题没有现成的解决方案,需要自己翻源码排查了。

安装Flutter

官网上Flutter的安装方式写了很长一页,但是实际上完全不需要这么麻烦。现在Flutter的安装已经非常简单了。
只需要打开Android Studio,在Preferences-plugins里找到flutter安装即可。
安装flutter插件
安装好后重启ide,选择创建一个Flutter工程,然后在创建过程中就会提示你下载Flutter Sdk,按照提示一步步下载安装即可。

创建Flutter工程
安装Flutter开发环境,就随着创建第一个Flutter工程的同时自动完成了。

创建Flutter_web工程

Flutter for Web 和 移动端的Flutter 现在没有在一个仓库里。而是从移动端Flutter仓库分裂出来,并且做了部分修改。
Flutter_web地址: https://github.com/flutter/flutter_web

Flutter web工程的创建同样非常简单,首先要建议下载Intellij idea,然后与Android studio一样,安装flutter插件。
新建一个project,类型选择Dart->Flutter Web App,上面的Dart SDK path路径选择flutter sdk目录下flutter/bin/cache/dart-sdk目录,然后按提示输入工程名即可。
创建flutter_web工程
如果不是新建项目,而是引入已经创建好的项目,则连intelij idea都不用下载,直接使用android studio打开已经创建好的项目就会自动识别为flutter_web工程,全自动!

到目前为止,flutter_web项目就已经能正常运行了。直接点这个按钮就能跑起来了。
运行

另外建议安装flutter_web github上的readme所说,安装webdev
只需要一行命令flutter pub global activate webdev即可安装。
运行这条命令,需要先把flutter命令放到环境变量path里,到安装的flutter sdk目录下,把相应的地址写到path内即可。
然后再把$HOME/.pub-cache/bin目录也放到环境变量path里,就能在任意目录的命令行下使用webdev了。

编译release包

默认情况下,开发中使用的都是用于开发模式下的编译器Dart Dev Compiler,它专为快速,增量编译和简单调试而设计。
而release包是应该使用Flutter_web的发布编译器 dart2js的。release模式下打出来的包,性能更快、兼容性更好、包大小也小。

编译release包需要先按上一步安装好webdev命令。
然后运行webdev build命令即可。
这时会在build目录下,生成对应的html,js等文件。

如果只是想本地测试下release模式下的代码运行情况,直接运行webdev serve -r即可。

偶尔编译报错的解决办法

  1. 一般编译报错,可以先看一下pubspec.yaml文件写的对不对,是不是有语法错误或者依赖冲突。修改完pubspec.yamlIDE一般会自动提示要packages get的,这一步相当于gradle的sync,点一下就好了。如果没有提示的话,可以手动在工程目录下执行flutter packages get命令。
  2. 另一种情况,可能是编译的缓存出错了,新的编译生成文件没有自动覆盖。android里用gradle编译也经常这样,gradle报错一般clean一下就好了,dart也一样,dart里的clean在IDE里没有按钮,要在工程目录下执行命令pub run build_runner clean

Flutter for web项目与Flutter for Android、IOS项目的迁移

虽说Flutter for web与Flutter for Android、IOS并不在一个git仓库里,略有不同。但是基本语法都是相通的。

最大的不同有两部分:

  1. import sdk中包的路径不同,Flutter for Android、IOS 中路径是 flutter/xxx ,而Flutter for web中是flutter_web/xxx.
    比如常用的material.dart包。
    在Flutter for Android、IOS 中 这么写:import 'package:flutter/material.dart';
    在Flutter for web中这么写:import 'package:flutter_web/material.dart';

  2. 部分第三方库,只支持Flutter for Android、IOS,而不支持Flutter for web。

基于以上两点,在没有使用多少第三方库的情况下,Flutter for web项目与Flutter for Android、IOS项目只要批量替换import包中的flutter和flutter_web字符串即可相互迁移。
我测试了写的flutter_web中的部分界面,修改import包中的字符串后,放到手机上,可以完美运行,并且UI上几乎没有区别。

Flutter这种基于Skia重写渲染引擎的模式,确实能实现高度的三端一致性。

具体差异详情可参阅官方文档:
https://github.com/flutter/flutter_web/blob/master/docs/faq.md
https://github.com/flutter/flutter_web/blob/master/docs/migration_guide.md

快速理解UI规则

Flutter的布局思想,是借鉴Flex的。

建议先看一下这篇文章,来熟悉一下Flex布局,这对学习Flutter的布局是很有帮助的。
http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html

学习完Flex之后,就能大概理解常用的几个熟悉的参数:

  • mainAxisAlignment:主轴方向上的布局方式(
    • start:居头,
    • end:居末,
    • center:居中,
    • spaceBetween:两侧无留白的平均分布,
    • spaceAround:两测小留白的平均分布,
    • spaceEvenly:两测大留白的平均分布
  • mainAxisSize:主轴方向上的大小(
    • min:对应warp_content,
    • max:对应match_parent
  • crossAxisAlignment:副轴上的布局方式(
    • start:居头,
    • end:居末,
    • center:居中,
    • stretch:拉伸,
    • baseline:基准线对齐
      )

另外,所有的alignment都是指子控件的摆放方式,对应于android中,就是只有gravity属性,而没有layout_gravity属性。

对于android开发来说,将Flutter中的布局组件,建立与对应的Android组件的对应关系,更容易理解。

常用的布局:
  • Row、 Column、 Flex 都对应于Android的LinearLayout,分别是横向,纵向,可按比例摆放的LinearLayout。
  • Container 、 Stack都对应的Android的FrameLayout,分别是一个子view和多个子view重叠的FrameLayout。
  • ListView、GridView对应于Android的ListView、GridView。
常用的控件:
  • Text:文本控件
  • Image:图片控件
  • RaisedButton、OutlineButton、FlatButton:按钮控件
  • TextField:输入框
  • RadioListTile:单选框
  • Checkbox:复选框
  • DropdownButtonHideUnderline:下拉框
  • Scaffold.of(context).showSnackBar(SnackBar(content: const Text("弹出SnackBar"))):SnackBar控件
  • showDialog( context: context, builder: (context) { return SimpleDialog( children: <Widget>[ Text("Dialog"), ], ); });:Dialog弹框
    通过使用以上组件,基本上常用的布局就能完成了,

需要注意的是,Flutter中没有相对布局,在Android中用相对布局实现的场景,Flutter中可能要找其他方式实现。

对于写UI代码很困难的朋友,也可以尝试下这个拖控件生成flutter代码的在线工具,作为辅助。
https://flutterstudio.app/
在这里插入图片描述

网络请求

Flutter中网络请求库主要有三个:

  1. dart:io
  2. package:http/http.dart
  3. package:dio/dio.dart
    Flutter_web中只能使用package:http/http.dart。

使用起来非常方便发请求只需要一行代码即可:http.get(url)

Response response = await http.get(testUrl);
print(response.body);

await async关键字

对于android和java开发来说,await async关键字比较陌生。但是对于熟悉js和C#的开发者来说,对await async用法可能已经很熟悉了。

await async关键字是一种异步操作的简洁写法,这实际上是一种语法糖。

要明白await async关键字,首先要先看一个Future数据结构,它有一个泛型声明,这个数据结构的对象表示未来某时刻会返回T类型的数据。大家可以把它和RxJava的Observable类比,两者是非常类似的。
比如 这个声明 Future<String> getUserName(),就表示这个方法会立即返回一个Future对象,而这个对象未来会返回String类型的一个结果。
这个getUserName方法的调用是可以这么写的:

   NetApi.getUserName().then((response) {
      print(response);
    });

在这种使用方式上,是和RxJava极度类似的。

而await async关键字,在这个基础上,又进一步简化了写法。
使用await关键字,可以这么写。

    var response = await NetApi.getUserName();
    print(response);

这种写法与上面的实现效果是完全一样的,编译器在编译时对这个语法糖进行降糖解析。

await关键字,可以理解为把当前函数体内,await关键字之后所有的函数调用,都放到了一个隐含的.then(response){ ... }代码块中。

而当该函数内使用了await关键字后,该函数的返回值,肯定也变成了“未来某时刻才会返回”。这样其他函数调用该函数时也要使用await关键字,或then来使用。dart里规定了,这种使用了await关键字的函数,要在自身声明上标记async关键字,这样其他函数才能明确知道,是否该使用await关键字,或then了。

Json解析

Flutter中不支持反射,所以java中常用的通过反射来生成json,解析json的方式,全都不能用了。
要在Flutter中使用Json,只能硬编码。
听起来硬编码Json生成、解析,似乎非常恐怖。但是实际上,这种硬编码的代码,都是非常有规律性的,所以完全可以通过工具自动生成。

  1. 已有Json,生成Dart代码
    常见的通过Json生成Dart代码的工具非常多,我在这里举两个我用的。
    https://javiercbk.github.io/json_to_dart/
    https://app.quicktype.io/
  2. 已有dart model代码,生成toJson和fromJson方法。
    通过Dart Json Serialization Generator插件即可实现该功能。
    Json插件操作步骤
  3. 有的时候,还想把已有的java代码直接转换成dart代码。 鉴于dart和java的语法规则实际上是非常相似的,这个工具也有人已经实现了。
    http://sma.github.io/stuff/java2dartweb/java2dartweb.html
    我测试过,普通的java bean是能完美转换成dart的,这对于从android上迁移逻辑到flutter上,是非常方便的。

样例代码

对于初学一个语言、框架来说,样例代码是重要的学习资料。
Flutter_web给出了几个sample,可做学习之用。
https://flutter.github.io/samples/
对应的源码在这里,https://github.com/flutter/samples/tree/master/web,可以clone下来查看。
其中对各种组件整理的最全的是flutter web gallery
https://flutter.github.io/samples/gallery/
在我的开发过程中,经常会到gallery上找到类似的界面,然后再相应的看其源码学习。

在Flutter中找不到相应的控件怎么办

现在Flutter还是一个新生框架,相应的库肯定是没有Java、Android这么全面的,经常有找不到某些控件或者功能的情况。
这个时候一般有这样几种选择:

  1. https://pub.dev/网站搜索三方库,这个网站区分了Flutter for web项目与Flutter for Android、IOS两种筛选条件,还有每个库的评分。并且找到的库直接修改项目的pubspec.yaml文件即可引入,非常方便。
  2. 看看当前的需求能不能灵活一点,使用其他方式实现,尽量用已有的基础组件组装出想要实现的功能。
  3. 当前控件仅有少数参数没有满足需求,可以把sdk中整个类拷贝出来,改个名字即可对源码进行二次加工,dart对代码的访问性限制没有java那么大,拷贝出来到自己工程目录一般也是能正常运行的。实在不行还可以直接改sdk内代码,只是几个人协同开发的话,所有人的sdk都要相应的改动,这点和python是很像的。
  4. 使用platformView来引入平台组件,对于web来说,平台组件就是html。
    这是官方一个引入YouTube播放控件的项目。
    https://github.com/flutter/flutter_web/tree/master/examples/html_platform_view
    代码很简单,只有十几行:
void main() {
  ui.platformViewRegistry.registerViewFactory(
      'hello-world-html',
      (int viewId) => IFrameElement()
        ..width = '640'
        ..height = '360'
        ..src = 'https://www.youtube.com/embed/IyFZznAk69U'
        ..style.border = 'none');

  runApp(Directionality(
    textDirection: TextDirection.ltr,
    child: SizedBox(
      width: 640,
      height: 360,
      child: HtmlView(viewType: 'hello-world-html'),
    ),
  ));
}

这种情况下html和dart的交互要使用EventMessage了,
js方代码

        window.parent.postMessage({//发消息
            "content": "test"
        }, "*");
        window.addEventListener("message", function (ev) {//注册消息监听
        });

dart方代码

    IFrameElement createdView = ui.platformViewRegistry.getCreatedView(viewId);
    createdView.contentWindow.postMessage("test", "*");//发消息
    window.addEventListener("message", (Event event) {});//注册消息监听

后端部分——Java Spring

后端部分使用Java Spring开发。Java对于我这样一个Android开发来说,使用起来自然是非常轻松。Spring被使用了这么多年,相关技术早就非常成熟了,几乎可以说是开箱即用。我们并没有遇到多少困难。

工程创建

使用Spring Initializr配置一下,就可以生成一个基本Spring工程框架,然后在此基础上实现自己要的业务逻辑即可。

在这里,只需要额外选择基本的MyBatis、Spring Web Starter、MySQL Driver即可,其他有需要的框架可以需要的时候再加。

http接口开发

Spring中可以使用注解非常方便的配置各种功能。
写http接口时,也用到了很多注解。
如下面的代码,用很简单的方式,就生成了一个接口。
其中@RestController表示当前类作为一个rest接口的Controller,@RequestMapping表示接口的路径,@RequestParam表示get请求的参数。

@RestController
@RequestMapping("/test/demo")
public class TestController {
    @RequestMapping(value = "greet", method = RequestMethod.GET)
    Map<String, Object> greet(@RequestParam String name) {
        HashMap<String, Object> response = new HashMap<>();
        response.put("code", "0");
        response.put("msg", "ok");
        response.put("body", "hello " + name);
        return response;
    }
}

该接口请求结果如下:
请求结果

数据库操作

上面的实现了常见的http请求,但是还没有数据库相关操作的参与,实际开发中,大多数接口的实际实现,都是数据库的操作。

这里,需要先加上相关配置。

  1. 在Application类上,增加注解@ImportResource("classpath:applicationContext.xml"),表示使用applicationContext.xml作为context配置文件。
    applicationContext.xml配置如下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                 http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
                 http://www.springframework.org/schema/context
                 http://www.springframework.org/schema/context/spring-context-4.2.xsd">
    <context:component-scan base-package="com.example.demo"/><!--spring注解类搜索的路径-->
    <import resource="classpath*:application_dao.xml"/><!--数据库相关配置文件-->
</beans>
  1. 编写application_dao.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://123.123.123.123:3306/dataBaseName?characterEncoding=utf8"/><!--数据库连接串-->
        <property name="username" value="root"/><!--数据用户名-->
        <property name="password" value="root12345"/><!--数据库密码-->
    </bean>
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath:mapper/*_mapper.xml"/><!--mapper文件位置-->
    </bean>
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.example.demo"/><!--注解和javaBean类搜索目录-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>
</beans>
  1. mapper配置
    test_mapper.xml
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.DemoModelMapper"><!--对应的Mapper Java类名-->
    <resultMap id="demoModel" type="com.example.demo.demoModel"><!--将"com.example.demo.demoModel"类做映射,名称是“demoModel”-->
        <id property="id" column="id"/><!--java变量id和数据库字段id做映射-->
        <result property="viewCode" column="view_code"/><!--java变量viewCode和数据库字段view_code做映射-->
        <result property="title" column="title"/><!--java变量title和数据库字段title做映射-->
    </resultMap>
    <sql id="table_name">test_table</sql><!--定义table_name的值为test_table-->
    <select id="getDemoModels" resultMap="DemoModel"><!--mapper类中getDemoModels接口的实现,返回值是上面定义过的demoModel类型-->
    <!--具体sql-->
        select *
        from
        <include refid="table_name"/><!--使用上面的定义的table_name值,即test_table-->
        where
        view_code=#{viewCode} <!--筛选条件,使用“#{}”符号从接口调用中取到相应参数的值-->
    </select>
</mapper>

做好以上xml配置后,还需要定义Mapper接口类。

@Mapper //表示该类是Mapper类
public interface DemoModelMapper {
    List<DemoModel> getDemoModels(String viewCode);
}

之后就可以直接使用该接口操作数据库了,接口的实现,会由Spring框架根据xml配置自动注入。
我们将上面的TestController略微修改,来调用该mapper操作数据库。

@RestController
@RequestMapping("/test/demo")
public class TestController {
    @Resource
    private DemoModelMapper demoMapper; //不需要手动实例化,Spring会根据注解和配置自动注入
    @RequestMapping(value = "greet", method = RequestMethod.GET)
    Map<String, Object> greet(@RequestParam String viewCode) {
        HashMap<String, Object> response = new HashMap<>();
        response.put("code", "0");
        response.put("msg", "ok");
        response.put("body", demoMapper.getDemoModels(viewCode));
        return response;
    }
}

如此,就完成了一个可从数据库查找数据的http接口。

集成Flutter_web打包产物

我们虽然采用了前后端分离的方式开发,但是在发布时,采用了合并到一起,发布到一台服务器上的形式来上线。这种方式虽然不利于前后端分开迭代,但是在接入我们内部一些权限管理、自动部署等组件时,具有巨大的便利性。

首先使用上面提到过的webdev build命令,将Flutter_web项目编译成html和js等生成产物。

然后将这些生成产物,统一复制到JavaProject/demo/src/main/resources/static目录下。这样,java项目运行后,直接访问http://127.0.0.1:8080地址,就可以访问到网站主页面了。

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页