开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

前言及redis环境准备 一、前言 二、 环境准本

发表于 2021-11-21

Redis核心数据结构之String(一)

Redis数据结构之Hash实现演唱会抢票(二)

Redis核心数据结构之List(三)

Redis数据结构之Set实现抽奖小程序(四)

Redis数据结构之Zset实现微博排行榜(五)

Redis之bitmap使用及项目应用(六)

一、前言

专栏说明

(一)本专栏讲解redis常用的api的使用,并且会给对应的api提供项目使用案例。

(二)专栏redis使用的版本是5.x

(三)该专栏适合所有等级的开发者

(四)使用案例采用的客户端语言为java,后期如有需要会提供php等其他语言。

二、 环境准本

环境安装,这里最好装个虚拟机的linux环境,一来大家可以更加熟悉linux的基本命令,二来学习环境更加贴近生产环境。linux系统环境这里就不带大家装了,直接进入redis的环境安装。

(一)下载 -> 解压 -> 编译

安装gcc 用户编译

yum install gcc

image.png

下载redis

wget http://download.redis.io/releases/redis-5.0.8.tar.gz

image.png

解压

tar -zxvf redis-5.0.8.tar.gz

image.png

(二)进入 ->编译安装 -> 修改配置

进入解压目录

cd redis-5.0.8/

image.png

编译安装

make
image.png

打开配置文件

vi redis.conf

将配置文件下列参数置为对应的值

表示后台启动 daemonize yes
image.png

*关闭保护模式,开启的话,只有本机才可以访问redis *protected‐mode no
image.png

*注释掉即可 bind 127.0.0.1
image.png

保存退出

vi redis.conf

(三)启动

如下所示,最终启动成功。只要按照步骤,就能成功安装(如需docker方式安装,以后补充)

启动redis

src/redis-server redis.conf

查看进程

ps -ef |grep redis

image.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

python爬虫实战,requests模块,Python实现

发表于 2021-11-21

「这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战」。

前言

利用Python爬取的是今日头条中的街拍美图。废话不多说。

让我们愉快地开始吧~

开发工具

Python版本: 3.6.4

相关模块:

re;

requests模块;

以及一些Python自带的模块。

环境搭建

安装Python并添加到环境变量,pip安装需要的相关模块即可。

详细浏览器信息

1.png

2.png

3.png

获取文章链接相关代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
python复制代码import requests
import json
import re

headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'
}

def get_first_data(offset):
    params = {
        'offset': offset,
        'format': 'json',
        'keyword': '街拍',
        'autoload': 'true',
        'count': '20',
        'cur_tab': '1',
        'from':'search_tab'
    }

这里需要提一下requests模块的报错,在response对象上调用 raise_for_status()方法,如果下载文件出错,会抛出异常,需要使用 try 和 except 语句将代码行包裹起来,处理这一错误,不让程序崩溃。

获取图片链接部分代码:

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
python复制代码def get_second_data(url):
    if url:
        try:
            reponse = requests.get(url, headers=headers)
            reponse.raise_for_status()
            return reponse.text
        except Exception as exc:
            print("进入链接发生错误")
            return None

def handle_second_data(html):
    if html:
        pattern = re.compile(r'gallery: JSON.parse\((.*?)\),', re.S)
        result = re.search(pattern, html)
        if result:
            imageurl = []
            data = json.loads(json.loads(result.group(1)))
            if data and "sub_images" in data.keys():
                sub_images = data.get("sub_images")
                images = [item.get('url') for item in sub_images]
                for image in images:
                    imageurl.append(images)
                return imageurl
        else:
            print("have no result")

获取图部分代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
python复制代码def download_image(imageUrl):
    for url in imageUrl:
        try:
            image = requests.get(url).content
        except:
            pass
        with open("images"+str(url[-10:])+".jpg", "wb") as ob:
            ob.write(image)
            ob.close()
            print(url[-10:] + "下载成功!" + url)

if __name__ == '__main__':
    main()

最后下载成功

4.png

5.png

查看详情

6.png

7.png

8.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Spring之AOP源码分析

发表于 2021-11-21

Spring AOP

  • 它基于动态代理来实现。 如果使用接口,用JDK提供给动态代理来实现,如果没有接口,使用CGLIB实现。
  • Spring 3.2以后,spring-core直接把CGLIB和ASM的源码包括进来。
  • Spring AOP需要依赖于IOC容器来管理。
  • Spring提供了AspectJ的支持,但只用到了切点解析和匹配。

源码

开启aop切面

spring通过@EnableAspectJAutoProxy开启aop切面,在注解类上面发现@Import(AspectJAutoProxyRegistrar.class),AspectJAutoProxyRegistrar实现了ImportBeanDefinitionRegistrar,所以他会通过registerBeanDefinitions方法为我们容器导入beanDefinition。
image.png

image.png

image.png

解析切面

image.png
image.png

第一次调用bean的后置处理器回去进行切面的解析。具体解析类是AbstractAutoProxyCreator,它实现了InstantiationAwareBeanPostProcessor。进入postProcessBeforeInstantiation方法。
image.png

判断是否应该跳过。真正的解析是在shouldSkip中。
image.png

image.png

进入findCandidateAdvisors的重写方法。
image.png

调用super.findCandidateAdvisors(),去查找实现了Advisor接口的切面类。

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
kotlin复制代码public List<Advisor> findAdvisorBeans() {

/**
* 探测器字段缓存中cachedAdvisorBeanNames 是用来保存我们的Advisor全类名
* 会在第一个单实例bean的中会去把这个advisor名称解析出来
*/
String[] advisorNames = this.cachedAdvisorBeanNames;
if (advisorNames == null) {
/**
* 去我们的容器中获取到实现了Advisor接口的实现类
* 而我们的事务注解@EnableTransactionManagement 导入了一个叫ProxyTransactionManagementConfiguration配置类
* 而在这个配置类中配置了
* @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor();
然后把他的名字获取出来保存到 本类的属性变量cachedAdvisorBeanNames中
*/
advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, Advisor.class, true, false);
this.cachedAdvisorBeanNames = advisorNames;
}
//若在容器中没有找到,直接返回一个空的集合
if (advisorNames.length == 0) {
return new ArrayList<>();
}

