0%

在Apple M芯片上运行Oracle数据库

当前,Oracle数据库并不支持在Apple M芯片上运行,使用Docker也无法使用,需要使用
colima。

安装colima

1
brew install colima

启动colima

1
2
# 基于x86_64架构,使用内存为4GiB
colima start --arch x86_64 --memory 4

使用docker启动oracle数据库

1
docker run -d -p 1521:1521 -e ORACLE_PASSWORD=<your password> -v oracle-volume:/opt/oracle/oradata gvenzl/oracle-xe

一个简单的启动脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#!/bin/bash -e

VERSION=latest
PASSWORD=sys

usage() {
cat << EOF

Run Oracle database on Apple M chip.

Parameters:
-p: oracle database password, default for 'sys'
-v: oracle-e version
EOF
}

while getopts "hp:v:" optname; do
case "${optname}" in
"h")
usgae
exit 0
;;
"p")
PASSWORD="${OPTARG}"
;;
"v")
VERSION="${OPTARG}"
;;
esac
done

CONTAINER_RUNTIME=$(which docker 2>/dev/null) ||
{
echo "There is no docker in your PATH"
exit 1
}

arch=$(uname -m)
if [ $arch = "arm64" ]
then
echo "Run oracle database on Apple M chip."

COLIMA=$(which colima)
if [ -z $COLIMA ]
then
echo "No colima, try to install..."

brew_run=$(which brsew) && { ${brew_run} install colima; } ||
mac_port_run=$(which port) && { ${mac_port_run} install colima; } ||
nix_run=$(which nix-env) && { ${nix_run} -iA nixpkgs.colima; } ||
{
echo "Colima is available on Homebrew, MacPorts, and Nix."
echo "But now, can't found any of them."
exit 1
}
fi

colima start --arch x86_64 --memory 4
fi

if [ -z $PASSWORD ]
then
PASSWORD=sys
fi

PORT=1521
${CONTAINER_RUNTIME} run -d -p ${PORT}:1521 -e ORACLE_PASSWORD=${PASSWORD} gvenzl/oracle-xe:${VERSION}

echo "Oracle-xe start with port ${PORT}"
echo "Oracle password: ${PASSWORD}"

# stop and delete colima instance
# colima stop
# colima delete
# This is not a good idea delete colima instance, delete instance will delete
# all context for colima, of course, include image in this instance.

这个脚本暂时没有编写结束相关,参数相关内容,所以只能用作简单的启动脚本

Appendix

  • Colima - container runtimes on macOS (and Linux) with minimal setup.
  • Oracle-xe - Oracle-xe image

首先,介绍一下CMAKE_BUILD_TYPE,在CMake的官网中有一段用来指出它是用于single-configuration generators的。

这里,区分一下single-configuration generatormulti-config generator
single-configuration generator在构建平台与环境的同时会生成构建类型,如Debug
Release,常见的在MacOSX上使用CLion时,默认情况下生成的是只有Debug构建环境的,而CLion默认使用的
是Ninja做为生成器,这里一个明显的特定是无法直接切换Debug与Release,如果要启用Release则需要
构建、执行、部署Cmake创建一个新的配置。multi-config generator则想Visual Studio一样,混合了多种
配置。

CMAKE_BUILD_TYPE只在single-configuration generator起作用,如下这一段
在MSVC下是错误的,CMAKE_BUILD_TYPE无法在MSVC中获取值。

1
2
3
4
5
6
# WARNING: This is wrong for multi-config generators because they don't use
# and typically don't even set CMAKE_BUILD_TYPE
string(TOLOWER ${CMAKE_BUILD_TYPE} build_type)
if (build_type STREQUAL debug)
target_compile_definitions(exe1 PRIVATE DEBUG_BUILD)
endif()

在multi-config generator中,只能在构建时选择构建类型,这里,
需要借助Generator expressions

1
2
3
4
# Works correctly for both single and multi-config generators
target_compile_definitions(exe1 PRIVATE
$<$<CONFIG:Debug>:DEBUG_BUILD>
)

总的来说,在multi-config generator中,因为需要在构建过程中选择构建类型,直接的提供
CMAKE_BUILD_TYPE是无效的,因为构建类型是时刻在改变,这里需要借助Generator expressions
来动态修改构建类型

链接

这是一篇基于Spring openfeign,关于Spring的一些常用组件,类,编写方式的笔记与源码阅读。

Spring openfeign封装了openfeign,这里暂时不讨论关于openfeign的详细实现,主要考虑
Spring通过什么方法将openfeign注入的。

