spring源碼分析——BeanPostProcessor接口

 

  BeanPostProcessor是處理bean的後置接口,beanDefinitionMaps中的BeanDefinition實例化完成后,完成populateBean,屬性設置,完成

初始化后,這個接口支持對bean做自定義的操作。

一:BeanPostProcessor的使用

定義一個測試用的model對象,name屬性默認為hello

public class BeanDemo {

	private String name = "hello";

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		final StringBuffer sb = new StringBuffer("BeanDemo{");
		sb.append("name='").append(name).append('\'');
		sb.append('}');
		return sb.toString();
	}
}

  

自定義一個MyBeanPostProcessor類,實現BeanPostProcessor接口

@Service
public class MyBeanPostProcessor implements BeanPostProcessor {

	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return null;
	}

	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if(beanName.equals("beanDemo")){
			BeanDemo beanDemo = (BeanDemo)bean;
			beanDemo.setName("kitty");
			return beanDemo;
		}
		return bean;
	}
}

  

 

 

從運行結果看,spring中維護的beanName為beanDemo的對象,name屬性為ketty

 

 

二:看看源碼怎麼實現的

1:實例化並且註冊到beanPostProcessors集合中

 

 

 

 

主要的實例化邏輯在這個接口,這個接口的作用就是把所有實現BeanPostProcessor接口的類實例化,然後註冊到 beanPostProcessors這個緩存中

 

 

	public static void registerBeanPostProcessors(
			ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {

		// 獲取所有實現接口BeanPostProcessor的beanName
		String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);

		// Register BeanPostProcessorChecker that logs an info message when
		// a bean is created during BeanPostProcessor instantiation, i.e. when
		// a bean is not eligible for getting processed by all BeanPostProcessors.
		int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
		beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));

		// Separate between BeanPostProcessors that implement PriorityOrdered,
		// Ordered, and the rest.
		/**
		 * 把實現PriorityOrdered 和 Ordered 和 其他的處理器分開
		 */
		List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
		List<BeanPostProcessor> internalPostProcessors = new ArrayList<>();
		List<String> orderedPostProcessorNames = new ArrayList<>();
		List<String> nonOrderedPostProcessorNames = new ArrayList<>();
		/**
		 * 1:遍歷集合postProcessorNames
		 * 2:判斷類型,如果是PriorityOrdered,則實例化對象放入priorityOrderedPostProcessors集合,
		 * Ordered 則放入orderedPostProcessorNames集合,其他的放入nonOrderedPostProcessorNames集合
 		 */
		for (String ppName : postProcessorNames) {
			if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
				BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
				priorityOrderedPostProcessors.add(pp);
				if (pp instanceof MergedBeanDefinitionPostProcessor) {
					internalPostProcessors.add(pp);
				}
			}
			else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
				orderedPostProcessorNames.add(ppName);
			}
			else {
				nonOrderedPostProcessorNames.add(ppName);
			}
		}

		// First, register the BeanPostProcessors that implement PriorityOrdered.
		// 首先對priorityOrderedPostProcessors集合中實例對象排序,然後註冊,放入beanFactory中緩存下來
		sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
		registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);

		// Next, register the BeanPostProcessors that implement Ordered.
		// 然後再實例化實現Ordered接口的對象,完成註冊
		List<BeanPostProcessor> orderedPostProcessors = new ArrayList<>();
		for (String ppName : orderedPostProcessorNames) {
			BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
			orderedPostProcessors.add(pp);
			if (pp instanceof MergedBeanDefinitionPostProcessor) {
				internalPostProcessors.add(pp);
			}
		}
		sortPostProcessors(orderedPostProcessors, beanFactory);
		registerBeanPostProcessors(beanFactory, orderedPostProcessors);

		// Now, register all regular BeanPostProcessors.
		// 最後實例化什麼都沒有實現的,完成實例化並註冊
		List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<>();
		for (String ppName : nonOrderedPostProcessorNames) {
			BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
			nonOrderedPostProcessors.add(pp);
			if (pp instanceof MergedBeanDefinitionPostProcessor) {
				internalPostProcessors.add(pp);
			}
		}
		registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);

		// Finally, re-register all internal BeanPostProcessors.
		// 最後再次註冊內部postProcessor
		sortPostProcessors(internalPostProcessors, beanFactory);
		registerBeanPostProcessors(beanFactory, internalPostProcessors);

		// Re-register post-processor for detecting inner beans as ApplicationListeners,
		// moving it to the end of the processor chain (for picking up proxies etc).
		beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
	}

  

 

 

定義四類容器,高優先級有序、有序、無序、內部

 

 分類放入四種容器:

 

 

註冊BeanPostProcessor,將實現BeanPostProcessor接口的對象放入beanPostProcessors緩存中

 

 

 

 

 

 

註冊完PriorityOrdered的實現類后,再處理Ordered的實現類

 

 

註冊什麼都沒有實現的BeanPostProcessor接口實現類,

 

 

最後註冊內部的BeanPostProcessor對象

 

 到這裏BeanPostProcessor的實例化以及註冊工作完成,在beanFactory的beanPostProcessors集合中已經緩存了所有的beanPostProcessor的對象

 

2:BeanPostProcessor的使用

因為這個接口是bean的後置接口,所以需要bean創建並初始化完成,才可以發揮作用,上一步的緩存只是埋好點,以備使用,因為bean的實例化流程我們

還沒有分析,這裏直接看一下怎麼使用的

 

 

我們看一下init方法后的攔截,因為這個時候已經init完成,可以在後置接口中對bean做一下修改的操作

 

 

調用到我們自定義的MyBeanPostProcessor實現類:

 

 

把這個beanDemo對象屬性修改一下,修改完,再返回,將這個對象緩存到spring的一級緩存中。

 

 

總結:

  BeanPostProcessor接口主要是對bean對象做一些自定義的操作,修改bean對象的信息,aop代理也是通過這種方式實現的,

在refresh的registryBeanPostProcessor方法中實例化BeanPostProcessor對象,並且註冊到beanFactory容器的beanPostProcessors的緩存中,

然後在後續的操作中攔截使用。

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司“嚨底家”!

※推薦評價好的iphone維修中心

聚甘新

菜渣開源一個基於 EMIT 的 AOP 庫(.NET Core)

目錄

  • 1,快速入門
    • 1.1 繼承 ActionAttribute 特性
    • 1.2 標記代理類型
    • 2,如何創建代理類型
      • 2.1 通過API直接創建
  • 2,創建代理類型
    • 通過API
    • 通過 Microsoft.Extensions.DependencyInjection
      • 通過 Autofac
  • 3,深入使用
    • 代理類型
    • 方法、屬性代理
    • 上下文
      • 攔截方法或屬性的參數
    • 非侵入式代理

Nuget 庫地址:https://www.nuget.org/packages/CZGL.AOP/

Github 庫地址:https://github.com/whuanle/CZGL.AOP

CZGL.AOP 是 基於 EMIT 編寫的 一個簡單輕量的AOP框架,支持非侵入式代理,支持.NET Core/ASP.NET Core,以及支持多種依賴注入框架。

1,快速入門

CZGL.AOP 使用比較簡單,你只需要使用 [Interceptor] 特性標記需要代理的類型,然後使用繼承 ActionAttribute 的特性標記要被代理的方法或屬性。

1.1 繼承 ActionAttribute 特性

ActionAttribute 是用於代理方法或屬性的特性標記,不能直接使用,需要繼承后重寫方法。

示例如下:

    public class LogAttribute : ActionAttribute
    {
        public override void Before(AspectContext context)
        {
            Console.WriteLine("執行前");
        }

        public override object After(AspectContext context)
        {
            Console.WriteLine("執行后");
            if (context.IsMethod)
                return context.MethodResult;
            else if (context.IsProperty)
                return context.PropertyValue;
            return null;
        }
    }

Before 會在被代理的方法執行前或被代理的屬性調用時生效,你可以通過 AspectContext 上下文,獲取、修改傳遞的參數。

After 在方法執行后或屬性調用時生效,你可以通過上下文獲取、修改返回值。

1.2 標記代理類型

在被代理的類型中,使用 [Interceptor] 特性來標記,在需要代理的方法中,使用 繼承了 ActionAttribute 的特性來標記。

此方法是侵入式的,需要在編譯前完成。

[Interceptor]
public class Test : ITest
{
    [Log] public virtual string A { get; set; }
    [Log]
    public virtual void MyMethod()
    {
        Console.WriteLine("運行中");
    }
}

注意的是,一個方法或屬性只能設置一個攔截器。

2,如何創建代理類型

CZGL.AOP 有多種生成代理類型的方式,下面介紹簡單的方式。

請預先創建如下代碼:

    public class LogAttribute : ActionAttribute
    {
        public override void Before(AspectContext context)
        {
            Console.WriteLine("執行前");
        }

        public override object After(AspectContext context)
        {
            Console.WriteLine("執行后");
            if (context.IsMethod)
                return context.MethodResult;
            else if (context.IsProperty)
                return context.PropertyValue;
            return null;
        }
    }

    public interface ITest
    {
        void MyMethod();
    }

    [Interceptor]
    public class Test : ITest
    {
        [Log] public virtual string A { get; set; }
        public Test()
        {
            Console.WriteLine("構造函數沒問題");
        }
        [Log]
        public virtual void MyMethod()
        {
            Console.WriteLine("運行中");
        }
    }

2.1 通過API直接創建

通過 CZGL.AOP 中的 AopInterceptor 類,你可以生成代理類型。

示例如下:

            ITest test1 = AopInterceptor.CreateProxyOfInterface<ITest, Test>();
            Test test2 = AopInterceptor.CreateProxyOfClass<Test>();
            test1.MyMethod();
            test2.MyMethod();

CreateProxyOfInterface 通過接口創建代理類型;CreateProxyOfClass 通過類創建代理類型;

默認調用的是無參構造函數。

2,創建代理類型

通過API

你可以參考源碼解決方案

中的 ExampleConsole 項目。

如果要直接使用 AopInterceptor.CreateProxyOfInterfaceAopInterceptor.CreateProxyOfClass 方法,通過接口或類來創建代理類型。

        ITest test1 = AopInterceptor.CreateProxyOfInterface<ITest, Test>();
        Test test2 = AopInterceptor.CreateProxyOfClass<Test>();

如果要指定實例化的構造函數,可以這樣:

            // 指定構造函數
            test2 = AopInterceptor.CreateProxyOfClass<Test>("aaa", "bbb");
            test2.MyMethod();

通過 Microsoft.Extensions.DependencyInjection

Microsoft.Extensions.DependencyInjection 是 .NET Core/ASP.NET Core 默認的依賴注入容器。