List<Advisor> advisors = new ArrayList<>();
//ioc容器中找到了我们配置的BeanFactoryTransactionAttributeSourceAdvisor
for (String name : advisorNames) {
//判断他是不是一个合适的 是我们想要的
if (isEligibleBean(name)) {
//BeanFactoryTransactionAttributeSourceAdvisor是不是正在创建的bean
if (this.beanFactory.isCurrentlyInCreation(name)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping currently created advisor '" + name + "'");
}
}
//不是的话
else {
try {
//显示的调用getBean方法方法创建我们的BeanFactoryTransactionAttributeSourceAdvisor返回去
advisors.add(this.beanFactory.getBean(name, Advisor.class));
}
catch (BeanCreationException ex) {
Throwable rootCause = ex.getMostSpecificCause();
if (rootCause instanceof BeanCurrentlyInCreationException) {
BeanCreationException bce = (BeanCreationException) rootCause;
String bceBeanName = bce.getBeanName();
if (bceBeanName != null && this.beanFactory.isCurrentlyInCreation(bceBeanName)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping advisor '" + name +
"' with dependency on currently created bean: " + ex.getMessage());
}
// Ignore: indicates a reference back to the bean we're trying to advise.
// We want to find advisors other than the currently created bean itself.
continue;
}
}
throw ex;
}
}
}
}
return advisors;
}

调用buildAspectJAdvisors去查找注解方式的切面类。

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
kotlin复制代码/**
* 去容器中获取到所有的切面信息保存到缓存中
* @return the list of {@link org.springframework.aop.Advisor} beans
* @see #isEligibleBean
*/
public List<Advisor> buildAspectJAdvisors() {
/**
* 用于保存切面的名称,该地方aspectNames 是我们的类级别的缓存,用户缓存已经解析出来的切面信息
*/
List<String> aspectNames = this.aspectBeanNames;
// 缓存字段aspectNames没有值 会在第一个单例执行后置处理器(AnnotationAwareAspectJAutoProxyCreator注册之后)的时候就会触发解析切面的操作
if (aspectNames == null) {
// 加上同步锁, 防止多线程同时加载Aspect
synchronized (this) {
aspectNames = this.aspectBeanNames;
//做了双重检查加锁
if (aspectNames == null) {
// 保存所有通知的集合
List<Advisor> advisors = new ArrayList<>();
// 保存切面的名称的集合
aspectNames = new ArrayList<>();
/**
* aop功能中在这里传入的是Object.class,代表去容器中获取到所有的组件的名称,然后再经过
* 一一的进行遍历,这个过程是十分的消耗性能的,所以说spring会再这里加入了保存切面信息的缓存。
* 但是事务功能不一样,事务模块的功能是直接去容器中获取Advisor类型的,选择范围小,且不消耗性能。所以
* spring在事务模块中没有加入缓存来保存我们的事务相关的advisor
*/
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, Object.class, true, false);
//遍历我们从IOC容器中获取处的所有bean的名称
for (String beanName : beanNames) {
if (!isEligibleBean(beanName)) {
continue;
}
//通过beanName去容器中获取到对应class对象
Class<?> beanType = this.beanFactory.getType(beanName);
if (beanType == null) {
continue;
}
//根据class对象判断是不是切面 即判断类上面是否有Aspect注解且不是ajc$开头的文件。
if (this.advisorFactory.isAspect(beanType)) {
//是切面类
//加入到缓存中
aspectNames.add(beanName);
//把beanName和class对象构建成为一个AspectMetadata
AspectMetadata amd = new AspectMetadata(beanType, beanName);
if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {

//构建切面注解的实例工厂
MetadataAwareAspectInstanceFactory factory =
new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
//真正的去获取我们的通知对象 详细看下面getAdvisors方法
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
//加入到缓存中
if (this.beanFactory.isSingleton(beanName)) {
this.advisorsCache.put(beanName, classAdvisors);
}
else {
this.aspectFactoryCache.put(beanName, factory);
}
advisors.addAll(classAdvisors);
}
else {
// Per target or per this.
if (this.beanFactory.isSingleton(beanName)) {
throw new IllegalArgumentException("Bean with name '" + beanName +
"' is a singleton, but aspect instantiation model is not singleton");
}
MetadataAwareAspectInstanceFactory factory =
new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
this.aspectFactoryCache.put(beanName, factory);
advisors.addAll(this.advisorFactory.getAdvisors(factory));
}
}
}
this.aspectBeanNames = aspectNames;
return advisors;
}
}
}

if (aspectNames.isEmpty()) {
return Collections.emptyList();
}
/**
* 真正的创建切面的时候,我们不需要去解析了而是直接去缓存中获取处
*/
List<Advisor> advisors = new ArrayList<>();
for (String aspectName : aspectNames) {
List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
if (cachedAdvisors != null) {
advisors.addAll(cachedAdvisors);
}
else {
MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
advisors.addAll(this.advisorFactory.getAdvisors(factory));
}
}
return advisors;
}

getAdvisors方法

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
scss复制代码@Override
public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
//获取我们的标记为Aspect的类
Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
//获取我们的切面类的名称
String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
//校验我们的切面类
validate(aspectClass);

//我们使用的是包装模式来包装我们的MetadataAwareAspectInstanceFactory 构建为MetadataAwareAspectInstanceFactory
MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);

List<Advisor> advisors = new ArrayList<>();
//获取到切面类中的所有方法,但是该方法不会解析标注了@PointCut注解的方法
for (Method method : getAdvisorMethods(aspectClass)) {
//挨个去解析我们切面中的方法 详细看下面getAdvisor方法
Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
if (advisor != null) {
advisors.add(advisor);
}
}

// If it's a per target aspect, emit the dummy instantiating aspect.
if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
advisors.add(0, instantiationAdvisor);
}

// Find introduction fields.
for (Field field : aspectClass.getDeclaredFields()) {
Advisor advisor = getDeclareParentsAdvisor(field);
if (advisor != null) {
advisors.add(advisor);
}
}

return advisors;
}

getAdvisor方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
scss复制代码public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
int declarationOrderInAspect, String aspectName) {

validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());

// 获得当前通知的 切点表达式 详细看下面getPointcut方法
AspectJExpressionPointcut expressionPointcut = getPointcut(
candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
if (expressionPointcut == null) {
return null;
}
// 将切点表达式、 和通知 封装到InstantiationModelAwarePointcutAdvisorImpl对象中 详细看下面InstantiationModelAwarePointcutAdvisorImpl源码
return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}

getPointcut方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typescript复制代码private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
// 找到aspectJ的注解:
// @Pointcut @Around @Before @After @AfterReturning @AfterThrowing
AspectJAnnotation<?> aspectJAnnotation =
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
// 没有注解那肯定忽略
if (aspectJAnnotation == null) {
return null;
}

AspectJExpressionPointcut ajexp =
new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class<?>[0]);
ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
if (this.beanFactory != null) {
ajexp.setBeanFactory(this.beanFactory);
}
return ajexp;
}