Spring使用openfeign通过注解@EnableFeignClients,EnableFeignClients使用Import注解
,包含一个ImportBeanDefinitionRegistrar的实现类FeignClientsRegistrar。这里出现了
第一个关键接口ImportBeanDefinitionRegistrar,可以使用Import导入配置。
我们关注它的实现方法registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry),如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
// 调用方法
registerFeignClients(metadata, registry);
}

public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
}
else {
// 这里,通过EnableFeignClients中的clients()方法直接创建了BeanDefinition(BeanDefinition
// 是非常重要的关于Bean的一个类,请阅读Spring源码)
for (Class<?> clazz : clients) {
// AnnotatedGenericBeanDefinition是BeanDefinition的实现类。
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}

for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition beanDefinition) {
// verify annotated class is an interface
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
// 看到这个断言,@FeignClient只能直接接口
Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");

Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());

String name = getClientName(attributes);
String className = annotationMetadata.getClassName();
registerClientConfiguration(registry, name, className, attributes.get("configuration"));

registerFeignClient(registry, annotationMetadata, attributes);
}
}
}

可以看到在registerFeignClients尝试获取EnableFeignClients注解,这里,并没有正式加载Feign client,只是获取一个全局的
配置,扫描包路径等,比较特殊的方法是clients(),直接获取关于@FeignClient的类。

接下来是注册Bean到容器,FeignClientRegister提供了两种注册,分别是eagerlyRegisterFeignClientBeanDefinition(className, attributes, registry)
lazilyRegisterFeignClientBeanDefinition(className, attributes, registry),选择哪一种由
spring.cloud.openfeign.lazy-attributes-resolution决定,默认为false,使用eagerlyRegisterFeignClientBeanDefinition。
这里需要注意一点,不论哪一种方法,其实都是注册了一个FactoryBean的子类,并不是直接注册Feign Client。

最后,我们发现,Spring的openfeign依然是通过feignClientFactory完成。

成员解析

primarySourcessources

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class SpringApplication {

//...

private Set<Class<?>> primarySources;

private Set<String> sources = new LinkedHashSet<>();

//...

public void addPrimarySources(Collection<Class<?>> additionalPrimarySources) {
this.primarySources.addAll(additionalPrimarySources);
}

public void setSources(Set<String> sources) {
Assert.notNull(sources, "Sources must not be null");
this.sources = new LinkedHashSet<>(sources);
}
}

primarySources与sources最直观的区别就是一个是Class集合,一个是String集合,但是两个变量
都是用来构建ApplicationContext。另一个则是,primarySources可以追加,但是sources则会被
后一个sources覆盖。

ApplicationContextFactory - 初始化ApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
   // SpringApplication.class
protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
// BeanDefinitionLoader是用于load环境的帮助类
BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
loader.load();
}

BeanDefinitionLoader -

本文主要解析一下有关于Java的SPI技术。

ServiceLoader继承了Iterable,所以,在基本的使用时可以将它理解为一个迭代器,现在,
要关注的是ServiceLoader是如何加载类并实例化的。

ServiceLoader内部依然是通过ClassLoader#getResources()查询实现了接口的类全限定路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

private Class<?> nextProviderClass() {
if (configs == null) {
try {
// 注意这里的PREFIX被限定为META-INF/services
String fullName = PREFIX + service.getName();
if (loader == null) {
configs = ClassLoader.getSystemResources(fullName);
} else if (loader == ClassLoaders.platformClassLoader()) {
// The platform classloader doesn't have a class path,
// but the boot loader might.
if (BootLoader.hasClassPath()) {
configs = BootLoader.findResources(fullName);
} else {
configs = Collections.emptyEnumeration();
}
} else {
configs = loader.getResources(fullName);
}
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return null;
}
pending = parse(configs.nextElement());
}
String cn = pending.next();
try {
return Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service, "Provider " + cn + " not found");
return null;
}
}

META-INF/services

主要设计的类DefaultSingletonBeanRegistryAbstractBeanFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {

//...

/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);


}

Spring无法解决的循环依赖问题,当两个循环依赖的类的注入方式是构造器注入时则无法解决循环依赖问题,
因为在类初始化时将直接导致初始化失败,因为无法查询和创建需要被注入的依赖。

首先,这只是一个简单的笔记,用于记录dpkg的使用,所以并可能并不会详细展开。

dpkg是Debian的包管理工具,同时它适用与Ubuntu,最主要的用途是打包需要安装的运行库,执行程序,
同时用于创建,删除,管理Debian包。

dpkg-deb - Debian包归档工具

dpkg-deb用于归档生成.deb文件,用于dpkg(这里可以直接适用dpkg调用dpkg-deb)

dpkg包结构

1
2
3
4
5
6
7
8
9
10
<deb_dir>
|
|-----DEBIAN
| |-----control
|
|-----opt
| |-----lib_name
| |-----bin
| |-----include
| |-----lib