如果需要支持 ASP.NET Core 中使用 AOP,你可以在 Nuget 包中安裝 CZGL.AOP.MEDI

如果你在控制台下使用 Microsoft.Extensions.DependencyInjection,你可以使用名為 BuildAopProxyIServiceCollection 拓展方法來為容器中的類型,生成代理類型。

示例如下:

            IServiceCollection _services = new ServiceCollection();
            _services.AddTransient<ITest, Test>();
            var serviceProvider = _services.BuildAopProxy().BuildServiceProvider();
            serviceProvider.GetService<ITest>();
            return serviceProvider;

你可以參考源碼解決方案中的 ExampleMEDI 項目。

如果你要在 ASP.NET Core 中使用,你可以在 Startup 中,ConfigureServices 方法的最後一行代碼使用 services.BuildAopProxy();

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.BuildAopProxy();
        }

還可以在 ProgramIHostBuilder 中使用 .UseServiceProviderFactory(new AOPServiceProxviderFactory()) 來配置使用 CZGL.AOP。

示例:

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
            .UseServiceProviderFactory(new AOPServiceProxviderFactory())
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });

可以參考解決方案中的 ExampleConsoleExampleWebMEDI 兩個項目。

你不必擔心引入 CZGL.AOP 后,使用依賴注入會使程序變慢或者破壞容器中的原有屬性。CZGL.AOP 只會在創建容器時處理需要被代理的類型,不會影響容器中的服務,也不會幹擾到依賴注入的執行。

通過 Autofac

如果需要在 Autofac 中使用 AOP,則需要引用 CZGL.AOP.Autofac 包。

如果你在控制台程序中使用 Autofac,則可以在 Build() 後面使用 BuildAopProxy()

            ContainerBuilder builder = new ContainerBuilder();
            builder.RegisterType<Test>().As<ITest>();
            var container = builder.Build().BuildAopProxy();

            using (ILifetimeScope scope = container.BeginLifetimeScope())
            {
                // 獲取實例
                ITest myService = scope.Resolve<ITest>();
                myService.MyMethod();
            }

            Console.ReadKey();
        }

要注意的是,在已經完成的組件註冊創建一個新的容器后,才能調用 BuildAopProxy() 方法,

這樣針對一個新的容器你可以考慮是否需要對容器中的組件進行代理。

如果在 ASP.NET Core 中使用 Autofac,你需要在 Program 類的 IHostBuilder 中使用:

.UseServiceProviderFactory(new AutofacServiceProviderFactory())

如果需要代理已經註冊的組件,則將其替換為:

 .UseServiceProviderFactory(new CZGL.AOP.Autofac.AOPServiceProxviderFactory())

請參考 源碼解決方案中的 ExampleAutofacExampleWebAutofac 兩個項目。

3,深入使用

代理類型

要被代理的類型,需要使用 [Interceptor]來標記,例如:

    [Interceptor]
    public class Test : ITest
    {
    }

支持泛型類型。

被代理的類型必須是可被繼承的。

類型的構造函數沒有限制,你可以隨意編寫。

在使用 API 創建代理類型並且實例化時,你可以指定使用哪個構造函數。

例如:

			string a="",b="",c="";
			ITest test1 = AopInterceptor.CreateProxyOfInterface<ITest, Test>(a,b,c);

API 會根據參數的多少以及參數的類型自動尋找合適的構造函數。

方法、屬性代理

為了代理方法或屬性,你需要繼承 ActionAttribute 特性,然後為方法或屬性標記此特性,並且將方法或屬性設置為 virtual

一個類型中的不同方法,可以使用不同的攔截器。

        [Log1]
        public virtual void MyMethod1(){}
        
        [Log2]
        public virtual void MyMethod2(){}

對於屬性,可以在屬性上直接使用特性,或者只在 get 或 set 構造器使用。

        [Log] public virtual string A { get; set; }
        
        // 或
        public virtual string A { [Log] get; set; }
        
        // 或
        public virtual string A { get; [Log] set; }

如果在屬性上使用特性,相當於 [Log] get; [Log] set;

上下文

一個簡單的方法或屬性攔截標記是這樣的:

    public class LogAttribute : ActionAttribute
    {
        public override void Before(AspectContext context)
        {
            Console.WriteLine("執行前");
        }

        public override object After(AspectContext context)
        {
            Console.WriteLine("執行后");
            if (context.IsMethod)
                return context.MethodResult;
            else if (context.IsProperty)
                return context.PropertyValue;
            return null;
        }
    }

AspectContext 的屬性說明如下:

字段 說明
Type 當前被代理類型生成的代理類型
ConstructorParamters 類型被實例化時使用的構造函數的參數,如果構造函數沒有參數,則 MethodValues.Length = 0,而不是 MethodValues 為 null。
IsProperty 當前攔截的是屬性
PropertyInfo 當前被執行的屬性的信息,可為 null。
PropertyValue 但調用的是屬性時,返回 get 的結果或 set 的 value 值。
IsMethod 當前攔截的是方法
MethodInfo 當前方法的信息
MethodValues 方法被調用時傳遞的參數,如果此方法沒有參數,則 MethodValues.Length = 0,而不是 MethodValues 為 null
MethodResult 方法執行返回的結果(如果有)

攔截方法或屬性的參數

通過上下文,你可以修改方法或屬性的參數以及攔截返回結果:

    public class LogAttribute : ActionAttribute
    {
        public override void Before(AspectContext context)
        {
            // 攔截並修改方法的參數
            for (int i = 0; i < context.MethodValues.Length; i++)
            {
                context.MethodValues[i] = (int)context.MethodValues[i] + 1;
            }
            Console.WriteLine("執行前");
        }

        public override object After(AspectContext context)
        {
            Console.WriteLine("執行后");

            // 攔截方法的執行結果
            context.MethodResult = (int)context.MethodResult + 664;

            if (context.IsMethod)
                return context.MethodResult;
            else if (context.IsProperty)
                return context.PropertyValue;
            return null;
        }
    }

    [Interceptor]
    public class Test
    {
        [Log]
        public virtual int Sum(int a, int b)
        {
            Console.WriteLine("運行中");
            return a + b;
        }
    }
            Test test = AopInterceptor.CreateProxyOfClass<Test>();

            Console.WriteLine(test.Sum(1, 1));

方法的參數支持 inrefout;支持泛型方法泛型屬性;支持異步方法;

非侵入式代理

此種方式不需要改動被代理的類型,你也可以代理程序集中的類型。

    public class LogAttribute : ActionAttribute
    {
        public override void Before(AspectContext context)
        {
            Console.WriteLine("執行前");
        }

        public override object After(AspectContext context)
        {
            Console.WriteLine("執行后");
            if (context.IsMethod)
                return context.MethodResult;
            else if (context.IsProperty)
                return context.PropertyValue;
            return null;
        }
    }
    public class TestNo
    {
        public virtual string A { get; set; }
        public virtual void MyMethod()
        {
            Console.WriteLine("運行中");
        }
    }
            TestNo test3 = AopInterceptor.CreateProxyOfType<TestNo>(new ProxyTypeBuilder()
                .AddProxyMethod(typeof(LogAttribute), typeof(TestNo).GetMethod(nameof(TestNo.MyMethod)))
                .AddProxyMethod(typeof(LogAttribute), typeof(TestNo).GetProperty(nameof(TestNo.A)).GetSetMethod()));

通過 ProxyTypeBuilder 來構建代理類型。

代理方法或屬性都是使用 AddProxyMethod,第一個參數是要使用的攔截器,第二個參數是要攔截的方法。

如果要攔截屬性,請分開設置屬性的 getset 構造。

如果多個方法或屬性使用同一個攔截器,則可以這樣:

            TestNo test3 = AopInterceptor.CreateProxyOfType<TestNo>(
                new ProxyTypeBuilder(new Type[] { typeof(LogAttribute) })
                .AddProxyMethod("LogAttribute", typeof(TestNo).GetMethod(nameof(TestNo.MyMethod)))
                .AddProxyMethod("LogAttribute", typeof(TestNo).GetProperty(nameof(TestNo.A)).GetSetMethod()));
            TestNo test3 = AopInterceptor.CreateProxyOfType<TestNo>(
                new ProxyTypeBuilder(new Type[] { typeof(LogAttribute) })
                .AddProxyMethod("LogAttribute", typeof(TestNo).GetMethod(nameof(TestNo.MyMethod)))
                .AddProxyMethod(typeof(LogAttribute2), typeof(TestNo).GetProperty(nameof(TestNo.A)).GetSetMethod()));

在構造函數中傳遞過去所需要的攔截器,然後在攔截時使用。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司“嚨底家”!

※推薦評價好的iphone維修中心

聚甘新

【Flutter實戰】六大布局組件及半圓菜單案例

老孟導讀:Flutter中布局組件有水平 / 垂直布局組件( RowColumn )、疊加布局組件( StackIndexedStack )、流式布局組件( Wrap )和 自定義布局組件(Flow)。

水平、垂直布局組件

Row 是將子組件以水平方式布局的組件, Column 是將子組件以垂直方式布局的組件。項目中 90% 的頁面布局都可以通過 Row 和 Column 來實現。

將3個組件水平排列:

Row(
  children: <Widget>[
    Container(
      height: 50,
      width: 100,
      color: Colors.red,
    ),
    Container(
      height: 50,
      width: 100,
      color: Colors.green,
    ),
    Container(
      height: 50,
      width: 100,
      color: Colors.blue,
    ),
  ],
)

將3個組件垂直排列:

Column(
  mainAxisSize: MainAxisSize.min,
  children: <Widget>[
    Container(
      height: 50,
      width: 100,
      color: Colors.red,
    ),
    Container(
      height: 50,
      width: 100,
      color: Colors.green,
    ),
    Container(
      height: 50,
      width: 100,
      color: Colors.blue,
    ),
  ],
)

在 Row 和 Column 中有一個非常重要的概念:主軸( MainAxis )交叉軸( CrossAxis ),主軸就是與組件布局方向一致的軸,交叉軸就是與主軸方向垂直的軸。

具體到 Row 組件,主軸 是水平方向,交叉軸 是垂直方向。而 Column 與 Row 正好相反,主軸 是垂直方向,交叉軸 是水平方向。

明白了 主軸 和 交叉軸 概念,我們來看下 mainAxisAlignment 屬性,此屬性表示主軸方向的對齊方式,默認值為 start,表示從組件的開始處布局,此處的開始位置和 textDirection 屬性有關,textDirection 表示文本的布局方向,其值包括 ltr(從左到右) 和 rtl(從右到左),當 textDirection = ltr 時,start 表示左側,當 textDirection = rtl 時,start 表示右側,