InstantiationModelAwarePointcutAdvisorImpl函数

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
kotlin复制代码public InstantiationModelAwarePointcutAdvisorImpl(AspectJExpressionPointcut declaredPointcut,
Method aspectJAdviceMethod, AspectJAdvisorFactory aspectJAdvisorFactory,
MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {

//当前的切点
this.declaredPointcut = declaredPointcut;
//切面的class对象
this.declaringClass = aspectJAdviceMethod.getDeclaringClass();
//切面方法的名称
this.methodName = aspectJAdviceMethod.getName();
//切面方法的参数类型
this.parameterTypes = aspectJAdviceMethod.getParameterTypes();
//切面方法对象
this.aspectJAdviceMethod = aspectJAdviceMethod;
//aspectj的通知工厂
this.aspectJAdvisorFactory = aspectJAdvisorFactory;
//aspect的实例工厂
this.aspectInstanceFactory = aspectInstanceFactory;
//切面的顺序
this.declarationOrder = declarationOrder;
//切面的名称
this.aspectName = aspectName;

/**
* 判断当前的切面对象是否需要延时加载
*/
if (aspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
// Static part of the pointcut is a lazy type.
Pointcut preInstantiationPointcut = Pointcuts.union(
aspectInstanceFactory.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut);

// Make it dynamic: must mutate from pre-instantiation to post-instantiation state.
// If it's not a dynamic pointcut, it may be optimized out
// by the Spring AOP infrastructure after the first evaluation.
this.pointcut = new PerTargetInstantiationModelPointcut(
this.declaredPointcut, preInstantiationPointcut, aspectInstanceFactory);
this.lazy = true;
}
else {
// A singleton aspect.
this.pointcut = this.declaredPointcut;
this.lazy = false;
//把切面中的通知构造为一个一个的advice通知对象 详细看下面instantiateAdvice源码
this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);
}
}

instantiateAdvice方法

1
2
3
4
5
6
kotlin复制代码private Advice instantiateAdvice(AspectJExpressionPointcut pointcut) {
// 详细看下面getAdvice源码
Advice advice = this.aspectJAdvisorFactory.getAdvice(this.aspectJAdviceMethod, pointcut,
this.aspectInstanceFactory, this.declarationOrder, this.aspectName);
return (advice != null ? advice : EMPTY_ADVICE);
}

getAdvice源码

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
78
79
80
81
java复制代码public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,
MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {

//获取我们的切面类的class对象
Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
validate(candidateAspectClass);

//获取切面方法上的注解
AspectJAnnotation<?> aspectJAnnotation =
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
//解析出来的注解信息是否为null
if (aspectJAnnotation == null) {
return null;
}

//判断这里的class对象是不是切面信息对象
if (!isAspect(candidateAspectClass)) {
throw new AopConfigException("Advice must be declared inside an aspect type: " +
"Offending method '" + candidateAdviceMethod + "' in class [" +
candidateAspectClass.getName() + "]");
}
// 记录找到了advice方法
if (logger.isDebugEnabled()) {
logger.debug("Found AspectJ method: " + candidateAdviceMethod);
}

AbstractAspectJAdvice springAdvice;

//判断标注在方法上的注解类型
switch (aspectJAnnotation.getAnnotationType()) {
//是PointCut注解 那么就抛出异常 因为在外面传递进来的方法已经排除了pointcut的方法
case AtPointcut:
if (logger.isDebugEnabled()) {
logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");
}
return null;

case AtAround: //环绕通知 构建AspectJAroundAdvice
springAdvice = new AspectJAroundAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
break;
case AtBefore: //前置通知 构建AspectJMethodBeforeAdvice
springAdvice = new AspectJMethodBeforeAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
break;
case AtAfter: //后置通知 AspectJAfterAdvice
springAdvice = new AspectJAfterAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
break;
case AtAfterReturning: //返回通知 AspectJAfterReturningAdvice
springAdvice = new AspectJAfterReturningAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
if (StringUtils.hasText(afterReturningAnnotation.returning())) {
springAdvice.setReturningName(afterReturningAnnotation.returning());
}
break;
case AtAfterThrowing: //异常通知 AspectJAfterThrowingAdvice
springAdvice = new AspectJAfterThrowingAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
}
break;
default:
throw new UnsupportedOperationException(
"Unsupported advice type on method: " + candidateAdviceMethod);
}

//配置我们构建出来的通知对象
springAdvice.setAspectName(aspectName);
springAdvice.setDeclarationOrder(declarationOrder);
String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
if (argNames != null) {
springAdvice.setArgumentNamesFromStringArray(argNames);
}
springAdvice.calculateArgumentBindings();

return springAdvice;
}

applyBeanPostProcessorsBeforeInstantiation到这里第一次调用后置处理器完成了切面的解析那么接下来即调用切面创建动态代理,创建动态代理实在初始化Bean后才调用,即在doCreateBean的时候回去初始化Bean,initializeBean方法中才去调用applyBeanPostProcessorsAfterInitialization方法创建动态代理。

applyBeanPostProcessorsAfterInitialization方法

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
typescript复制代码public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {

Object result = existingBean;
//获取我们容器中的所有的bean的后置处理器
for (BeanPostProcessor processor : getBeanPostProcessors()) {
/**
* 在这里是后置处理器的【第九次调用】 aop和事务都会在这里生存代理对象
*
* 【很重要】
* 我们AOP @EnableAspectJAutoProxy 为我们容器中导入了 AnnotationAwareAspectJAutoProxyCreator
* 我们事务注解@EnableTransactionManagement 为我们的容器导入了 InfrastructureAdvisorAutoProxyCreator
* 都是实现了我们的 BeanPostProcessor接口,InstantiationAwareBeanPostProcessor,
* 在这里实现的是BeanPostProcessor接口的postProcessAfterInitialization来生成我们的代理对象
*/
// 详细往下看AbstractAutoProxyCreator实现的postProcessAfterInitialization 方法
Object current = processor.postProcessAfterInitialization(result, beanName);
//若只有有一个返回null 那么直接返回原始的
if (current == null) {
return result;
}
result = current;
}
return result;
}

postProcessAfterInitialization方法(AbstractAutoProxyCreator类实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
less复制代码public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException {
if (bean != null) {
//获取缓存key
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 之前循环依赖创建的动态代理 如果是现在的bean 就不再创建,,并且移除
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
// 该方法将会返回动态代理实例
// 详细看wrapIfNecessary源码
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}

wrapIfNecessary方法

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
kotlin复制代码protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
//已经被处理过(解析切面时targetSourcedBeans出现过) 就是自己实现创建动态代理逻辑
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
//不需要增强的
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
//是不是基础的bean 是不是需要跳过的 重复判断 ( 因为循环依赖是可以改变bean的,如果把bean改成了advisor呢)
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}

// 根据当前bean找到匹配的advisor
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
// 当前bean匹配到了advisor
if (specificInterceptors != DO_NOT_PROXY) {
// 标记为已处理
this.advisedBeans.put(cacheKey, Boolean.TRUE);
//创建我们的真正的代理对象
//详细看createProxy方法
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
//加入到缓存
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}

this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}

createProxy方法创建动态代理

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
less复制代码protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {

if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}
//创建一个代理对象工厂
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);