整个包结构中之后DEBIAN目录是必须的,DEBIAN目录下包含一个control文件,用于描述deb文件,
其他的目录将在安装时在根目录展开,如deb包下的/opt/lib_name路径将在被复制到/opt下,如果
lib_name不存在,同时创建lib_name目录。所以,你可以把deb下的目录理解成根目录的映射。

这里主要讨论一下在MacOSX上编译动态运行库后,在移动库后无法找到相应依赖的问题。其实
本质上还是无法在指定的目录中查找到依赖库(与Windows上不同的一点时,在Windows上可以
通过环境变量PATH,在PATH中搜索被依赖的运行库,同时,Windows上的依赖库还会自动
搜索运行库所在目录下是否存在依赖库)。

CMAKE_BUILD_WITH_INSTALL_RPATH

Spring Cloud Gateway基于Spring webflux

Route Predicate Factories

默认情况下,RoutePredicateFactory的实现通过org.springframework.cloud.gateway.config.GatewayAutoConfiguration
被注入到运行环境中。

RoutePredicateFactory - 创建Predicate

RoutePredicateFactory是所有Predicated factory的基类,提供一个default的方法name(),
通过改方法用于区分获取需要的RoutePredicateFactory

1
2
3
4
5
6
7
8
9
10
@FunctionalInterface
public interface RoutePredicateFactory<C> extends ShortcutConfigurable, Configurable<C> {

//...

default String name() {
return NameUtils.normalizeRoutePredicateName(getClass());
}

}

Route RouteLocator

核心初始化组件RouteDefinitionRouteLocator,它是RouteLocator的一个子类, 用于创建需要的
Predicate, Filter,并将它们与uri关联,创建一个可用的Route。Route通过静态内部类builder创建

RouteDefinitionLocator

Windows下忘记mysql的root密码(–skip-grant-tables)

首先,需要注意my.ini的位置,一般,my.ini会在两个地方,第一个位置是在安装mysql服务的位置(举例,
C:/Program Files/MySQL/MySQL Server X.Y),第二个位置地方则是Data的同级目录下(举例,
datadir的路径是C:/ProgramData/MySQL/MySQL Server 8.0/Data,则my.ini的位置是
C:/ProgramData/MySQL/MySQL Server 8.0/my.ini)。

如果my.ini不在第一个位置,则在后面需要使用–defaults-file

跳过grant tables

在windows上使用--skip-grant-tables的同时必须同时使用--shared-memory--named-pipe
的一个。

mysql –defaults-file=”C:\ProgramData\MySQL\MySQL Server 8.0\my.ini”
–console –skip-grant-tables –shared-memory

mysql –defaults-file=”C:\ProgramData\MySQL\MySQL Server 8.0\my.ini”
–console –skip-grant-tables –named-pipe

说明

  • 像上面说的一样,如果my.ini在mysql服务的安装位置上,则可以省略--defaults-file
  • --console -用于设置默认错误日志目标为控制台,有利于与观察服务运行状态。
  • --skip-grant-tables - 跳过grant tables,这是必须的。
  • --shared-memory--named-pipe - 二选一,注意,这两个协议只在windows上有效。
    • 另外提一句,在linux下是socket(不是特指TCP/IPTCP/IP是默认系统通用)

注意

1
2
--skip-grant-tables将跳过grant tables,同时拒绝远程访问,并且给予任何人
unrestricted access to all databases(不受限制地访问所有数据库)。

另一种方法,修改my.ini

通过修改my.ini同样可以达到这种方法,通过在mysqld下添加skip-grant-tables
shared-memory(或named-pipe)同样可以达到上面的效果,同时,依然要注意my.ini
的位置,这也是网上有人说修改my.ini的原因,因为并没有正确加载。

mysql连接

这里比较简单,基本可以一笔带过,直接在terminal中键入mysql就可以直接连接到数据库。

注意,如果使用了--named-pipe,在需要使用mysql --protocol=pipe,protocol的默认
是memory(–shared-memory)

数据库操作

刷入权限

首先,接下来的操作需要权限,所以需要FLUSH PRIVILEGES获取权限

1
MariaDB [(none)] > FLUSH PRIVILEGES;

官方文档对于权限的描述如下

1
2
3
4
5
To cause a server started with --skip-grant-tables to load the grant tables at runtime, perform a privilege-flushing operation, which can be done in these ways:

Issue a MySQL FLUSH PRIVILEGES statement after connecting to the server.

Execute a mysqladmin flush-privileges or mysqladmin reload command from the command line.

修改密码

这一个就是常规操作,简略地提供一种方法

1
MariaDB [(none)] > ALTER USER 'root'@'localhost' IDENTIFIED BY '<your_passcode>';