Container(
  decoration: BoxDecoration(border: Border.all(color: Colors.black)),
  child: Row(
    children: <Widget>[
      Container(
        height: 50,
        width: 100,
        color: Colors.red,
      ),
      Container(
        height: 50,
        width: 100,
        color: Colors.green,
      ),
      Container(
        height: 50,
        width: 100,
        color: Colors.blue,
      ),
    ],
  ),
)

黑色邊框是Row控件的範圍,默認情況下Row鋪滿父組件。

主軸對齊方式有6種,效果如下圖:

spaceAround 和 spaceEvenly 區別是:

  • spaceAround :第一個子控件距開始位置和最後一個子控件距結尾位置是其他子控件間距的一半。
  • spaceEvenly : 所有間距一樣。

和主軸對齊方式相對應的就是交叉軸對齊方式 crossAxisAlignment ,交叉軸對齊方式默認是居中。Row控件的高度是依賴子控件高度,因此子控件高都一樣時,Row的高和子控件高相同,此時是無法體現交叉軸對齊方式,修改3個顏色塊高分別為50,100,150,這樣Row的高是150,代碼如下:

Container(
      decoration: BoxDecoration(border: Border.all(color: Colors.black)),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: <Widget>[
          Container(
            height: 50,
            width: 100,
            color: Colors.red,
          ),
          Container(
            height: 100,
            width: 100,
            color: Colors.green,
          ),
          Container(
            height: 150,
            width: 100,
            color: Colors.blue,
          ),
        ],
      ),
    )

主軸對齊方式效果如下圖:

mainAxisSize 表示主軸尺寸,有 min 和 max 兩種方式,默認是 maxmin 表示盡可能小,max 表示盡可能大。

Container(
	decoration: BoxDecoration(border: Border.all(color: Colors.black)),
	child: Row(
		mainAxisSize: MainAxisSize.min,
		...
	)
)

看黑色邊框,正好包裹子組件,而 max 效果如下:

textDirection 表示子組件主軸布局方向,值包括 ltr(從左到右) 和 rtl(從右到左)

Container(
  decoration: BoxDecoration(border: Border.all(color: Colors.black)),
  child: Row(
    textDirection: TextDirection.rtl,
    children: <Widget>[
      ...
    ],
  ),
)

verticalDirection 表示子組件交叉軸布局方向:

  • up :從底部開始,並垂直堆疊到頂部,對齊方式的 start 在底部,end 在頂部。
  • down: 與 up 相反。
Container(
  decoration: BoxDecoration(border: Border.all(color: Colors.black)),
  child: Row(
    crossAxisAlignment: CrossAxisAlignment.start,
    verticalDirection: VerticalDirection.up,
    children: <Widget>[
      Container(
        height: 50,
        width: 100,
        color: Colors.red,
      ),
      Container(
        height: 100,
        width: 100,
        color: Colors.green,
      ),
      Container(
        height: 150,
        width: 100,
        color: Colors.blue,
      ),
    ],
  ),
)

想一想這種效果完全可以通過對齊方式實現,那麼為什麼還要有 textDirectionverticalDirection 這兩個屬性,官方API文檔已經解釋了這個問題:

This is also used to disambiguate start and end values (e.g. [MainAxisAlignment.start] or [CrossAxisAlignment.end]).

用於消除 MainAxisAlignment.start 和 CrossAxisAlignment.end 值的歧義的。

疊加布局組件

疊加布局組件包含 StackIndexedStack,Stack 組件將子組件疊加显示,根據子組件的順利依次向上疊加,用法如下:

Stack(
  children: <Widget>[
    Container(
      height: 200,
      width: 200,
      color: Colors.red,
    ),
    Container(
      height: 170,
      width: 170,
      color: Colors.blue,
    ),
    Container(
      height: 140,
      width: 140,
      color: Colors.yellow,
    )
  ],
)

Stack 對未定位(不被 Positioned 包裹)子組件的大小由 fit 參數決定,默認值是 StackFit.loose ,表示子組件自己決定,StackFit.expand 表示盡可能的大,用法如下:

Stack(
  fit: StackFit.expand,
  children: <Widget>[
    Container(
      height: 200,
      width: 200,
      color: Colors.red,
    ),
    Container(
      height: 170,
      width: 170,
      color: Colors.blue,
    ),
    Container(
      height: 140,
      width: 140,
      color: Colors.yellow,
    )
  ],
)

效果只有黃色(最後一個組件的顏色),並不是其他組件沒有繪製,而是另外兩個組件被黃色組件覆蓋。

Stack 對未定位(不被 Positioned 包裹)子組件的對齊方式由 alignment 控制,默認左上角對齊,用法如下:

Stack(
  alignment: AlignmentDirectional.center,
  children: <Widget>[
    Container(
      height: 200,
      width: 200,
      color: Colors.red,
    ),
    Container(
      height: 170,
      width: 170,
      color: Colors.blue,
    ),
    Container(
      height: 140,
      width: 140,
      color: Colors.yellow,
    )
  ],
)

通過 Positioned 定位的子組件:

Stack(
  alignment: AlignmentDirectional.center,
  children: <Widget>[
    Container(
      height: 200,
      width: 200,
      color: Colors.red,
    ),
    Container(
      height: 170,
      width: 170,
      color: Colors.blue,
    ),
    Positioned(
      left: 30,
      right: 40,
      bottom: 50,
      top: 60,
      child: Container(
        color: Colors.yellow,
      ),
    )
  ],
)

topbottomleftright 四種定位屬性,分別表示距離上下左右的距離。

如果子組件超過 Stack 邊界由 overflow 控制,默認是裁剪,下面設置總是显示的用法:

Stack(
  overflow: Overflow.visible,
  children: <Widget>[
    Container(
      height: 200,
      width: 200,
      color: Colors.red,
    ),
    Positioned(
      left: 100,
      top: 100,
      height: 150,
      width: 150,
      child: Container(
        color: Colors.green,
      ),
    )
  ],
)

IndexedStack 是 Stack 的子類,Stack 是將所有的子組件疊加显示,而 IndexedStack 通過 index 只显示指定索引的子組件,用法如下:

class IndexedStackDemo extends StatefulWidget {
  @override
  _IndexedStackDemoState createState() => _IndexedStackDemoState();
}

class _IndexedStackDemoState extends State<IndexedStackDemo> {
  int _index = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        SizedBox(height: 50,),
        _buildIndexedStack(),
        SizedBox(height: 30,),
        _buildRow(),
      ],
    );
  }

  _buildIndexedStack() {
    return IndexedStack(
      index: _index,
      children: <Widget>[
        Center(
          child: Container(
            height: 300,
            width: 300,
            color: Colors.red,
            alignment: Alignment.center,
            child: Icon(
              Icons.fastfood,
              size: 60,
              color: Colors.blue,
            ),
          ),
        ),
        Center(
          child: Container(
            height: 300,
            width: 300,
            color: Colors.green,
            alignment: Alignment.center,
            child: Icon(
              Icons.cake,
              size: 60,
              color: Colors.blue,
            ),
          ),
        ),
        Center(
          child: Container(
            height: 300,
            width: 300,
            color: Colors.yellow,
            alignment: Alignment.center,
            child: Icon(
              Icons.local_cafe,
              size: 60,
              color: Colors.blue,
            ),
          ),
        ),
      ],
    );
  }

  _buildRow() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        IconButton(
          icon: Icon(Icons.fastfood),
          onPressed: () {
            setState(() {
              _index = 0;
            });
          },
        ),
        IconButton(
          icon: Icon(Icons.cake),
          onPressed: () {
            setState(() {
              _index = 1;
            });
          },
        ),
        IconButton(
          icon: Icon(Icons.local_cafe),
          onPressed: () {
            setState(() {
              _index = 2;
            });
          },
        ),
      ],
    );
  }
}

流式布局組件

Wrap 為子組件進行水平或者垂直方向布局,且當空間用完時,Wrap 會自動換行,也就是流式布局。

創建多個子控件做為 Wrap 的子控件,代碼如下:

Wrap(
  children: List.generate(10, (i) {
    double w = 50.0 + 10 * i;
    return Container(
      color: Colors.primaries[i],
      height: 50,
      width: w,
      child: Text('$i'),
    );
  }),
)

direction 屬性控制布局方向,默認為水平方向,設置方向為垂直代碼如下:

Wrap(
  direction: Axis.vertical,
  children: List.generate(4, (i) {
    double w = 50.0 + 10 * i;
    return Container(
      color: Colors.primaries[i],
      height: 50,
      width: w,
      child: Text('$i'),
    );
  }),
)

alignment 屬性控制主軸對齊方式,crossAxisAlignment 屬性控制交叉軸對齊方式,對齊方式只對有剩餘空間的行或者列起作用,例如水平方向上正好填充完整,則不管設置主軸對齊方式為什麼,看上去的效果都是鋪滿。

說明 :主軸就是與當前組件方向一致的軸,而交叉軸就是與當前組件方向垂直的軸,如果Wrap的布局方向為水平方向 Axis.horizontal,那麼主軸就是水平方向,反之布局方向為垂直方向 Axis.vertical ,主軸就是垂直方向。

Wrap(
	alignment: WrapAlignment.spaceBetween,
	...
)

主軸對齊方式有6種,效果如下圖:

spaceAroundspaceEvenly 區別是:

  • spaceAround:第一個子控件距開始位置和最後一個子控件距結尾位置是其他子控件間距的一半。
  • spaceEvenly:所有間距一樣。

設置交叉軸對齊代碼如下:

Wrap(
	crossAxisAlignment: WrapCrossAlignment.center,
	...
)

如果 Wrap 的主軸方向為水平方向,交叉軸方向則為垂直方向,如果想要看到交叉軸對齊方式的效果需要設置子控件的高不一樣,代碼如下:

Wrap(
  spacing: 5,
  runSpacing: 3,
  crossAxisAlignment: WrapCrossAlignment.center,
  children: List.generate(10, (i) {
    double w = 50.0 + 10 * i;
    double h = 50.0 + 5 * i;
    return Container(
      color: Colors.primaries[i],
      height: h,
      alignment: Alignment.center,
      width: w,
      child: Text('$i'),
    );
  }),
)

runAlignment 屬性控制 Wrap 的交叉抽方向上每一行的對齊方式,下面直接看 runAlignment 6中方式對應的效果圖,

runAlignmentalignment 的區別:

  • alignment :是主軸方向上對齊方式,作用於每一行。
  • runAlignment :是交叉軸方向上將每一行看作一個整體的對齊方式。

spacingrunSpacing 屬性控制Wrap主軸方向和交叉軸方向子控件之間的間隙,代碼如下:

Wrap(
	spacing: 5,
    runSpacing: 2,
	...
)