//为proxyFactory设置创建jdk代理还是cglib代理
// 如果设置了 <aop:aspectj-autoproxy proxy-target-class="true"/>不会进if,说明强制使用cglib
if (!proxyFactory.isProxyTargetClass()) {
// 内部设置的 , 配置类就会设置这个属性
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
// 检查有没有接口
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}

//把我们的specificInterceptors数组中的Advisor转化为数组形式的
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
//为我们的代理工厂加入通知器,
proxyFactory.addAdvisors(advisors);
//设置targetSource对象
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);

proxyFactory.setFrozen(this.freezeProxy);
// 代表之前是否筛选advise.
// 因为继承了AbstractAdvisorAutoProxyCreator , 并且之前调用了findEligibleAdvisors进行筛选, 所以是true
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
//真正的创建代理对象
return proxyFactory.getProxy(getProxyClassLoader());
}

getProxy方法

1
2
3
4
less复制代码public Object getProxy(@Nullable ClassLoader classLoader) {
//createAopProxy() 用来获取我们的代理工厂
return createAopProxy().getProxy(classLoader);
}

createAopProxy方法

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
arduino复制代码/**
*
* @param config 用来为我们指定我们advisor信息
* 该方法用来创建我们的代理对象
* 所我们的targetClass对象实现了接口,且 ProxyTargetClass 没有指定强制的走cglib代理,那么就是创建jdk代理
* 我们代理的类没有实现接口,那么会直接走cglib代理
* 若我们 ProxyTargetClass 指定为false 且代理类是接口才会走jdk代理 否在我们还是cglib代理
* @return
* @throws AopConfigException
*/
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
//判断我们是否前置指定使用cglib代理ProxyTargetClass =true 或者没有接口
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
//所targetClass是接口 使用的就是jdk代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
//cglib代理
return new ObjenesisCglibAopProxy(config);
}
else {
//动态代理
return new JdkDynamicAopProxy(config);
}
}

getProxy方法有两种实现,CGLIB和JDK动态代理。
image.png

到这里就已经创建了AOP动态代理,那么在调用的时候,就会进入到JdkDynamicAopProxy的invoke方法。

invoke方法

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
kotlin复制代码public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
//获取到我们的目标对象
TargetSource targetSource = this.advised.targetSource;
Object target = null;

try {
//若执行代理对象的equals方法不需要代理
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
// The target does not implement the equals(Object) method itself.
return equals(args[0]);
}
//若执行的是hashCode方法 不需要代理
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
// The target does not implement the hashCode() method itself.
return hashCode();
}
//若执行的class对象是DecoratingProxy 则不会对其应用切面进行方法的增强。返回源目标类型
else if (method.getDeclaringClass() == DecoratingProxy.class) {
// There is only getDecoratedClass() declared -> dispatch to proxy config.
return AopProxyUtils.ultimateTargetClass(this.advised);
}
// 如果目标对象实现的Advised接口,则不会对其应用切面进行方法的增强。 直接执行方法
else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
// Service invocations on ProxyConfig with the proxy config...
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
}

Object retVal;

/**
* 这个配置很重要很实用【暴露我们的代理对象到线程变量中】需要搭配@EnableAspectJAutoProxy(exposeProxy = true)
* 一起使用.
* 比如我们的aop中 multi和 mode方法都是被切入的方法,但是在切入的方法中通过
* this来调用另外一个方法的时候,那么该方法就不会被代理执行,而是通过方法内部执行
public int mod(int numA,int numB){
System.out.println("执行目标方法:mod");
int retVal = ((Calculate) AopContext.currentProxy()).add(numA,numB);
return retVal%numA;
}
*还有的就是事务方法调用事务方法的时候 也需要这样来社会
*/
if (this.advised.exposeProxy) {
//把我们的代理对象暴露到线程变量中
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}

//获取我们的目标对象
target = targetSource.getTarget();
//获取我们目标对象的class
Class<?> targetClass = (target != null ? target.getClass() : null);

//把我们的aop的advisor 全部转化为拦截器, 通过责任链模式 依此调用
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

//加入我们的拦截器链为空
if (chain.isEmpty()) {
//通过反射直接调用执行
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
//创建一个方法调用对象
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
//调用执行
retVal = invocation.proceed();
}

// Massage return value if necessary.
Class<?> returnType = method.getReturnType();
if (retVal != null && retVal == target &&
returnType != Object.class && returnType.isInstance(proxy) &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
// Special case: it returned "this" and the return type of the method
// is type-compatible. Note that we can't help if the target sets
// a reference to itself in another returned object.
retVal = proxy;
}
else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException(
"Null return value from advice does not match primitive return type for: " + method);
}
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
// Must have come from TargetSource.
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}

通过责任链调用。

ReflectiveMethodInvocation的proceed方法

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
kotlin复制代码public Object proceed() throws Throwable {
//从-1开始,结束条件执行目标方法是下标=拦截器的长度-1(执行到了最后一个拦截器的时候)
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}