textDirection 屬性表示 Wrap 主軸方向上子組件的方向,取值範圍是 ltr(從左到右) 和 rtl(從右到左),下面是從右到左的代碼:

Wrap(
	textDirection: TextDirection.rtl,
	...
)

verticalDirection 屬性表示 Wrap 交叉軸方向上子組件的方向,取值範圍是 up(向上) 和 down(向下),設置代碼如下:

Wrap(
	verticalDirection: VerticalDirection.up,
	...
)

注意:文字為0的組件是在下面的。

自定義布局組件

大部分情況下,不會使用到 Flow ,但 Flow 可以調整子組件的位置和大小,結合Matrix4繪製出各種酷炫的效果。

Flow 組件對使用轉換矩陣操作子組件經過系統優化,性能非常高效。

基本用法如下:

Flow(
  delegate: SimpleFlowDelegate(),
  children: List.generate(5, (index) {
    return Container(
      height: 100,
      color: Colors.primaries[index % Colors.primaries.length],
    );
  }),
)

delegate 控制子組件的位置和大小,定義如下 :

class SimpleFlowDelegate extends FlowDelegate {
  @override
  void paintChildren(FlowPaintingContext context) {
    for (int i = 0; i < context.childCount; ++i) {
      context.paintChild(i);
    }
  }

  @override
  bool shouldRepaint(SimpleFlowDelegate oldDelegate) {
    return false;
  }
}

delegate 要繼承 FlowDelegate,重寫 paintChildrenshouldRepaint 函數,上面直接繪製子組件,效果如下:

只看到一種顏色並不是只繪製了這一個,而是疊加覆蓋了,和 Stack 類似,下面讓每一個組件有一定的偏移,SimpleFlowDelegate 修改如下:

class SimpleFlowDelegate extends FlowDelegate {
  @override
  void paintChildren(FlowPaintingContext context) {
    for (int i = 0; i < context.childCount; ++i) {
      context.paintChild(i,transform: Matrix4.translationValues(0,i*30.0,0));
    }
  }

  @override
  bool shouldRepaint(SimpleFlowDelegate oldDelegate) {
    return false;
  }
}

每一個子組件比上一個組件向下偏移30。

仿 掘金-我的效果

效果如下:

到拿到一個頁面時,先要將其拆分,上面的效果拆分如下:

總體分為3個部分,水平布局,紅色區域圓形頭像代碼如下:

_buildCircleImg() {
  return Container(
    height: 60,
    width: 60,
    decoration: BoxDecoration(
        shape: BoxShape.circle,
        image: DecorationImage(image: AssetImage('assets/images/logo.png'))),
  );
}

藍色區域代碼如下:

_buildCenter() {
  return Column(
    mainAxisAlignment: MainAxisAlignment.center,
    crossAxisAlignment: CrossAxisAlignment.start,
    children: <Widget>[
      Text('老孟Flutter', style: TextStyle(fontSize: 20),),
      Text('Flutter、Android', style: TextStyle(color: Colors.grey),)
    ],
  );
}

綠色區域是一個圖標,代碼如下:

Icon(Icons.arrow_forward_ios,color: Colors.grey,size: 14,),

將這3部分組合在一起:

Container(
  color: Colors.grey.withOpacity(.5),
  alignment: Alignment.center,
  child: Container(
    height: 100,
    color: Colors.white,
    child: Row(
      children: <Widget>[
        SizedBox(
          width: 15,
        ),
        _buildCircleImg(),
        SizedBox(
          width: 25,
        ),
        Expanded(
          child: _buildCenter(),
        ),
        Icon(Icons.arrow_forward_ios,color: Colors.grey,size: 14,),
        SizedBox(
          width: 15,
        ),
      ],
    ),
  ),
)

最終的效果就是開始我們看到的效果圖。

水平展開/收起菜單

使用Flow實現水平展開/收起菜單的功能,代碼如下:

class DemoFlowPopMenu extends StatefulWidget {
  @override
  _DemoFlowPopMenuState createState() => _DemoFlowPopMenuState();
}

class _DemoFlowPopMenuState extends State<DemoFlowPopMenu>
    with SingleTickerProviderStateMixin {
  //動畫必須要with這個類
  AnimationController _ctrlAnimationPopMenu; //定義動畫的變量
  IconData lastTapped = Icons.notifications;
  final List<IconData> menuItems = <IconData>[
    //菜單的icon
    Icons.home,
    Icons.new_releases,
    Icons.notifications,
    Icons.settings,
    Icons.menu,
  ];

  void _updateMenu(IconData icon) {
    if (icon != Icons.menu) {
      setState(() => lastTapped = icon);
    } else {
      _ctrlAnimationPopMenu.status == AnimationStatus.completed
          ? _ctrlAnimationPopMenu.reverse() //展開和收攏的效果
          : _ctrlAnimationPopMenu.forward();
    }
  }

  @override
  void initState() {
    super.initState();
    _ctrlAnimationPopMenu = AnimationController(
      //必須初始化動畫變量
      duration: const Duration(milliseconds: 250), //動畫時長250毫秒
      vsync: this, //SingleTickerProviderStateMixin的作用
    );
  }

//生成Popmenu數據
  Widget flowMenuItem(IconData icon) {
    final double buttonDiameter =
        MediaQuery.of(context).size.width * 2 / (menuItems.length * 3);
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8.0),
      child: RawMaterialButton(
        fillColor: lastTapped == icon ? Colors.amber[700] : Colors.blue,
        splashColor: Colors.amber[100],
        shape: CircleBorder(),
        constraints: BoxConstraints.tight(Size(buttonDiameter, buttonDiameter)),
        onPressed: () {
          _updateMenu(icon);
        },
        child: Icon(icon, color: Colors.white, size: 30.0),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Flow(
        delegate: FlowMenuDelegate(animation: _ctrlAnimationPopMenu),
        children: menuItems
            .map<Widget>((IconData icon) => flowMenuItem(icon))
            .toList(),
      ),
    );
  }
}

FlowMenuDelegate 定義如下:

class FlowMenuDelegate extends FlowDelegate {
  FlowMenuDelegate({this.animation}) : super(repaint: animation);
  final Animation<double> animation;

  @override
  void paintChildren(FlowPaintingContext context) {
    double x = 50.0; //起始位置
    double y = 50.0; //橫向展開,y不變
    for (int i = 0; i < context.childCount; ++i) {
      x = context.getChildSize(i).width * i * animation.value;
      context.paintChild(
        i,
        transform: Matrix4.translationValues(x, y, 0),
      );
    }
  }

  @override
  bool shouldRepaint(FlowMenuDelegate oldDelegate) =>
      animation != oldDelegate.animation;
}

半圓菜單展開/收起

代碼如下:

import 'dart:math';

import 'package:flutter/material.dart';

class DemoFlowMenu extends StatefulWidget {
  @override
  _DemoFlowMenuState createState() => _DemoFlowMenuState();
}

class _DemoFlowMenuState extends State<DemoFlowMenu>
    with TickerProviderStateMixin {
  //動畫需要這個類來混合
  //動畫變量,以及初始化和銷毀
  AnimationController _ctrlAnimationCircle;

  @override
  void initState() {
    super.initState();
    _ctrlAnimationCircle = AnimationController(
        //初始化動畫變量
        lowerBound: 0,
        upperBound: 80,
        duration: Duration(milliseconds: 300),
        vsync: this);
    _ctrlAnimationCircle.addListener(() => setState(() {}));
  }

  @override
  void dispose() {
    _ctrlAnimationCircle.dispose(); //銷毀變量,釋放資源
    super.dispose();
  }

  //生成Flow的數據
  List<Widget> _buildFlowChildren() {
    return List.generate(
        5,
        (index) => Container(
              child: Icon(
                index.isEven ? Icons.timer : Icons.ac_unit,
                color: Colors.primaries[index % Colors.primaries.length],
              ),
            ));
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        Positioned.fill(
          child: Flow(
            delegate: FlowAnimatedCircle(_ctrlAnimationCircle.value),
            children: _buildFlowChildren(),
          ),
        ),
        Positioned.fill(
          child: IconButton(
            icon: Icon(Icons.menu),
            onPressed: () {
              setState(() {
                //點擊后讓動畫可前行或回退
                _ctrlAnimationCircle.status == AnimationStatus.completed
                    ? _ctrlAnimationCircle.reverse()
                    : _ctrlAnimationCircle.forward();
              });
            },
          ),
        ),
      ],
    );
  }
}

FlowAnimatedCircle 代碼如下:

class FlowAnimatedCircle extends FlowDelegate {
  final double radius; //綁定半徑,讓圓動起來
  FlowAnimatedCircle(this.radius);

  @override
  void paintChildren(FlowPaintingContext context) {
    if (radius == 0) {
      return;
    }
    double x = 0; //開始(0,0)在父組件的中心
    double y = 0;
    for (int i = 0; i < context.childCount; i++) {
      x = radius * cos(i * pi / (context.childCount - 1)); //根據數學得出坐標
      y = radius * sin(i * pi / (context.childCount - 1)); //根據數學得出坐標
      context.paintChild(i, transform: Matrix4.translationValues(x, -y, 0));
    } //使用Matrix定位每個子組件
  }

  @override
  bool shouldRepaint(FlowDelegate oldDelegate) => true;
}

交流

老孟Flutter博客地址(330個控件用法):http://laomengit.com

歡迎加入Flutter交流群(微信:laomengit)、關注公眾號【老孟Flutter】:

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司“嚨底家”!

※推薦評價好的iphone維修中心

聚甘新

德國小城弗萊堡 交通轉型有成 35年私人汽車減半

環境資訊中心特約記者 陳文姿報導

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司“嚨底家”!

※推薦評價好的iphone維修中心

聚甘新

第七屆中國國際新能源汽車論壇2017打造全球規模最大,最國際化的新能源汽車論壇

隨著全球能源危機的加重及汽車排放引起的環境問題日益得到世界各國的重視,發展環保節能的汽車已經成為全球汽車行業的必然趨勢。

在過去六屆新能源汽車論壇成功舉辦的基礎上,由希邁商務諮詢(上海)有限公司主辦的2017年第七屆中國國際新能源汽車論壇即將於5月17日-5月19日在上海隆重舉行。新能源汽車系列論壇成功邀請了包括國家發改委能源研究所、世界電動車協會、亞太電動車協會、世界氫能協會、世界分散式能源聯盟、中國工程院等在內的政府單位與研究機構,以及包括寶馬、賓士、奇瑞捷豹路虎、大眾、奧迪、比亞迪、上汽、北汽等在內的知名整車商,共同研討新能源汽車行業政策趨勢、技術路線及難點、基礎設施建設、商業模式等並取得了豐碩的成果,獲得了業內外人士的一致好評。