/**
* 获取第一个方法拦截器使用的是前++
*/
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// Evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// Dynamic matching failed.
// Skip this interceptor and invoke the next in the chain.
return proceed();
}
}
else {
//在这个地方需要注意,抵用第一个拦截器的invoke方法,传入的是this 当前的方法拦截器对象
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Python中的标准类型(可改变)

发表于 2021-11-21

这是我参与18月更文挑战的第20天,活动详情查看:2021最后一次更文挑战

前言:更具昨天的发布的Python中的标准类型(可改变),这篇则是对可改变类型的概述。

可改变的基本类型:

在Python中不可改变的基本类型有:Dictionary(字典)、Set(集合)、List(列表)

Dictionary(字典)

首先我们来看一下字典的创建以及他为什么是可改变的,代码如下:

1
2
3
4
5
6
7
8
9
python复制代码a = {"name": "又菜又想玩的大白", "age": 24}
a1 = dict({"name": "又菜又想玩的大白", "age": 24})
print(a)
print(type(a))
print(type(a1))
print(id(a))
a['name'] = "又菜又想玩的XXX"
print(a)
print(id(a))

image.png

从上我列了一下字典的创建两种方式,a是直接创建,a1则是用dict函数创建的,我们从再看下面的打印他们的类型都是字典类型的。从图中还可以看到,我的内容更变了,但是当前对象的ID依旧没变,因此可以证实字典为可改变的。

Set(集合)

我们看完了字典在来看一下集合的创建以及他为什么是可改变的,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
scss复制代码a = {1, 2, 3, 4}
a1 = set({1, 2, 3, 4})
print(a)
print(type(a))
print(type(a1))
print(id(a))
a.remove(1)
print(a)
print(id(a))
a.add(5)
print(a)
print(id(a))

image.png

以上是集合的创建和是否可变的判断,从上面我们可以对比字典,因此不难看出集合类型是可改变的。

List(列表)

看完了集合和字典,我们再来看一下列表的创建以及他为什么是可改变的,代码如下:

1
2
3
4
5
6
7
8
9
scss复制代码a = [1, 2, 3, 4]
a1 =list({1, 2, 3, 4})
print(a)
print(type(a))
print(type(a1))
print(id(a))
a.append(5)
print(a)
print(id(a))

image.png

以上是列表的创建和是否可变的判断,从上面我们可以对比字典和集合,因此不难看出集合类型是可改变的。

总结:本篇是对基本数据类型中可变的类型验证和创建,我们可以看到即使对象更变了,但是他们的指向ID依旧没有更变,所以在开发的时候我们可能是注意当此类对象为全局变量时,同时被调用时是否会产生死锁等异常,我们应在开发时更具自己的场景适当的应用对应的类型。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

分库分表技术之MyCat(2)

发表于 2021-11-21

「这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战」

什么是MyCat

MyCat是目前最流行的基于java语言编写的数据库中间件,是一个实现了MySQL协议的服务器,前端用户可以把它看作是一个数据库代理,用MySQL客户端工具和命令行访问,而其后端可以用MySQL原生协议与多个MySQL服务器通信,也可以用JDBC协议与大多数主流数据库服务器通信,其核心功能是分库分表和读写分离,即将一个大表水平分割为N个小表,存储在后端MySQL服务器里或者其他数据库里。

MyCat对于我们Java程序员来说,就是一个近似等于MySQL的数据库服务器,你可以用连接MySQL的方式去连接Mycat(除了端口不同,默认的Mycat端口是8066而非MySQL的3306,因此需要在连接字符串上增加端口信息)

我们可以像使用MySQL一样使用MyCat,Mycat可以管理若干MySQL数据库,同时实现数据的存储和操作

image.png

MyCat支持的数据库

  • Oracle
  • MySQL
  • mongoDB
  • SQlServer

MyCat概念说明

MyCat的分片策略

  • 什么是分片
    • 通过某种特定的条件,将我们存放在同一个数据库中的数据分散存放到多个数据库(主机)上面,以达到分散单台设备负载的效果。
  • MyCat支持两种切分模式
    • 一种是按照不同的表(或者Schema)来切分到不同的数据库(主机)之上,这种切可以称之为数据的垂直(纵向)切分
    • 另外一种则是根据表中的数据的逻辑关系,将同一个表中的数据按照某种条件拆分到多台数据库(主机)上面,这种切分称之为数据的水平(横向)切分。

image.png

image.png

  • 逻辑库(schema)
    对数据进行分片处理之后,从原有的一个库,被切分为多个分片数据库,所有的分片数据库集群构成了整个完整的数据库存储。Mycat在操作时,使用逻辑库来代表这个完整的数据库集群,便于对整个集群操作。
  • 逻辑表(table)
    既然有逻辑库,那么就会有逻辑表,分布式数据库中,对应用来说,读写数据的表就是逻辑表。逻辑表,可以是数据切分后,分布在一个或多个分片库中,也可以不做数据切分,不分片,只有一个表构成。
1
2
3
4
5
复制代码分片表:
是指那些原有的很大数据的表,需要切分到多个数据库的表,这样,每个分片都有一部分数据,所有分片构成了完整的数据。 总而言之就是需要进行分片的表。

非分片表:
一个数据库中并不是所有的表都很大,某些表是可以不用进行切分的,非分片是相对分片表来说的,就是那些不需要进行数据切分的表。
  • 分片节点(dataNode)
    数据切分后,一个大表被分到不同的分片数据库上面,每个表分片所在的数据库就是分片节点(dataNode)。
  • 节点主机(dataHost)
    数据切分后,每个分片节点不一定都会独占一台机器,同一机器上面可以有多个分片数据库, 这样一个或多个分片节点所在的机器就是节点主机,为了规避单节点主机并发数限制, 尽量将读写压力高的分片节点均衡的放在不同的节点主机dataHost。
  • 分片规则
    前面讲了数据切分,一个大表被分成若干个分片表,就需要一定的规则rule,这样按照某种业务规则把数据分到 某个分片的规则就是分片规则,数据切分选择合适的分片规则非常重要,将极大的避免后续数据处理的难度。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Netty 框架基础知识点

发表于 2021-11-21

这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战

前言

image.png

  1. Netty 是什么?

Netty是 一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。Netty是基于nio的,它封装了jdk的nio,让我们使用起来更加方法灵活。

  1. Netty 的特点是什么?

  • 高并发:Netty 是一款基于 NIO(Nonblocking IO,非阻塞IO)开发的网络通信框架,对比于 BIO(Blocking I/O,阻塞IO),他的并发性能得到了很大提高。
  • 传输快:Netty 的传输依赖于零拷贝特性,尽量减少不必要的内存拷贝,实现了更高效率的传输。
  • 封装好:Netty 封装了 NIO 操作的很多细节,提供了易于使用调用接口。
  1. Netty 的优势有哪些?

  • 使用简单:封装了 NIO 的很多细节,使用更简单。
  • 功能强大:预置了多种编解码功能,支持多种主流协议。
  • 定制能力强:可以通过 ChannelHandler 对通信框架进行灵活地扩展。
  • 性能高:通过与其他业界主流的 NIO 框架对比,Netty 的综合性能最优。
  • 稳定:Netty 修复了已经发现的所有 NIO 的 bug,让开发人员可以专注于业务本身。
  • 社区活跃:Netty 是活跃的开源项目,版本迭代周期短,bug 修复速度快。

4.Netty 的应用场景有哪些?

典型的应用有:阿里分布式服务框架 Dubbo,默认使用 Netty 作为基础通信组件,还有 RocketMQ 也是使用 Netty 作为通讯的基础。

Netty常见的使用场景如下:

  • 互联网行业 在分布式系统中,各个节点之间需要远程服务调用,高性能的RPC框架必不可少,Netty作为异步高新能的通信框架,往往作为基础通信组件被这些RPC框架使用。 典型的应用有:阿里分布式服务框架Dubbo的RPC框架使用Dubbo协议进行节点间通信,Dubbo协议默认使用Netty作为基础通信组件,用于实现各进程节点之间的内部通信。
  • 游戏行业 无论是手游服务端还是大型的网络游戏,Java语言得到了越来越广泛的应用。Netty作为高性能的基础通信组件,它本身提供了TCP/UDP和HTTP协议栈。 非常方便定制和开发私有协议栈,账号登录服务器,地图服务器之间可以方便的通过Netty进行高性能的通信
  • 大数据领域 经典的Hadoop的高性能通信和序列化组件Avro的RPC框架,默认采用Netty进行跨界点通信,它的Netty Service基于Netty框架二次封装实现。

5.Netty 高性能表现在哪些方面?

  • IO 线程模型:同步非阻塞,用最少的资源做更多的事。
  • 内存零拷贝:尽量减少不必要的内存拷贝,实现了更高效率的传输。
  • 内存池设计:申请的内存可以重用,主要指直接内存。内部实现是用一颗二叉查找树管理内存分配情况。
  • 串形化处理读写:避免使用锁带来的性能开销。
  • 高性能序列化协议:支持 protobuf 等高性能序列化协议。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Go语言学习查缺补漏ing Day3

发表于 2021-11-21

「这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战」。

Go语言学习查缺补漏ing Day3

本文收录于我的专栏:《让我们一起Golang》

一、结构体的比较问题

我们先来看一段关于结构体的比较的代码:

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
go复制代码package main
​
import "fmt"
​
func main() {
struct1 := struct {
age  int
name string
sex bool
}{age: 18, name: "搏达", sex:false}
   
struct2 := struct {
age  int
name string
sex bool
}{age: 21, name: "Regan Yue", sex:true}
   
if struct1 == struct2 {
fmt.Println("struct1 == struct2")
}
​
struct3 := struct {
age int
people map[string]bool
}{age: 31, people: map[string]bool{"搏达": false}}
   
struct4 := struct {
age int
people map[string]bool
}{age: 21, people: map[string]bool{"ReganYue": false}}
   
if struct3 == struct4{
fmt.Println("struct3 == struct4")
}
}

你觉得编译时会报错吗?

image-20211121193059286

image-20211121193143297

啊这,出大问题。它说包含map的结构体不能被比较。

是的,只有包含bool类型、数值型、字符串、指针、数组等类型的结构体才能比较,而包含slice、map、函数其中任一项的结构体均不能做比较。

还有几点值得注意的是:

  1. 结构体之间只能比较它们是否相等,而不能比较它们的大小。
  2. 只有所有属性都相等而属性顺序都一致的结构体才能进行比较。

二、通过指针变量访问成员变量的几种方式

先看这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
go复制代码package main
​
import "fmt"
​
func main() {
s1 := struct {
name string
age int64
}{name:"Regan",age:21}
s2 := &s1
fmt.Println(s2.name)
fmt.Println((*s2).name)
fmt.Println((&s2).name)
}

你觉得哪里会报错?

image-20211121194638463

哦!是(&s2).name有点问题…..

因为&是取地址符,*是指针解引用符。所以将指针解引用才能访问到成员变量。

你会好奇为什么指针不解引用也能获取成员变量吗?其实是进行解引用了,这就是我们GO语言的强大之处了,它能够自动进行解引用,但是遗憾的是,只能解引用乙级指针,如果是多级指针,就得靠你自己了。

三、类型别名和类型定义的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
go复制代码package main
​
import "fmt"
​
type Int1 int
type Int2 = int
​
func main() {
var i = 0
var i1 Int1 = i
var i2 Int2= i
fmt.Println(i, i1, i2)
}
​

是不是一眼就看出来问题所在了?

不过他们放在一起,你能马上分别哪个是类型别名,哪个是类型定义吗?

1
go复制代码type Int1 int

是定义了基于int类型创建了一个新类型,而

1
go复制代码type Int2 = int

是将Int2作为int类型的别名。

所以将会报如下错误:

image-20211121201235626

这是因为Go是强类型语言,所以我们需要将int强制转换Int1类型就可以通过编译了。

四、切片的一个注意事项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
go复制代码package main
​
import "fmt"
​
func main() {
a := []int{7, 8, 9}
fmt.Printf("%+v\n", a)
fun1(a)
fmt.Printf("%+v\n", a)
fun2(a)
fmt.Printf("%+v\n", a)
}
func fun1(a []int) {
a = append(a, 5)
}
func fun2(a []int) {
a[0] = 8
}
​

看一看运行结果:

1
2
3
csharp复制代码[7 8 9]
[7 8 9]
[8 8 9]

什么?!! 没有append进去?这是因为append可能会导致内存的重新分配,我们就不能再确定append之前的切片与之后的切片是不是引用的相同的内存空间。

所以说这个执行fun1来append之后的切片a与外面的切片a不是同一个,所以查看外面的a,可以发现外面切片的内容没有改变。

五、Golang字符串连接的几种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
go复制代码package main
​
import (
"bytes"
"fmt"
"strings"
)
​
func main() {
str1 := "abc"
str2 := "def"
strs := []string{"Regan","Yue"}
fmt.Println(str1+str2)
sprintf := fmt.Sprintf("abc%s", "accc")
fmt.Println(sprintf)
join := strings.Join(strs, "-")
fmt.Println(join)
​
var buffer bytes.Buffer
buffer.WriteString("hello")
buffer.WriteString("world")
fmt.Println(buffer.String())
}

这里提到了四种连接字符串的方式,第一种利用+号直接连接,第二种利用 fmt.Sprintf(),第三种是利用strings.Join(),第四种是利用buffer.WriteString()来进行字符串连接。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Python类中的单例模式和描述符 单例模式: 描述符

发表于 2021-11-21

「这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战」

new方法会开辟空间

每次实例化,这个new方法都会开辟一个新的空间

可不可以让这new方法只开辟一个空间:::单例模式

单例模式:

提前引入一些小知识点:::

第一个:

hasattr() #has attribute

hasattr() 函数用于判断对象是否包含对应的属性\

hasattr(object(对象),name(“字符串,属性名”))

返回True(有的时候) False(没有的时候)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
python复制代码class Person:
#范式:固定的公式;固定的写法
def __new__(cls,*args,**kwargs):
#hasattr 判断类里面有没有这个方法
#如果类没有instance这个属性
if not hasattr(cls,'instance'): #cls就是Person这个类本身
#没有instance这个属性就会执行下面这句
#创建instance这个属性,等于父类的new方法
#new返回父类的new方法,和返回instance都可以
cls.instance = super().__new__(cls) #上面说如果类没有instance这个属性就执行这句,把父类的new方法添加为类本身的属性
#在下一句在return这个类本身的这个instance属性,这样在第二次实例化的时候,return
#的仍然是第一次实例化的cls.instance,就没有返回父类的new方法,也就没有创建新的内存
#空间,而是仍然指向第一次创建的内存空间
return cls.instance #如果把返回的改为 super().__new__(cls),直接返回父类的new方法,这就不是单例模式,因为每次实例化都会返回父类
#的new方法,即创建一个新的内存空间。
def __init__(self,name):
self.name = name

a = Person('寒沙')
b = Person('敢敢')
print(id(a))
print(id(b))

会发现两次的id都一样了,即两次指向同一片内存空间,相当于a被b覆盖了。

意味着这两个其实引用的是同一个实例,是一个实例的不同名字

描述符

描述符协议:python描述符是一个“绑定行为”的对象属性,在描述符协议中,

它可以通过方法重写属性的访问。这些方法有__get__(), set(), 和__delete__()。

如果这些方法中的任何一个被定义在一个对象中,这个对象就是一个描述符

举例说明:
描述符:描述符就是类里面的属性base

控制实例对象a访问 这个属性 (base) 可以做一些额外的操作

描述符 定义了__get__ set delete 当中的一种

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
python复制代码class Base:
def __get__(self,instance,owner):
print('恭喜玩家获得荒古宝典')
def __set__(self, instance,value):
print('强化%s'%value)
def __delete__(self,instance):
print('武器已经损坏')

class A:
base = Base()

#实例化
a = A()

a.base # __get__ 会直接输出__get__方法里面的内容

a.base = 50 # __set__ 会直接输出__set__方法里面的内容

del a.base # __delete__ 会直接输出__delete__方法里面的内容

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

从零开始学设计模式(十四):中介者模式(Mediator P

发表于 2021-11-21

这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战

作者的其他平台:

| CSDN:blog.csdn.net/qq\_4115394…

| 掘金:juejin.cn/user/651387…

| 知乎:www.zhihu.com/people/1024…

| GitHub:github.com/JiangXia-10…

| 公众号:1024笔记

本文大概2124字,读完预计需要8分钟

定义

中介者模式(Mediator Pattern)又被称为调停者模式。它定义了一个中介对象来封装一系列对象之间的交互关系。中介者使各个对象之间不需要显式地相互引用,从而使耦合性降低,而且可以独立地改变它们之间的交互行为。它是一种对象行为型模式。

生活中最常见的例子就是租房中介是一种中介,如果我们想要租房可以通过中介和房东沟通,这时候其实并不需要知道对方是谁,也并不需要面对面,类似于电话、QQ、微信等等都是中介,能大大降低了沟通的复杂性。

组成部分

中介者模式总共有为以下三个组成部分:

1、抽象中介者(Mediator):抽象中介者是中介者的抽象类,它提供了同事对象注册与转发同事对象信息的抽象方法,用于各个同事类之间的通信。一般包括一个或几个抽象的事件方法,并由子类去实现。

2、中介者实现类(Concrete Mediator):即具体中介者。继承抽象中介者,并且实现抽象中介者中定义的事件方法。从一个同事类接收消息,然后通过消息影响其他同时类。因为它管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。

3、同事类(Colleague):如果一个对象会影响其他的对象,同时也会被其他对象影响,那么这两个对象称为同事类。同事类也由两部分组成:抽象同事类(Abstract Colleague)和具体同事类(Concrete Colleague)。抽象同事类定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。具体同事类就是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。

栗子

抽象中介者

1
2
3
4
5
csharp复制代码public abstract class Mediator {
    public abstract void look(Colleague cl);
public abstract void regist(Colleague cl);

}

抽象同事类:

1
2
3
4
5
6
7
8
csharp复制代码public abstract class Colleague {
protected Mediator mediator;
public void setMedium(Mediator mediator) {
this.mediator = mediator;
}
public abstract void get();
public abstract void send();
}

具体中介类继承抽象中介类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
scala复制代码public class ConcreteMediator extends Mediator {
//list同事管理对象
private List<Colleague> colleagues = new ArrayList<Colleague>();
@Override
public void look(Colleague cl) {
if (!colleagues.contains(cl)) {
colleagues.add(cl);
cl.setMedium(this);
}
}

@Override
public void regist(Colleague cl) {
for (Colleague ob : colleagues) {
if (!ob.equals(cl)) {
((Colleague) ob).get();
}
}
}
}

具体同事类继承抽象同事类:

1
2
3
4
5
6
7
8
9
10
11
12
scala复制代码public class ConcreteColleague1 extends Colleague{
@Override
public void get() {
System.out.println("具体同事类1收到请求。");
}

@Override
public void send() {
System.out.println("具体同事类1发出请求。");
mediator.look(this);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
scala复制代码public class ConcreteColleague2 extends  Colleague{

@Override
public void get() {
System.out.println("具体同事类2收到请求。");
}

@Override
public void send() {
System.out.println("具体同事类2发出请求。");
mediator.look(this);
}
}

测试方法:

1
2
3
4
5
6
7
8
9
10
11
12
ini复制代码public class MediatorPatternTest {
public static void main(String[] args) {
Mediator md = new ConcreteMediator();
Colleague c1, c2;
c1 = new ConcreteColleague1();
c2 = new ConcreteColleague2();
md.look(c1);
md.look(c2);
c1.send();
c2.send();
}
}

运行结果如下:

通过上面代码可以发现中介者模式最为复杂的是同事类之间的关系,多个同事类之间相关关联,相互影响,如果是多个类(大于2个)它们之间的关系会是一种复杂的网状结构,这样它就是一种过度耦合的架构了,这样的系统肯定是不好的。这样的话就可以通过引入中介者,使得各个同事对象都只跟中介者打交道,这样过度耦合的网状结构就变成了扩散的星状结构了(可以自己脑补一下,就是以中介者对象为核心,同事类对象扩散开来),这是一个解耦的过程。可以把中介者理解为居委会大妈,不管社区有什么事情,都去找居委会大妈,她给你解决。

中介者模式的优点

1、使用中介者模式可以避免同事类之间的过度耦合,使得各同事类之间可以相对独立地使用。

2、使用中介者模式可以将对象间一对多的关联转变为一对一的关联,使对象间的关系易于理解和维护。

3、使用中介者模式可以将对象的行为和协作进行抽象,能够比较灵活的处理对象间的相互作用。

中介者模式的缺点

中介者模式的缺点很明显也比较少,比较显著的缺点就是:

1、具体中介者类中包含了同事之间的交互细节,可能会导致具体中介者类非常复杂;

2、中介者模式是将原本多个对象直接的相互依赖变成了中介者和多个同事类的依赖关系。使得系统难以维护。这样的话当同事类越来越多的时候,中介者就会越臃肿,变得复杂且难以维护。

应用场景

中介者模式的应用场景就是它的定义了。如果一个类同时依赖多个类的情况,那么适当的使用中介者模式可以使原本混乱的对象关系清晰,解耦关系。

比较常见的一个应用就是在MVC模式,其中的Controller控制器就是一个中介者对象。Model和View都是利用它进行通信。

总结

中介者模式是一种比较常用的模式,它的本质就是解耦多个同事对象之间的交互关系。每个对象都持有中介者对象的引用,只跟中介者对象打交道。通过中介者对象统一管理这些交互关系。所以如果对象之间的关系并没有达到一种复杂的混乱关系,其实并没有必要使用中介者模式,只需要通过依赖关系管理即可,如果使用中介者模式不恰当会使得程序更加混乱。

本文以及之前的所有的设计模式中的例子代码,都将同步至github,需要的欢迎下载star!

github地址:github.com/JiangXia-10…

相关推荐:

从零开始学设计模式(一):什么是设计模式

从零开始学设计模式(二):单例模式

从零开始学设计模式(三):原型模式(Prototype Pattern)

从零开始学设计模式(四):工厂模式(Factory Pattern)

从零开始学设计模式(五):建造者模式(Builder Pattern)

从零开始学设计模式(六):适配器模式(Adapter Pattern)

从零开始学设计模式(六):代理模式(Proxy Pattern)

从零开始学设计模式(八):装饰器模式(Decorator Pattern)

从零开始学设计模式(九):外观模式(Facade Pattern)

从零开始学设计模式(十):桥接模式(Bridge Pattern)

从零开始学设计模式(十一):组合模式(Composite Pattern)

从零开始学设计模式(十二):享元模式(Flyweight Pattern)

从零开始学设计模式(十三):访问者模式(Visitor Pattern)

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

手把手,带你从零封装Gin框架(十一):使用文件记录错误日志

发表于 2021-11-21

前言

Gin 框架的日志默认是在控制台输出,本篇将使用 Gin 提供的 RecoveryWithWriter() 方法,封装一个中间件,使用 lumberjack 作为的写入器,将错误日志写入文件中;同时使用 github.com/gin-contrib/cors ,作下跨域处理。

安装

1
2
shell复制代码go get -u gopkg.in/natefinch/lumberjack.v2
go get github.com/gin-contrib/cors

Recovery 中间件

在 app/common/response/response.go 文件中,添加 ServerError() 方法,作为 RecoveryFunc

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
go复制代码package response

import (
"github.com/gin-gonic/gin"
"jassue-gin/global"
"net/http"
"os"
)

// ...

func ServerError(c *gin.Context, err interface{}) {
msg := "Internal Server Error"
// 非生产环境显示具体错误信息
if global.App.Config.App.Env != "production" && os.Getenv(gin.EnvGinMode) != gin.ReleaseMode {
if _, ok := err.(error); ok {
msg = err.(error).Error()
}
}
c.JSON(http.StatusInternalServerError, Response{
http.StatusInternalServerError,
nil,
msg,
})
c.Abort()
}
// ...

新建 app/middleware/recovery.go 文件,编写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
go复制代码package middleware

import (
"github.com/gin-gonic/gin"
"gopkg.in/natefinch/lumberjack.v2"
"jassue-gin/app/common/response"
"jassue-gin/global"
)

func CustomRecovery() gin.HandlerFunc {
return gin.RecoveryWithWriter(
&lumberjack.Logger{
Filename: global.App.Config.Log.RootDir + "/" + global.App.Config.Log.Filename,
MaxSize: global.App.Config.Log.MaxSize,
MaxBackups: global.App.Config.Log.MaxBackups,
MaxAge: global.App.Config.Log.MaxAge,
Compress: global.App.Config.Log.Compress,
},
response.ServerError)
}

CORS 跨域处理

新建 app/middleware/cors.go 文件,编写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
go复制代码package middleware

import (
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)

func Cors() gin.HandlerFunc {
config := cors.DefaultConfig()
config.AllowAllOrigins = true
config.AllowHeaders = []string{"Origin", "Content-Length", "Content-Type", "Authorization"}
config.AllowCredentials = true
config.ExposeHeaders = []string{"New-Token", "New-Expires-In", "Content-Disposition"}

return cors.New(config)
}

使用中间件

在 bootstrap/router.go 文件,编写:

1
2
3
4
5
6
7
8
9
10
11
12
go复制代码func setupRouter() *gin.Engine {
if global.App.Config.App.Env == "production" {
gin.SetMode(gin.ReleaseMode)
}
router := gin.New()
router.Use(gin.Logger(), middleware.CustomRecovery())

// 跨域处理
// router.Use(middleware.Cors())

// ...
}

测试

为了演示,这里我故意将数据库配置写错,请求登录接口,中间件成功生效

Snip20211121_7.png

接着查看 storage/logs/app.log 文件,错误信息成功写入到文件,内容如下:

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
bash复制代码[31m2021/11/21 20:40:18 [Recovery] 2021/11/21 - 20:40:18 panic recovered:
POST /api/auth/login HTTP/1.1
Host: localhost:8888
Accept: */*
Accept-Encoding: gzip, deflate
Cache-Control: no-cache
Connection: keep-alive
Content-Length: 51
Content-Type: application/json
Postman-Token: 30136d3a-9a7d-43ff-bd6e-8f408dd20a7e
User-Agent: PostmanRuntime/7.18.0


runtime error: invalid memory address or nil pointer dereference
/usr/local/go/src/runtime/panic.go:221 (0x104a9c6)
panicmem: panic(memoryError)
/usr/local/go/src/runtime/signal_unix.go:735 (0x104a996)
sigpanic: panicmem()
/Users/sjj/go/pkg/mod/gorm.io/gorm@v1.21.16/gorm.go:355 (0x1537ed8)
(*DB).getInstance: if db.clone > 0 {
/Users/sjj/go/pkg/mod/gorm.io/gorm@v1.21.16/chainable_api.go:146 (0x152efdb)
(*DB).Where: tx = db.getInstance()
/Users/sjj/go/src/jassue-gin/app/services/user.go:31 (0x17a963e)
(*userService).Login: err = global.App.DB.Where("mobile = ?", params.Mobile).First(&user).Error
/Users/sjj/go/src/jassue-gin/app/controllers/app/auth.go:37 (0x17aab7b)
Login: if err, user := services.UserService.Login(form); err != nil {
/Users/sjj/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go:165 (0x1797681)
(*Context).Next: c.handlers[c.index](c)
/Users/sjj/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/recovery.go:99 (0x179766c)
CustomRecoveryWithWriter.func1: c.Next()
/Users/sjj/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go:165 (0x17968e6)
(*Context).Next: c.handlers[c.index](c)
/Users/sjj/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/logger.go:241 (0x17968c9)
LoggerWithConfig.func1: c.Next()
/Users/sjj/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/context.go:165 (0x1795e1d)
(*Context).Next: c.handlers[c.index](c)
/Users/sjj/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/gin.go:489 (0x1795aa5)
(*Engine).handleHTTPRequest: c.Next()
/Users/sjj/go/pkg/mod/github.com/gin-gonic/gin@v1.7.4/gin.go:445 (0x1795604)
(*Engine).ServeHTTP: engine.handleHTTPRequest(c)
/usr/local/go/src/net/http/server.go:2878 (0x129e49a)
serverHandler.ServeHTTP: handler.ServeHTTP(rw, req)
/usr/local/go/src/net/http/server.go:1929 (0x1299b47)
(*conn).serve: serverHandler{c.server}.ServeHTTP(w, w.req)
/usr/local/go/src/runtime/asm_amd64.s:1581 (0x1065ac0)
goexit: BYTE $0x90 // NOP
[0m

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…247248249…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%