在即將到來的2017年,組委會為感謝業內外人士對系列論壇長期以來的支援和關注,將傾情奉上相比歷屆舉辦規模最大的第六屆新能源汽車論壇,涉及七個論壇,頒獎典禮,研討會及晚宴。屆時將誠邀全球範圍內的整車製造商、電網電力公司、電池廠商、零部件供應商、核心技術提供商和政府官員近600位行業人士一起,對新能源汽車產業面臨的挑戰,機遇與對策各方面進行為期三天更深層次並具有建設和戰略性的探討

大會亮點

往屆參會企業分部

大會結構

若您對峰會有更多要求,請撥打021-6045 1760與我們聯繫,謝謝理解和支持!
我們期待與貴單位一起出席於5月17-19日在上海舉辦的第七屆中國國際新能源汽車論壇2017,以利決策!
想瞭解詳細內容,請登陸官方網站:http://www.ourpolaris.com/2017/nev/indexcn.html
連絡人:Hill Zeng(曾先生)
電話:021-6045 1760
傳真:021-6047 5887
郵箱: market@ourpolaris.com

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司“嚨底家”!

※推薦評價好的iphone維修中心

聚甘新

日本電動車技術競爭激烈,相關部件發展迅速

日本是發展較為穩健且持續的電動車市場之一,車商為搶奪發展先機,相關技術的競爭十分激烈。《日經》報導,有多家車商將落實純電動車(EV)的規格一致化,以備未來發展之需;而日立製作所也推出了續航力達400公里的鋰電池技術。

日產於2010年12月推出的Leaf是全球首輛量產型EV,最低價格約280萬日圓,是目前全球最暢銷的EV車款之一。《日經中文網》報導,日產汽車、法國雷諾、三菱汽車等三家有資本合作關係的車商將統一EV基本結構的底盤,並推動車用電池、馬達、逆變器等零件的通用。不過,各品牌車款的外觀與內飾仍將各別進行設計,以維持品牌特性。

雷諾、三菱將終止自己對於EV用底盤的研發,統一採用日產預計將在2018年推出的次世代Leaf底盤。底盤的統一,加上前述電池等零件通用化,雷諾和三菱汽車目標將EV的價格降至與豐田Corolla高端車款相當水準的價格,以強化市場競爭力,與美商特斯拉(Tesla)、中國比亞迪(BYD)等相庭抗禮。

採用鎳、矽,日立推高續航力鋰電池

電車搭載的鋰電池攸關續航力與安全性,向來是技術革新的兵家必爭之地。日產Leaf每次充飽電後最多可行駛約280公里,若可提高續航力到400公里,將對電動車普及大有幫助。

日立製作所改用鎳材料作為鋰電池的正極材料,並將電極的厚度增加為兩倍,始可儲存的鋰離子增加,且儲存功能也更好。負極則改用矽取代常見的碳材料,讓鋰離子更易於儲存和釋放,藉此使電容提高到原本的兩倍(約320Wh/kg),並延長EV的行駛距離40%。

日立製作所也在正極材料表面包覆氧化物,改善電解液因持續使用的化學反應而在電極表面形成覆膜,導致性能降低的問題。負極則在電解液中添加氟系添加物,抑制覆膜產生。日立製作所測試,改良後的鋰電池反覆充放電100次後,仍可維持90%以上的電容量。

這款新式鋰電池的成本不會較現行產品大幅提高。日立製作所將繼續確認安全性等問題,並預計在2020年投入量產。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司“嚨底家”!

※推薦評價好的iphone維修中心

聚甘新

武漢肺炎疫情蔓延 蝙蝠餐在印尼仍熱賣

摘錄自2020年2月11日中央社報導

2019新型冠狀病毒疫情蔓延,儘管研究顯示,起自中國的新型冠狀病毒源頭可能是蝙蝠,並經由其他中間宿主傳給人類。不過在印尼部分地區,蝙蝠餐仍暢銷熱賣。

整隻蝙蝠含頭翅全下鍋,佐咖哩烹煮的「燉蝙蝠」(Paniki),是印尼北蘇拉威西省(North Sulawesi)米納哈薩(Minahasa)當地民眾趨之若鶩的傳統菜餚。

小販鐵布倫(Stenly Timbuleng)在北蘇拉威西省首府萬鴉老(Manado)以南的托莫洪(Tomohon)擺攤賣燉蝙蝠。他說:「新型冠狀病毒的疫情不影響生意,而且……持續熱賣完售。」鐵布倫平均每天可賣50至60隻蝙蝠,逢節慶可熱賣高達600隻。

 

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

Java多線程與併發基礎面試題

CS-LogN思維導圖:記錄專業基礎 面試題
開源地址:https://github.com/FISHers6/CS-LogN

多線程與併發基礎

實現多線程

面試題1:有幾種實現線程的方法,分別是什麼

  • 1.繼承Thread類,啟動線程的唯一方法就是通過 Thread 類的 start()實例方法,start()方法是一個 native 方法,它將啟動一個新線程去執行 run()方法

  • 2.實現 Runnable 接口,重寫run()函數,作為參數放到Thread類構造函數中作為target屬性,運行start()方法

  • 線程池創建線程、Callable本質還是使Runnable創建,Callable是父輩類繼承了Runnable,線程池需傳入參數

面試題2:實現Runnable方法好,還是繼承Thread類好

  • 實現Runnable接口更好

    • 1.單一繼承原則,如果繼承了Thread,就不能繼承其它類了,限制了可擴展性
    • 2.Thread類每次只能創建一個獨立的線程,損耗大,而Runnable能利用線程池工具來創建線程
    • 3.從代碼架構上看,run內容應該與Trhead代碼解耦

面試題3:一個線程兩次調用start方法會出現什麼情況(考察源碼)

  • 第二次會出現異常,從start源碼上和線程生命周期上分析,一個線程start后,
    改變了threadState狀態字;而第二次再start每次會先檢查這個狀態不是0就報異常

面試題4:既然start方法會調用run方法,為什麼我們還是要用start方法,而不是直接調用run方法呢(考察源碼)

  • 因為start后線程才會經過完整的線程生命周期,start調用native start0,虛擬機執startThread,thread_entry入口中調用Thread的run,

面試題5:start和run有什麼區別

  • run()方法:只是普通的方法,調用run普通方法,可以重複多次調用
  • start()方法,會啟動一個線程,使得虛擬機去調用Runnable對象的run()方法,不能多次啟動同一個線程

面試題6:start方法如何調用run方法的(考察源碼和JVM)

  • start方法調用native start0,JVM虛擬機執行startThread,在thread_entry中調用Thread的run方法

面試題7:如何正確停止線程

  • 使用interrupt中斷通知,而不是強制,中斷通知後會讓被停止線程去決定何時停止,即把主動權交給需要被中斷的線程

線程的生命周期

面試題1:Java線程有哪幾種狀態 說說生命周期

  • 六種生命狀態(若time_waiting也算一種)

    • New,已創建但還尚未啟動的新線程
    • Runable,可運行狀態;對應操作系統的兩種狀態“就緒態” 和 “運行態”(分配到CPU)
    • Blocked,阻塞狀態;請求synchronized鎖未分配到時阻塞,直到獲取到monitor鎖再進入Runnable
    • Waiting,等待狀態
    • Timed waiting,限期等待
    • Terminated終止狀態
  • 線程的生命周期 狀態轉換圖

Thread和Object類中

與線程相關的重要方法

面試題1:實現兩個線程交替打印奇數偶數

面試題2:手寫生產者消費者設計模式,為什麼用該模式

  • 主要是為了解耦,匹配不同的能力

面試題3:wait后發生了什麼,為什麼需要在同步代碼內才能使用

  • 從jvm的源碼實現上看,wait后,線程讓出佔有的cpu並釋放同步資源鎖;把自己加入到等待池,以後不會再主動參与cpu的競爭,除非被其它notify命中
  • 為了確保線程安全;另外wait會釋放資源,所以肯定要先拿到這個鎖,能進入同步代碼塊已經拿到了鎖

面試題4:為什麼線程通信的方法wait,notify和notifyAll放在Object類,而sleep定義在Thread類里 (考察對象鎖)

  • 與對象的鎖有關,對象鎖綁定在對象的對象頭中,且放在Object里,使每個線程都可以持有多個對象的鎖

面試題5:wait方法是屬於Object對象的,那調用Thread.wait會怎麼樣

  • 線程死的時候會自己notifyAll,釋放掉所有的持有自己對象的鎖。這個機制是實現很多同步方法的基礎。如果調用Thrad.wait,干擾了我們設計的同步業務流程

面試題6:如何選擇notify還是notifyAll

  • 優先選用notifyAll,喚醒所有線程;除非業務需要每次只喚醒一個線程的

面試題7:notfiy后發生的操作,notifyAll之後所有的線程都會再次搶奪鎖,如果某線程搶奪失敗怎麼辦?

  • notify后,讓waiterSet等待池中的一個線程與entry_List鎖池一級活躍線程一起競爭CPU
  • 搶奪鎖失敗後會繼續待在原鎖池或原等待池,等待競爭CPU的調度

面試題8:sleep方法與notify/wait方法的異同點

  • 相同點:線程都會進入waiting狀態,都可以響應中斷
  • 不同點:1.所屬類不同;2.wait/notify必須用在同步方法中,且會釋放鎖;3.sleep可以指定時間

面試題9:join方法後父線程進入什麼狀態

  • waiting狀態,join內部調用wait,子線程結束后自動調用notifyAll喚醒(jvm:exit函數)

線程安全與性能

面試題1:守護線程和普通線程的區別

  • 守護線程是服務於普通線程的,並且不會影響到jvm的退出

面試題2:什麼是線程安全

  • 不管業務中遇到怎樣的多個線程訪問某對象或某方法的情況,而在編程這個業務邏輯的時候,都不需要再額外做任何額外的處理(也就是可以像單線程編程一樣),程序也可以正常運行(不會因為多線程而出錯),就可以稱為線程安全

面試題3:有哪些線程不安全的情況,什麼原因導致的

  • 1.數據爭用、同時操作,如數據讀寫由於同時寫,非原子性操作導致運行結果錯誤,a++
  • 2.存在競爭,順序不當,如死鎖、活鎖、飢餓

面試題4:什麼是多線程的上下文切換,及導致的後果

  • 進程線程切換要保存所需要的CPU運行環境,如寄存器、棧、全局變量等資源
  • 在頻繁的io以及搶鎖的時候,會導緻密集的上下文切換,多線程切換時,由於緩存和上下文的切換會帶來性能問題

面試題5:多線程導致的開銷有哪些

  • 1.上下文切換開銷,如保存緩存(cache、快表等)的開銷

  • 2.同步協作的開銷(java內存模型)

    • 為了數據的正確性,同步手段往往會使用禁止編譯器優化(如指令重排序優化、鎖粗化等),性能變差
    • 使CPU內的緩存失效(比如volatile可見性讓自己線程的緩存失效后,必須使用主存來查看數據)

Java內存模型

面試題1:Java的代碼如何一步步轉化,最終被CPU執行的

    1. 最開始,我們編寫的Java代碼,是*.java文件
  1. 在編譯(javac命令)后,從剛才的.java文件會變出一個新的Java字節碼文件.class
  2. JVM會執行剛才生成的字節碼文件(*.class),並把字節碼文件轉化為機器指令
  3. 機器指令可以直接在CPU上執運行,也就是最終的程序執行
  • JVM實現會帶來不同的“翻譯”,不同的CPU平台的機器指令又千差萬別,無法保證併發安全的效果一致

面試題2:單例模式的作用和適用場景

  • 單例模式:只獲取一次資源,全程序通用,節省內存和計算;保證多線程計算結果正確;方便管理;
    比如日期工具類只需要一個實例就可以,無需多個示例

面試題3:單例模式的寫法,考察(重排序、單例和高併發的關係)

  • 餓漢式(靜態常量、靜態代碼塊)

    • 原理1:static靜態常量在類加載的時候就初始化完成了,且由jvm保證線程安全,保證了變量唯一
    • 原理2:靜態代碼塊中實例化和靜態常量類似;放在靜態代碼塊里初始化,類加載時完成;
    • 特徵:簡單,但沒有懶加載(需要時再加載)
  • 懶漢式(加synchronized鎖)

    • 對初始化的方法加synchronized鎖達到線程安全的目的,但效率低,多線程下變成了同步
    • 懶漢式取名:用到的時候才去加載
  • 雙重檢查

    • 代碼實現

      • 屬性加volatile,兩次if判斷NULL值,第二次前加類鎖
    • 優點

      • 線程安全;延遲加載;效率高
    • 為什麼用雙重而不用單層

      • 從線程安全方面、效率方面講
  • 靜態內部類

    • 需要理解靜態內部類的優點,懶漢式加載,jvm加載順序
  • 枚舉

    • 代碼實現簡單

      • public enum Singleton{
        INSTANCE;
        public void method(){}
        }
    • 保證了線程安全

      • 枚舉是一個特殊的類,經過反編譯查看,枚舉最終被編譯成一個final的類,繼承了枚舉父類。各個實例通過static定義,本質就是一個靜態的對象,所有第一次使用的時候採取加載(懶加載)
    • 避免反序列化破壞單例

      • 避免了:比如用反射就繞過了構造方法,反序列化出多個實例

面試題4:單例模式各種寫法分別適用的場合

  • 1.最好的方法是枚舉,因枚舉被編譯成final類,用static定義靜態對象,懶加載。既保證了線程安全又避免了反序列化破壞單例
  • 2.如果程序一開始要加載的資源太多,考慮到啟動速度,就應該使用懶加載
  • 3.如果是對象的創建需要配置文件(一開始要加載其它資源),就不適合用餓漢式

面試題5:餓漢式單例的缺點

  • 沒有懶加載(初始化時全部加載出),初始化開銷大

面試題6:懶漢式單例的缺點

  • 雖然用到的時候才去加載,但是由於加鎖,性能低

面試題7:單例模式的雙重檢查寫法為什麼要用double-check

  • 從代碼實現出發,保證線程安全、延遲加載效率高

面試題8:為什麼雙重檢查要用volatile

  • 1.保證instance的可見性

    • 類初始化分成3條指令,重排序帶來NPE空虛指針問題,加volatile防止重排序
  • 2.防止初始化指令重排序

面試題9:講一講什麼是Java的內存模型

  • 1.是一組規範,需要JVM實現遵守這個規範,以便實現安全的多線程程序
    2.volatile、synchronized、Lock等同步工具和關鍵字實現原理都用到了JMM
    3.重排序、內存可見性、原子性

面試題10:什麼是happens-before,規則有哪些

  • 解決可見性問題的:在時間上,動作A發生在動作B之前,B保證能看見A,這就是happens-before

  • 規則

    • 1 單線程按代碼順序規則;2 鎖操作(synchronized和Lock);3volatile變量;4.JUC工具類的Happens-Before原則
    • 5.線程啟動時子線程啟動前能看到主線程run的所有內容;6.線程join主線程一定要等待子線程完成后再去做後面操作
    • 7.傳遞性 8.中斷檢測 9.對象構造方法的最後一行指令 happens-before 於 finalize() 方法的第一行指令

面試題11:講一講volatile關鍵字

  • volatile是一種同步機制,比synchronized或者Lock相關類更輕量,因為使用volatile並不會發生上下文切換等開銷很大的行為。而加鎖時對象鎖會阻塞開銷大。
  • 可見性,如果一個變量別修飾成volatile,那麼JVM就知道了這個變量可能會被併發修改;
  • 不能保證原子性

面試題12:volatile的適用場合及作用

  • 作用

    • 1.保證可見性 2.禁止指令重排序(單例雙重鎖時)
  • 適合場景

    • 適用場合1:boolean flag,布爾具有原子性,可再由volatile保證其可見性
    • 適用場合2:作為刷新之前變量的觸發器
    • 但不適合非原子性操作如:a++等

面試題13:volatile和synchronized的異同

  • 1 性能開銷方面: 鎖開銷更大,volatile無加鎖阻塞開銷
    2 作用方面:volatile只能保證可見性,鎖既能保證可見性,又能保證原子性

面試題14:什麼是內存可見性問題,為什麼存在

  • 多線程下,一個線程修改共享數據后,其它線程能否感知到修改了數據的線程的變化
  • CPU有多級緩存,導致讀的數據過期,各處理機有獨自的緩存未及時更新時,與主存內容不一致

面試題15:主內存和本地內存的關係是什麼

  • Java 作為高級語言,屏蔽了CPU cache等底層細節,用 JMM 定義了一套讀寫內存數據的規範,雖然我們不再需要關心一級緩存和二級緩存的問題,但是,JMM 抽象了主內存和本地內存的概念。
  • 線程擁有自己的本地內存,並共享主內存的數據;線程讀寫共享數據也是通過本地內存交換的,所以才導致了可見性問題。

面試題16:什麼是原子操作,Java的原子操作有哪些

  • 原子操作

    • 一系列的操作,要麼全部執行成功,要麼全部不執行,不會出現執行一半的情況,是不可分割的。
  • 1)除long和double之外的基本類型(int, byte, boolean, short, char, float)的”賦值操作”

  • 2)所有”引用reference的賦值操作”,不管是 32 位的機器還是 64 位的機器

  • 3)java.concurrent.Atomic.* 包中所有類的原子操作

面試題17:long 和 double 的原子性你了解嗎

  • 在32位上的JVM上,long 和 double的操作不是原子的,但是在64位的JVM上是原子的。
  • 在32位機器上一次只能讀寫32位;而浮點數、long型有8字節64位;要分高32位和低32位兩條指令分開寫入,類似彙編語言浮點數乘法分高低位寄存器;64位不用分兩次讀寫了

面試題18:生成對象的過程是不是原子操作

  • 不是,對象生成會生成分配空間、初始化、賦值,三條指令,有可能會被重排序,導致空指針

面試題19:區分JVM內存結構、Java內存模型 、Java對象模型

  • Java內存模型,和Java的併發編程有關

    • 詳見面試題9
  • JVM內存結構,和Java虛擬機的運行時區域(堆棧)有關

    • 堆區、方法區(存放常量池 引用 類信息)
      棧區、本地方法棧、程序計數器

  • Java對象模型,和Java對象在虛擬機中的表現形式有關

    • 是Java對象自身的存儲模型,在方法區中Kclass類信息(虛函數表),在堆中存放new實例,在線程棧中存放引用,OOP-Klass Model

面試題20:什麼是重排序

  • 指令實際執行順序和代碼在java文件中的順序不一致
  • 重排序的好處:提高處理速度,包括編譯器優化、指令重排序(局部性原理)

死鎖

面試題1:寫一個必然死鎖的例子

  • synchronized嵌套,構成請求循環

面試題2:生產中什麼場景下會發生死鎖

  • 併發中多線程互不相讓:當兩個(或更多)線程(或進程)相互持有對方所需要的資源,又不主動釋放,導致所有人都無法繼續前進,導致程序陷入無盡的阻塞,這就是死鎖。

面試題3:發生死鎖必須滿足哪些條件

  • 1.互斥
  • 2.請求和保持
  • 3.不可剝奪
  • 4.存儲循環等待鏈

面試題4:如何用工具定位死鎖

  • 1.jstack命令在程序發生死鎖后,進行堆棧分析出死鎖線程
  • 2.ThreadMXbean 程序運行中發現死鎖,一旦發現死鎖可以讓用戶去打日誌

面試題5:有哪些解決死鎖問題的策略

  • 1.死鎖語法,不讓死鎖發生

    • 破壞死鎖的四個條件之一;如:哲學家換手、轉賬換序
  • 2.死鎖避免

    • 銀行家算法、系統安全序列
  • 3.死鎖檢查與恢復

    • 適用資源請求分配圖,一段時間內檢查死鎖,有死鎖就恢復策略,採用恢復策略;
    • 恢復方法:進程終止 、資源剝奪
  • 4.鴕鳥策略(忽略死鎖)

    • 先忽略,後期再讓人工恢復

面試題6:死鎖避免策略和檢測與恢復策略的主要思路是什麼

  • 死鎖語法

    • 破壞死鎖的四大條件之一
  • 死鎖避免

    • 找到安全序列,銀行家算法
  • 死鎖檢測與恢復

    • 資源請求分配圖

面試題7:講一講經典的哲學家就餐問題,如何解決死鎖

  • 什麼時候死鎖

    • 哲學家各拿起自己左手邊的筷子,又去請求拿右手邊筷子循環請求時而阻塞
  • 如何解決死鎖

    • 1.一次兩隻筷子,形成原子性操作
    • 2.只允許4個人拿有筷子

面試題8:實際開發中如何避免死鎖

  • 設置超時時間
  • 多使用併發類而不是自己設計鎖
  • 盡量降低鎖的使用粒度:用不同的鎖而不是一個鎖,鎖的範圍越小越好
  • 避免鎖的嵌套:MustDeadLock類
  • 分配資源前先看能不能收回來:銀行家算法
  • 盡量不要幾個功能用同一把鎖:專鎖專用
  • 給你的線程起個有意義的名字:debug和排查時事半功倍,框架和JDK都遵守這個最佳實踐

面試題9:什麼是活躍性問題?活鎖、飢餓和死鎖有什麼區別

  • 活鎖

    • 雖然線程並沒有阻塞,也始終在運行(所以叫做“活”鎖,線程是“活”的),但是程序卻得不到進展,因為線程始終互相謙讓,重複做同樣的事

    • 工程中的活鎖實例:消息隊列,消息如果處理失敗,就放在隊列開頭重試,沒阻塞程序無法繼續

    • 如何解決活鎖問題

      • 加入隨機因素,以太網的指數退避算法
  • 飢餓

    • 當線程需要某些資源(例如CPU),但是卻始終得不到,可能原因是飢餓線程的優先級過低

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

如何提升自己?

如何提升自己?

看完後浪,感慨良多…

在程序員圈子,聽得最多的便是”35歲中年危機“。

危機

其實不僅僅存在“35歲危機”,還有“畢業危機”,“被裁員危機”,不僅僅在程序員圈子,幾乎所有圈子都是這樣,就像剛畢業的大學生說的:畢業等於失業。現在的社會飛速發展,我們常常感嘆大多數父母一代的人,智能手機玩着都比較費勁,其實也算是一種危機。其實不管任何職業,任何年齡的人,都應該保持“學習”的狀態,只有自身有了底氣,才能挺直了腰板,度過一個又一個危機。恩,做的不開心,我就換個工作…厲害的人,都是別人來請他去上班的。

作為一個Javaer,當然也需要不斷的保持學習,特別是對於剛畢業的人,可能在找第一份工作的時候,你大廠與你擦肩而過,但是只要你對未來有一個完整的規劃,3年後,你一樣能達到你的目標。

說了這麼多,只是為了強調學習的重要性。但是如何學習?學習什麼?這才是真正的問題。

如何學習?

很多人喜歡看視頻學習,記得剛學Java的時候,很多同學都會去看馬士兵,傳智博客等等。。。的確,視頻適合帶你入門,但是卻不適合進階。

如果你是一個什麼都不知道的小白, 不知道什麼是IDE,是什麼叫配置環境變量,那麼的確可以看看視頻學習,因為它能帶你很快的上手,避免走很多坑。

但是如果你是一個有一點項目經驗的人,那麼個人是不推薦通過視頻來學習新的知識的。第一個便是因為資源太少。除了培訓機構和各種專門為了做教育行業的人,很少有其他人會專門通過視頻介紹技術,即使有,視頻質量也難以得到保障。第二個便是效率問題,看視頻你不敢跳過,害怕錯過知識點,你也更加容易走神,因為進度掌握在別人手裡。

所以最好的學習方式便是看資料,比如看書,看官方文檔等。

如何看書?

書讀百遍,其義自見。能真正把一本書看很多遍的人,一定能體會到這句話的精髓所在。

擁有不同知識的人,看一本書的收貨一定是不一樣的。這裏可以簡單舉一個例子:一個剛學完Java基礎知識的人,看《Effective Java》的時候,可能只會死記硬背。一個擁有三年開發經驗的人,看《Effectice Java》的時候,會覺得恍然大悟。而一個擁有豐富的開發經驗的人,看《Effective Java》的時候,一定會激動的拍打着桌子:“對,當時這個坑就是這樣的”。

當你想要了解一個知識點的時候,比如JVM,你可以先去各個網站,找一找網友推薦的書,一般比較經典的技術,都會有一本比較經典的書。比如JVM中的《深入理解Java虛擬機》。記住,如果是想深入了解的話,一定要買好書,湊字數的書,只適合你看個大概。

挑選好一本書後,首先應該查看書的前言,然後看看目錄,了解整本書得框架以及知識點的分佈。最好帶着問題去看書。比如你看JVM,可能就是想了解大家常說的GC,JVM內存分佈,JVM調優等等,明白這些問題在書的第幾節,想想作者為什麼要把這個問題安排在這個地方?想要解答這些問題,需要明白哪些前提條件?

做完上面的步驟后,就可以開始看書了,看一個好書,我建議一遍泛讀,兩遍精讀,三遍薄讀。

第一遍,快速閱覽這本書,但是每個地方都要留一個印象,有問題不用怕,記在心裏。明白書的大體講了什麼,側重講了什麼,哪些是重點。更加重要的是,你在快速閱覽過程中,產生了什麼問題。

當看完第一遍后,我不太建議立即去看第二遍,看完第一遍,應該對整個技術有個大概的了解,這個時候你應該實際去上手去做,比如JVM打打日誌看看,jps.exe,jstat.exe等調試工具用一用看看,嘗試將書中的內容應用到實際中。這個時候,你會產生更多的問題。

第二遍,當經過一定的上手后,這個時候你就可以去看第二遍了,看第二遍的時候,心裏應該明白,你想解決什麼問題,你應該重點看哪裡。看的過程中,多想一想為什麼?想不明白的,一定上網查一查,問一問。這個過程中,其實更加推薦的是寫讀書筆記或者博客。嘗試將自己學到的東西講給別人聽,你會有意想不到的收穫。

當看完第二遍后,就可以暫時休息了,因為一本書,寫的再好,看兩遍都會有點乏味,看完這遍后,整理下知識點,簡單回顧下。

第三遍,第三遍應該在時間過去比較久之後再看,這一邊的速度可以很快,主要目的就是檢查你對這本書的內容的記憶程度理解的再好,都有可能會忘。每看到一部分內容,就去回想一下這部分內容的重點是什麼?有什麼值得注意的?為什麼是這樣。當你發現你都能說出來時,這本就,就已經薄成一張紙了、

看哪些書?

明白了怎麼看書之後,最後一個問題便是看哪些書了…

作為一個程序員,最重要的便是基礎。基礎不牢,地動山搖。技術的迭代是非常快的,前幾年大火的C#,如今在國內需求已經比較少了,再比如現在慢慢崛起的go,想要不被時代拋棄,必須學會快速掌握一個新的知識,而這些知識其實都萬變不離其中。那便是基礎。

掌握操作系統,能讓你快速明白JVM,多線程,NIO,零拷貝原理等。

掌握網絡基礎,能讓你快速明白HttpSocketHttps

當然,這裏所說的基礎,也包括一本語言的基礎,比如Java開發基礎等。

等有了這些基礎知識,再去學習整體開發的框架,會事半功倍。

明白了基礎的重要性,剩下的就是掌握一個高級開發工程師應該掌握的技能。

然而,什麼才是一個高級開發工程師應該掌握的技能?

很遺憾,我不能告訴你。因為不同方向,不同企業,不同部門,不同的業務。對一個人技能的要求,是不一樣的。

最好的方法便是定製一個目標,然後看看你離這個目標還有多遠。

怎麼去衡量你離這個目標還有多遠呢?最好的答案便是面試。面試犹如考試,少看哪些博眼球的文章標題為面試官問我…,製造焦慮,太偏的知識點可以簡單了解,但是別太浪費時間。不管你有沒有準備好,現在開始,準備一份你的簡歷,找一些差不多的崗位,然後接受面試官的鞭撻。總結每一次面試中,你發現你有空白的知識點,然後找一本書,看它。不用害怕簡歷沒什麼寫的,沒什麼寫的簡歷,更應該開始着手準備,機會總是給有準備的人。

堅持上面我說的,我相信,offer會比“危機”先到一步。

有感而發,隨便寫寫。

—— 胖毛2020/06/19

個人公眾號,隨便寫寫

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

Linux MySQL分庫分表之Mycat

介紹

背景

  • 表的個數達到了幾百千萬張表時,眾多的業務模塊都訪問這個數據庫,壓力會比較大,考慮對其進行分庫
  • 表的數據達到幾千萬級別,在做很多操作都比較吃力,考慮對其進行分庫或分表

數據切分(sharding)方案

  數據的切分(Sharding)根據其切分規則的類型,可以分為兩種切分模式:

  • 垂直切分:按照業務模塊進行切分,將不同模塊的表切分到不同的數據庫中
  • 水平切分,將一張大表按照一定的切分規則,按照行切分成不同的表或者切分到不同的庫中

如何理解垂直切分?

  垂直分庫:主要解決的問題是單個數據庫中[數據表]過多問題

  垂直分表:主要解決的問題是單個中[過多問題(將一張大表,拆分不同的關聯表)。

如何理解水平切分?

  水平切分主要解決的問題就是對於[單表數據量過大]的問題(1000W以上數據性能會有所下降)

切分原則

  1. 能不切盡量不要切分
  2. 如果要切分一定要選擇合適的切分規則,提前規劃好
  3. 數據切分盡量通過冗餘或表分組(Table Group)來降低跨庫Join的可能
  4. 由於數據庫中間件對數據Join實現的優劣難以把握,而且實現高性能難度極大,業務讀取盡量少使用多表Join

分庫分表之後帶來問題?

  1. 跨庫Join:訂單表需要關聯會員信息(訂單表和會員表拆分為兩個庫的表)
    1. 應用層由一個查詢拆分為多個
    2. 全局表,每個庫都存儲相同的數據,比如字典表、地址表
    3. 字段冗餘
    4. Mycat技術可以實現跨庫Join,只能實現2張表跨庫Join
  2. 分佈式事務(Mycat沒有很好實現分佈式事務)
    1. 強一致性(互聯網項目不推薦,性能不好)
    2. 最終一致性(異步方式去實現,需要通過日誌信息)
  3. 主鍵問題(保證ID的連續性和唯一性)
    1. UUID(性能不好)
    2. redis incr命令
    3. zookeeper
    4. 雪花算法
  4. 跨庫進行排序問題
    1. 在應用層進行排序

Mycat應用

官網鏈接

點我直達

Mycat核心概念

  • Schema:由它制定邏輯數據庫(相當於MySQL的database數據庫)
  • Table:邏輯表(相當於MySQL的table表)
  • DataNode:真正存儲數據的物理節點
  • DataHost:存儲節點所在的數據庫主機(指定MySQL數據庫的連接信息)
  • User:MyCat的用戶(類似於MySQL的用戶,支持多用戶)

MyCat主要解決的問題

  • 海量數據存儲
  • 查詢優化

Mycat對數據庫的支持

Mycat安裝

安裝要求

  • jdk:要求jdk必須是1.7及以上版本 (我使用的是jdk 1.8

  • Mysql:推薦mysql是5.5以上版本(我使用的是mysql 5.7

安裝jdk

具體教程:點我直達

Mcat下載

下載鏈接:點我直達

百度雲盤地址:https://pan.baidu.com/s/14A3BAwnBRGZppc3AicF5Hw  密碼: gkrp

解壓

修改配置文件

路徑:/cyb/soft/mycat/conf

server.xml

用途:用於配置用戶信息

<?xml version="1.0" encoding="UTF-8"?>
<!-- - - Licensed under the Apache License, Version 2.0 (the "License"); 
    - you may not use this file except in compliance with the License. - You 
    may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 
    - - Unless required by applicable law or agreed to in writing, software - 
    distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT 
    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the 
    License for the specific language governing permissions and - limitations 
    under the License. -->
<!DOCTYPE mycat:server SYSTEM "server.dtd">
<mycat:server xmlns:mycat="http://io.mycat/">
    <system>
    <property name="useSqlStat">0</property>  <!-- 1為開啟實時統計、0為關閉 -->
    <property name="useGlobleTableCheck">0</property>  <!-- 1為開啟全加班一致性檢測、0為關閉 -->

        <property name="sequnceHandlerType">2</property>
      <!--  <property name="useCompression">1</property>--> <!--1為開啟mysql壓縮協議-->
        <!--  <property name="fakeMySQLVersion">5.6.20</property>--> <!--設置模擬的MySQL版本號-->
    <!-- <property name="processorBufferChunk">40960</property> -->
    <!-- 
    <property name="processors">1</property> 
    <property name="processorExecutor">32</property> 
     -->
        <!--默認為type 0: DirectByteBufferPool | type 1 ByteBufferArena-->
        <property name="processorBufferPoolType">0</property>
        <!--默認是65535 64K 用於sql解析時最大文本長度 -->
        <!--<property name="maxStringLiteralLength">65535</property>-->
        <!--<property name="sequnceHandlerType">0</property>-->
        <!--<property name="backSocketNoDelay">1</property>-->
        <!--<property name="frontSocketNoDelay">1</property>-->
        <!--<property name="processorExecutor">16</property>-->
        <!--
            <property name="serverPort">8066</property> <property name="managerPort">9066</property> 
            <property name="idleTimeout">300000</property> <property name="bindIp">0.0.0.0</property> 
            <property name="frontWriteQueueSize">4096</property> <property name="processors">32</property> -->
        <!--分佈式事務開關,0為不過濾分佈式事務,1為過濾分佈式事務(如果分佈式事務內只涉及全局表,則不過濾),2為不過濾分佈式事務,但是記錄分佈式事務日誌-->
        <property name="handleDistributedTransactions">0</property>
        
            <!--
            off heap for merge/order/group/limit      1開啟   0關閉
        -->
        <property name="useOffHeapForMerge">1</property>

        <!--
            單位為m
        -->
        <property name="memoryPageSize">1m</property>

        <!--
            單位為k
        -->
        <property name="spillsFileBufferSize">1k</property>

        <property name="useStreamOutput">0</property>

        <!--
            單位為m
        -->
        <property name="systemReserveMemorySize">384m</property>


        <!--是否採用zookeeper協調切換  -->
        <property name="useZKSwitch">true</property>


    </system>
    
    <!-- 全局SQL防火牆設置 -->
    <!-- 
    <firewall> 
       <whitehost>
          <host host="127.0.0.1" user="mycat"/>
          <host host="127.0.0.2" user="mycat"/>
       </whitehost>
       <blacklist check="false">
       </blacklist>
    </firewall>
    -->
    
    <user name="root">
        <property name="password">root</property>
        <property name="schemas">TESTDB</property>
        
        <!-- 表級 DML 權限設置 -->
        <!--         
        <privileges check="false">
            <schema name="TESTDB" dml="0110" >
                <table name="tb01" dml="0000"></table>
                <table name="tb02" dml="1111"></table>
            </schema>
        </privileges>        
         -->
    </user>

    <user name="user">
        <property name="password">user</property>
        <property name="schemas">TESTDB</property>
        <property name="readOnly">true</property>
    </user>

</mycat:server>

schema.xml

用途:管理邏輯表

為了演示方便,刪掉一些不必要的標籤,標籤詳細用法:點我直達

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">

    <schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100">
        <!-- auto sharding by id (long) -->
        <table name="cyb_test" dataNode="dn1,dn2,dn3" rule="mod-long" />
    </schema>
    <dataNode name="dn1" dataHost="localhost1" database="db1" />
    <dataNode name="dn2" dataHost="localhost1" database="db2" />
    <dataNode name="dn3" dataHost="localhost1" database="db3" />
    <dataHost name="localhost1" maxCon="1000" minCon="10" balance="0"
              writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
        <heartbeat>select user()</heartbeat>
        <!-- can have multi write hosts -->
        <writeHost host="hostM1" url="192.168.31.200:3306" user="root"
                   password="root">
            <!-- can have multi read hosts -->
            <readHost host="hostS2" url="192.168.31.201:3306" user="root" password="root" />
        </writeHost>
    </dataHost>
</mycat:schema>

rule.xml

用途:定義了我們對錶進行拆分所涉及到的規則定義,視情況修改參數

<?xml version="1.0" encoding="UTF-8"?>
<!-- - - Licensed under the Apache License, Version 2.0 (the "License"); 
    - you may not use this file except in compliance with the License. - You 
    may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 
    - - Unless required by applicable law or agreed to in writing, software - 
    distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT 
    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the 
    License for the specific language governing permissions and - limitations 
    under the License. -->
<!DOCTYPE mycat:rule SYSTEM "rule.dtd">
<mycat:rule xmlns:mycat="http://io.mycat/">
    <tableRule name="rule1">
        <rule>
            <columns>id</columns>
            <algorithm>func1</algorithm>
        </rule>
    </tableRule>

    <tableRule name="rule2">
        <rule>
            <columns>user_id</columns>
            <algorithm>func1</algorithm>
        </rule>
    </tableRule>

    <tableRule name="sharding-by-intfile">
        <rule>
            <columns>sharding_id</columns>
            <algorithm>hash-int</algorithm>
        </rule>
    </tableRule>
    <tableRule name="auto-sharding-long">
        <rule>
            <columns>id</columns>
            <algorithm>rang-long</algorithm>
        </rule>
    </tableRule>
    <tableRule name="mod-long">
        <rule>
            <columns>id</columns>
            <algorithm>mod-long</algorithm>
        </rule>
    </tableRule>
    <tableRule name="sharding-by-murmur">
        <rule>
            <columns>id</columns>
            <algorithm>murmur</algorithm>
        </rule>
    </tableRule>
    <tableRule name="crc32slot">
        <rule>
            <columns>id</columns>
            <algorithm>crc32slot</algorithm>
        </rule>
    </tableRule>
    <tableRule name="sharding-by-month">
        <rule>
            <columns>create_time</columns>
            <algorithm>partbymonth</algorithm>
        </rule>
    </tableRule>
    <tableRule name="latest-month-calldate">
        <rule>
            <columns>calldate</columns>
            <algorithm>latestMonth</algorithm>
        </rule>
    </tableRule>
    
    <tableRule name="auto-sharding-rang-mod">
        <rule>
            <columns>id</columns>
            <algorithm>rang-mod</algorithm>
        </rule>
    </tableRule>
    
    <tableRule name="jch">
        <rule>
            <columns>id</columns>
            <algorithm>jump-consistent-hash</algorithm>
        </rule>
    </tableRule>

    <function name="murmur"
        class="io.mycat.route.function.PartitionByMurmurHash">
        <property name="seed">0</property><!-- 默認是0 -->
        <property name="count">2</property><!-- 要分片的數據庫節點數量,必須指定,否則沒法分片 -->
        <property name="virtualBucketTimes">160</property><!-- 一個實際的數據庫節點被映射為這麼多虛擬節點,默認是160倍,也就是虛擬節點數是物理節點數的160倍 -->
        <!-- <property name="weightMapFile">weightMapFile</property> 節點的權重,沒有指定權重的節點默認是1。以properties文件的格式填寫,以從0開始到count-1的整數值也就是節點索引為key,以節點權重值為值。所有權重值必須是正整數,否則以1代替 -->
        <!-- <property name="bucketMapPath">/etc/mycat/bucketMapPath</property> 
            用於測試時觀察各物理節點與虛擬節點的分佈情況,如果指定了這個屬性,會把虛擬節點的murmur hash值與物理節點的映射按行輸出到這個文件,沒有默認值,如果不指定,就不會輸出任何東西 -->
    </function>

    <function name="crc32slot"
              class="io.mycat.route.function.PartitionByCRC32PreSlot">
        <property name="count">2</property><!-- 要分片的數據庫節點數量,必須指定,否則沒法分片 -->
    </function>
    <function name="hash-int"
        class="io.mycat.route.function.PartitionByFileMap">
        <property name="mapFile">partition-hash-int.txt</property>
    </function>
    <function name="rang-long"
        class="io.mycat.route.function.AutoPartitionByLong">
        <property name="mapFile">autopartition-long.txt</property>
    </function>
    <function name="mod-long" class="io.mycat.route.function.PartitionByMod">
        <!-- how many data nodes -->
        <property name="count">3</property>
    </function>

    <function name="func1" class="io.mycat.route.function.PartitionByLong">
        <property name="partitionCount">8</property>
        <property name="partitionLength">128</property>
    </function>
    <function name="latestMonth"
        class="io.mycat.route.function.LatestMonthPartion">
        <property name="splitOneDay">24</property>
    </function>
    <function name="partbymonth"
        class="io.mycat.route.function.PartitionByMonth">
        <property name="dateFormat">yyyy-MM-dd</property>
        <property name="sBeginDate">2015-01-01</property>
    </function>
    
    <function name="rang-mod" class="io.mycat.route.function.PartitionByRangeMod">
            <property name="mapFile">partition-range-mod.txt</property>
    </function>
    
    <function name="jump-consistent-hash" class="io.mycat.route.function.PartitionByJumpConsistentHash">
        <property name="totalBuckets">3</property>
    </function>
</mycat:rule>

啟動mycat

進入mycat/bin,啟動mycat

啟動命令:./mycat start
停止命令:./mycat stop
重啟命令:./mycat restart
查看狀態命令:./mycat status

注意,可以使用mysql的客戶端直接連接mycat服務,默認端口為8066

錯誤日誌(重要)

  部署過程中,我碰到點小問題,找不到主機名,具體解決方案,請看我另一篇:點我直達 ,如果Mycat服務起不來,記得看錯誤日誌喲!

測試

ip:192.168.31.200(mysql主服務器)

ip:192.168.31.201(mysql從服務器)

ip:192.168.31.209(mycat服務器)

  注:演示過程中,因為mysql搭建了集群,主從複製,可能網絡原因,有些延遲,或者mysql主從複製同步機制問題,導致刷新好幾次,才显示出來,因為圖片較大,被分割幾張gif,內容都是連續的,驗證結果,達到預期,演示成功!

  MySQL集群搭建主從複製:點我直達

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心