【Spring註解驅動開發】組件註冊-@ComponentScan-自動掃描組件&指定掃描規則

寫在前面

在實際項目中,我們更多的是使用Spring的包掃描功能對項目中的包進行掃描,凡是在指定的包或子包中的類上標註了@Repository、@Service、@Controller、@Component註解的類都會被掃描到,並將這個類注入到Spring容器中。Spring包掃描功能可以使用XML文件進行配置,也可以直接使用@ComponentScan註解進行設置,使用@ComponentScan註解進行設置比使用XML文件配置要簡單的多。

項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

使用XML文件配置包掃描

我們可以在Spring的XML配置文件中配置包的掃描,在配置包掃描時,需要在Spring的XML文件中的beans節點中引入context標籤,如下所示。

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/context/spring-context.xsd ">

接下來,我們就可以在XML文件中定義要掃描的包了,如下所示。

<context:component-scan base-package="io.mykit.spring"/>

整個beans.xml文件如下所示。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context.xsd">

    <context:component-scan base-package="io.mykit.spring"/>

    <bean id = "person" class="io.mykit.spring.bean.Person">
        <property name="name" value="binghe"></property>
        <property name="age" value="18"></property>
    </bean>
</beans>

此時,只要在io.mykit.spring包下,或者io.mykit.spring的子包下標註了@Repository、@Service、@Controller、@Component註解的類都會被掃描到,並自動注入到Spring容器中。

此時,我們分別創建PersonDao、PersonService、和PersonController類,並在這三個類中分別添加@Repository、@Service、@Controller註解,如下所示。

  • PersonDao
package io.mykit.spring.plugins.register.dao;

import org.springframework.stereotype.Repository;

/**
 * @author binghe
 * @version 1.0.0
 * @description 測試的dao
 */
@Repository
public class PersonDao {
}
  • PersonService
package io.mykit.spring.plugins.register.service;

import org.springframework.stereotype.Service;

/**
 * @author binghe
 * @version 1.0.0
 * @description 測試的Service
 */
@Service
public class PersonService {
}
  • PersonController
package io.mykit.spring.plugins.register.controller;

import org.springframework.stereotype.Controller;

/**
 * @author binghe
 * @version 1.0.0
 * @description 測試的controller
 */
@Controller
public class PersonController {
}

接下來,我們在SpringBeanTest類中新建一個測試方法testComponentScanByXml()進行測試,如下所示。

@Test
public void testComponentScanByXml(){
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    String[] names = context.getBeanDefinitionNames();
    Arrays.stream(names).forEach(System.out::println);
}

運行測試用例,輸出的結果信息如下所示。

personConfig
personController
personDao
personService
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
person

可以看到,除了輸出我們自己創建的bean名稱之外,也輸出了Spring內部使用的一些重要的bean名稱。

接下來,我們使用註解來完成這些功能。

使用註解配置包掃描

使用@ComponentScan註解之前我們先將beans.xml文件中的下述配置註釋。

<context:component-scan base-package="io.mykit.spring"></context:component-scan>

註釋后如下所示。

<!--<context:component-scan base-package="io.mykit.spring"></context:component-scan>-->

使用@ComponentScan註解配置包掃描就非常Easy了!在我們的PersonConfig類上添加@ComponentScan註解,並將掃描的包指定為io.mykit.spring即可,整個的PersonConfig類如下所示。

package io.mykit.spring.plugins.register.config;

import io.mykit.spring.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * @author binghe
 * @version 1.0.0
 * @description 以註解的形式來配置Person
 */
@Configuration
@ComponentScan(value = "io.mykit.spring")
public class PersonConfig {

    @Bean("person")
    public Person person01(){
        return new Person("binghe001", 18);
    }
}

沒錯,就是這麼簡單,只需要在類上添加@ComponentScan(value = “io.mykit.spring”)註解即可。

接下來,我們在SpringBeanTest類中新增testComponentScanByAnnotation()方法,如下所示。

@Test
public void testComponentScanByAnnotation(){
    ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig.class);
    String[] names = context.getBeanDefinitionNames();
    Arrays.stream(names).forEach(System.out::println);
}

運行testComponentScanByAnnotation()方法輸出的結果信息如下所示。

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
personConfig
personController
personDao
personService
person

可以看到使用@ComponentScan註解同樣輸出了bean的名稱。

既然使用XML文件和註解的方式都能夠將相應的類注入到Spring容器當中,那我們是使用XML文件還是使用註解呢?我更傾向於使用註解,如果你確實喜歡使用XML文件進行配置,也可以,哈哈,個人喜好嘛!好了,我們繼續。

關於@ComponentScan註解

我們點開ComponentScan註解類,如下所示。

package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.type.filter.TypeFilter;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {

	@AliasFor("basePackages")
	String[] value() default {};

	@AliasFor("value")
	String[] basePackages() default {};

	Class<?>[] basePackageClasses() default {};

	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

	Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;

	ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

	String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;

	boolean useDefaultFilters() default true;

	Filter[] includeFilters() default {};

	Filter[] excludeFilters() default {};

	boolean lazyInit() default false;

	@Retention(RetentionPolicy.RUNTIME)
	@Target({})
	@interface Filter {
		FilterType type() default FilterType.ANNOTATION;
        
		@AliasFor("classes")
		Class<?>[] value() default {};
        
		@AliasFor("value")
		Class<?>[] classes() default {};
        
		String[] pattern() default {};
	}
}

這裏,我們着重來看ComponentScan類的兩個方法,如下所示。

Filter[] includeFilters() default {};
Filter[] excludeFilters() default {};

includeFilters()方法表示Spring掃描的時候,只包含哪些註解,而excludeFilters()方法表示不包含哪些註解。兩個方法的返回值都是Filter[]數組,在ComponentScan註解類的內部存在Filter註解類,大家可以看下上面的代碼。

1.掃描時排除註解標註的類

例如,我們現在排除@Controller、@Service和@Repository註解,我們可以在PersonConfig類上通過@ComponentScan註解的excludeFilters()實現。例如,我們在PersonConfig類上添加了如下的註解。

@ComponentScan(value = "io.mykit.spring", excludeFilters = {
        @Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class, Repository.class})
})

這樣,我們就使得Spring在掃描包的時候排除了使用@Controller、@Service和@Repository註解標註的類。運行SpringBeanTest類中的testComponentScanByAnnotation()方法,輸出的結果信息如下所示。

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
personConfig
person

可以看到,輸出的結果信息中不再輸出personController、personService和personDao說明Spring在進行包掃描時,忽略了@Controller、@Service和@Repository註解標註的類。

2.掃描時只包含註解標註的類

我們也可以使用ComponentScan註解類的includeFilters()來指定Spring在進行包掃描時,只包含哪些註解標註的類。

這裏需要注意的是,當我們使用includeFilters()來指定只包含哪些註解標註的類時,需要禁用默認的過濾規則。

例如,我們需要Spring在掃描時,只包含@Controller註解標註的類,可以在PersonConfig類上添加@ComponentScan註解,設置只包含@Controller註解標註的類,並禁用默認的過濾規則,如下所示。

@ComponentScan(value = "io.mykit.spring", includeFilters = {
        @Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
}, useDefaultFilters = false)

此時,我們再次運行SpringBeanTest類的testComponentScanByAnnotation()方法,輸出的結果信息如下所示。

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
personConfig
personController
person

可以看到,在輸出的結果中,只包含了@Controller註解標註的組件名稱,並沒有輸出@Service和@Repository註解標註的組件名稱。

注意:在使用includeFilters()來指定只包含哪些註解標註的類時,結果信息中會一同輸出Spring內部的組件名稱。

3.重複註解

不知道小夥伴們有沒有注意到ComponentScan註解類上有一個如下所示的註解。

@Repeatable(ComponentScans.class)

我們先來看看@ComponentScans註解是個啥,如下所示。

package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface ComponentScans {
	ComponentScan[] value();
}

可以看到,在ComponentScans註解類中只聲明了一個返回ComponentScan[]數組的value(),說到這裏,大家是不是就明白了,沒錯,這在Java8中是一個重複註解。

對於Java8不熟悉的小夥伴,可以到【Java8新特性】專欄查看關於Java8新特性的文章。專欄地址小夥伴們可以猛戳下面的鏈接地址進行查看:

https://mp.weixin.qq.com/mp/appmsgalbum?action=getalbum&__biz=Mzg3MzE1NTIzNA==&scene=1&album_id=1325066823947321344#wechat_redirect

在Java8中表示@ComponentScan註解是一個重複註解,可以在一個類上重複使用這個註解,如下所示。

@Configuration
@ComponentScan(value = "io.mykit.spring", includeFilters = {
        @Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
}, useDefaultFilters = false)
@ComponentScan(value = "io.mykit.spring", includeFilters = {
        @Filter(type = FilterType.ANNOTATION, classes = {Service.class})
}, useDefaultFilters = false)
public class PersonConfig {

    @Bean("person")
    public Person person01(){
        return new Person("binghe001", 18);
    }
}

運行SpringBeanTest類的testComponentScanByAnnotation()方法,輸出的結果信息如下所示。

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
personConfig
personController
personService
person

可以看到,同時輸出了@Controller註解和@Service註解標註的組件名稱。

如果使用的是Java8之前的版本,我們就不能直接在類上寫多個@ComponentScan註解了。此時,我們可以在PersonConfig類上使用@ComponentScans註解,如下所示。

@ComponentScans(value = {
        @ComponentScan(value = "io.mykit.spring", includeFilters = {
                @Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
        }, useDefaultFilters = false),
        @ComponentScan(value = "io.mykit.spring", includeFilters = {
                @Filter(type = FilterType.ANNOTATION, classes = {Service.class})
        }, useDefaultFilters = false)
})

再次運行SpringBeanTest類的testComponentScanByAnnotation()方法,輸出的結果信息如下所示。

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
personConfig
personController
personService
person

與使用多個@ComponentScan註解輸出的結果信息相同。

總結:我們可以使用@ComponentScan註解來指定Spring掃描哪些包,可以使用excludeFilters()指定掃描時排除哪些組件,也可以使用includeFilters()指定掃描時只包含哪些組件。當使用includeFilters()指定只包含哪些組件時,需要禁用默認的過濾規則

好了,咱們今天就聊到這兒吧!別忘了給個在看和轉發,讓更多的人看到,一起學習一起進步!!

項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

寫在最後

如果覺得文章對你有點幫助,請微信搜索並關注「 冰河技術 」微信公眾號,跟冰河學習Spring註解驅動開發。公眾號回復“spring註解”關鍵字,領取Spring註解驅動開發核心知識圖,讓Spring註解驅動開發不再迷茫。

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

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

天哪!手動編寫mybatis雛形竟然這麼簡單

前言

mybaits 在ORM 框架中,可算是半壁江山了,由於它是輕量級,半自動加載,靈活性和易拓展性。深受廣大公司的喜愛,所以我們程序開發也離不開mybatis 。但是我們有對mabtis 源碼進行研究嗎?或者想看但是不知道怎麼看的苦惱嗎?

歸根結底,我們還是需要知道為什麼會有mybatis ,mybatis 解決了什麼問題?
想要知道mybatis 解決了什麼問題,就要知道傳統的JDBC 操作存在哪些痛點才促使mybatis 的誕生。
我們帶着這些疑問,再來一步步學習吧。

原始JDBC 存在的問題

所以我們先來來看下原始JDBC 的操作:
我們知道最原始的數據庫操作。分為以下幾步:
1、獲取connection 連接
2、獲取preparedStatement
3、參數替代佔位符
4、獲取執行結果resultSet
5、解析封裝resultSet 到對象中返回。

如下是原始JDBC 的查詢代碼,存在哪些問題?

public static void main(String[] args) {
        String dirver="com.mysql.jdbc.Driver";
        String url="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8";
        String userName="root";
        String password="123456";

        Connection connection=null;
        List<User> userList=new ArrayList<>();
        try {
            Class.forName(dirver);
            connection= DriverManager.getConnection(url,userName,password);

            String sql="select * from user where username=?";
            PreparedStatement preparedStatement=connection.prepareStatement(sql);
            preparedStatement.setString(1,"張三");
            System.out.println(sql);
            ResultSet resultSet=preparedStatement.executeQuery();

            User user=null;
            while(resultSet.next()){
                user=new User();
                user.setId(resultSet.getInt("id"));
                user.setUsername(resultSet.getString("username"));
                user.setPassword(resultSet.getString("password"));
                userList.add(user);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (!userList.isEmpty()) {
            for (User user : userList) {
                System.out.println(user.toString());
            }
        }

    }

小夥伴們發現了上面有哪些不友好的地方?
我這裏總結了以下幾點:
1、數據庫的連接信息存在硬編碼,即是寫死在代碼中的。
2、每次操作都會建立和釋放connection 連接,操作資源的不必要的浪費。
3、sql 和參數存在硬編碼。
4、將返回結果集封裝成實體類麻煩,要創建不同的實體類,並通過set方法一個個的注入。

存在上面的問題,所以mybatis 就對上述問題進行了改進。
對於硬編碼,我們很容易就想到配置文件來解決。mybatis 也是這麼解決的。
對於資源浪費,我們想到是用連接池,mybatis 也是這個解決的。
對於封裝結果集麻煩,我們想到是用JDK的反射機制,好巧,mybatis 也是這麼解決的。

設計思路

既然如此,我們就來寫一個自定義吃持久層框架,來解決上述問題,當然是參照mybatis 的設計思路,這樣我們在寫完之後,再來看mybatis 的源碼就恍然大悟,這個地方這樣配置原來是因為這樣啊。
我們分為使用端和框架端兩部分。

使用端

我們在使用mybatis 的時候是不是需要使用SqlMapConfig.xml 配置文件,用來存放數據庫的連接信息,以及mapper.xml 的指向信息。mapper.xml 配置文件用來存放sql 信息。
所以我們在使用端來創建兩個文件SqlMapConfig.xml 和mapper.xml。

框架端

框架端要做哪些事情呢?如下:
1、獲取配置文件。也就是獲取到使用端的SqlMapConfig.xml 以及mapper.xml的 文件
2、解析配置文件。對獲取到的文件進行解析,獲取到連接信息,sql,參數,返回類型等等。這些信息都會保存在configuration 這個對象中。
3、創建SqlSessionFactory,目的是創建SqlSession的一個實例。
4、創建SqlSession ,用來完成上面原始JDBC 的那些操作。

那在SqlSession 中 進行了哪些操作呢?
1、獲取數據庫連接
2、獲取sql,並對sql 進行解析
3、通過內省,將參數注入到preparedStatement 中
4、執行sql
5、通過反射將結果集封裝成對象

使用端實現

好了,上面說了一下,大概的設計思路,主要也是仿照mybatis 主要的類實現的,保證類名一致,方便我們後面閱讀源碼。我們先來配置好使用端吧,我們創建一個maven 項目。
在項目中,我們創建一個User實體類

public class User {
    private Integer id;
    private String username;
    private String password;
    private String birthday;
    //getter()和setter()方法
}

創建SqlMapConfig.xml 和Mapper.xml
SqlMapConfig.xml

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&amp;characterEncoding=utf8&amp;useUnicode=true&amp;useSSL=false"></property>
    <property name="userName" value="root"></property>
    <property name="password" value="123456"></property>
    
    <mapper resource="UserMapper.xml">
    </mapper>
</configuration>

可以看到我們xml 中就配置了數據庫的連接信息,以及mapper 一個索引。mybatis中的SqlMapConfig.xml 中還包含其他的標籤,只是豐富了功能而已,所以我們只用最主要的。

mapper.xml
是每個類的sql 都會生成一個對應的mapper.xml 。我們這裏就用User 類來說吧,所以我們就創建一個UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="cn.quellanan.dao.UserDao">
    <select id="selectAll" resultType="cn.quellanan.pojo.User">
        select * from user
    </select>
    <select id="selectByName" resultType="cn.quellanan.pojo.User" paramType="cn.quellanan.pojo.User">
        select * from user where username=#{username}
    </select>
</mapper>

可以看到有點mybatis 裏面文件的味道,有namespace表示命名空間,id 唯一標識,resultType 返回結果集的類型,paramType 參數的類型。
我們使用端先創建到這,主要是兩個配置文件,我們接下來看看框架端是怎麼實現的。

加油哈哈。

框架端實現

框架端,我們按照上面的設計思路一步一步來。

獲取配置

怎麼樣獲取配置文件呢?我們可以使用JDK自帶自帶的類Resources加載器來獲取文件。我們創建一個自定義Resource類來封裝一下:

import java.io.InputStream;
public class Resources {
    public  static InputStream getResources(String path){
        //使用系統自帶的類Resources加載器來獲取文件。
        return Resources.class.getClassLoader().getResourceAsStream(path);
    }
}

這樣通過傳入路徑,就可以獲取到對應的文件流啦。

解析配置文件

上面獲取到了SqlMapConfig.xml 配置文件,我們現在來解析它。
不過在此之前,我們需要做一點準備工作,就是解析的內存放到什麼地方?
所以我們來創建兩個實體類Mapper 和Configuration。

Mapper
Mapper 實體類用來存放使用端寫的mapper.xml 文件的內容,我們前面說了裏面有.id、sql、resultType 和paramType .所以我們創建的Mapper實體如下:

public class Mapper {
    private String id;
    private Class<?> resultType;
    private Class<?> parmType;
    private String sql;
    //getter()和setter()方法
}

這裏我們為什麼不添加namespace 的值呢?
聰明的你肯定發現了,因為mapper裏面這些屬性表明每個sql 都對應一個mapper,而namespace 是一個命名空間,算是sql 的上一層,所以在mapper中暫時使用不到,就沒有添加了。

Configuration
Configuration 實體用來保存SqlMapConfig 中的信息。所以需要保存數據庫連接,我們這裏直接用JDK提供的 DataSource。還有一個就是mapper 的信息。每個mapper 有自己的標識,所以這裏採用hashMap來存儲。如下:

public class Configuration {

    private DataSource dataSource;
    HashMap <String,Mapper> mapperMap=new HashMap<>();
    //getter()和setter方法
    }

XmlMapperBuilder

做好了上面的準備工作,我們先來解析mapper 吧。我們創建一個XmlMapperBuilder 類來解析。通過dom4j 的工具類來解析XML 文件。我這裏用的dom4j 依賴為:

		<dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.3</version>
        </dependency>

思路:
1、獲取文件流,轉成document。
2、獲取根節點,也就是mapper。獲取根節點的namespace屬性值
3、獲取select 節點,獲取其id,sql,resultType,paramType
4、將select 節點的屬性封裝到Mapper 實體類中。
5、同理獲取update/insert/delete 節點的屬性值封裝到Mapper 中
6、通過namespace.id 生成key 值將mapper對象保存到Configuration實體中的HashMap 中。
7、返回 Configuration實體
代碼如下:


public class XmlMapperBuilder {
    private Configuration configuration;
    public XmlMapperBuilder(Configuration configuration){
        this.configuration=configuration;
    }

    public Configuration loadXmlMapper(InputStream in) throws DocumentException, ClassNotFoundException {
        Document document=new SAXReader().read(in);

        Element rootElement=document.getRootElement();
        String namespace=rootElement.attributeValue("namespace");

        List<Node> list=rootElement.selectNodes("//select");

        for (int i = 0; i < list.size(); i++) {
            Mapper mapper=new Mapper();
            Element element= (Element) list.get(i);
            String id=element.attributeValue("id");
            mapper.setId(id);
            String paramType = element.attributeValue("paramType");
            if(paramType!=null && !paramType.isEmpty()){
                mapper.setParmType(Class.forName(paramType));
            }
            String resultType = element.attributeValue("resultType");
            if (resultType != null && !resultType.isEmpty()) {
                mapper.setResultType(Class.forName(resultType));
            }
            mapper.setSql(element.getTextTrim());
            String key=namespace+"."+id;
            configuration.getMapperMap().put(key,mapper);
        }
        return configuration;
    }

}

上面我只解析了select 標籤。大家可以解析對應insert/delete/uupdate 標籤,操作都是一樣的。

XmlConfigBuilder

我們再來解析一下SqlMapConfig.xml 配置信息思路是一樣的,
1、獲取文件流,轉成document。
2、獲取根節點,也就是configuration。
3、獲取根節點中所有的property 節點,並獲取值,也就是獲取數據庫連接信息
4、創建一個dataSource 連接池
5、將連接池信息保存到Configuration實體中
6、獲取根節點的所有mapper 節點
7、調用XmlMapperBuilder 類解析對應mapper 並封裝到Configuration實體中
8、完
代碼如下:

public class XmlConfigBuilder {
    private Configuration configuration;
    public XmlConfigBuilder(Configuration configuration){
        this.configuration=configuration;
    }

    public Configuration loadXmlConfig(InputStream in) throws DocumentException, PropertyVetoException, ClassNotFoundException {

        Document document=new SAXReader().read(in);

        Element rootElement=document.getRootElement();

        //獲取連接信息
        List<Node> propertyList=rootElement.selectNodes("//property");
        Properties properties=new Properties();

        for (int i = 0; i < propertyList.size(); i++) {
            Element element = (Element) propertyList.get(i);
            properties.setProperty(element.attributeValue("name"),element.attributeValue("value"));
        }
		//是用連接池
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass(properties.getProperty("driverClass"));
        dataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
        dataSource.setUser(properties.getProperty("userName"));
        dataSource.setPassword(properties.getProperty("password"));
        configuration.setDataSource(dataSource);

        //獲取mapper 信息
        List<Node> mapperList=rootElement.selectNodes("//mapper");
        for (int i = 0; i < mapperList.size(); i++) {
            Element element= (Element) mapperList.get(i);
            String mapperPath=element.attributeValue("resource");
            XmlMapperBuilder xmlMapperBuilder = new XmlMapperBuilder(configuration);
            configuration=xmlMapperBuilder.loadXmlMapper(Resources.getResources(mapperPath));
        }
        return configuration;
    }
}

創建SqlSessionFactory

完成解析后我們創建SqlSessionFactory 用來創建Sqlseesion 的實體,這裏為了盡量還原mybatis 設計思路,也也採用的工廠設計模式。
SqlSessionFactory 是一個接口,裏面就一個用來創建SqlSessionf的方法。
如下:

public interface SqlSessionFactory {
    public SqlSession openSqlSession();
}

單單這個接口是不夠的,我們還得寫一個接口的實現類,所以我們創建一個DefaultSqlSessionFactory。
如下:

public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }
    public SqlSession openSqlSession() {
        return new DefaultSqlSeeion(configuration);
    }
}

可以看到就是創建一個DefaultSqlSeeion並將包含配置信息的configuration 傳遞下去。DefaultSqlSeeion 就是SqlSession 的一個實現類。

創建SqlSession

在SqlSession 中我們就要來處理各種操作了,比如selectList,selectOne,insert.update,delete 等等。
我們這裏SqlSession 就先寫一個selectList 方法。
如下:

public interface SqlSession {

    /**
     * 條件查找
     * @param statementid  唯一標識,namespace.selectid
     * @param parm  傳參,可以不傳也可以一個,也可以多個
     * @param <E>
     * @return
     */
    public <E> List<E> selectList(String statementid,Object...parm) throws Exception;

然後我們創建DefaultSqlSeeion 來實現SqlSeesion 。

public class DefaultSqlSeeion implements SqlSession {
    private Configuration configuration;
	private Executer executer=new SimpleExecuter();
	
    public DefaultSqlSeeion(Configuration configuration) {
        this.configuration = configuration;
    }

	@Override
    public <E> List<E> selectList(String statementid, Object... parm) throws Exception {
        Mapper mapper=configuration.getMapperMap().get(statementid);
        List<E> query = executer.query(configuration, mapper, parm);
        return query;
    }

}

我們可以看到DefaultSqlSeeion 獲取到了configuration,並通過statementid 從configuration 中獲取mapper。 然後具體實現交給了Executer 類來實現。我們這裏先不管Executer 是怎麼實現的,就假裝已經實現了。那麼整個框架端就完成了。通過調用Sqlsession.selectList() 方法,來獲取結果。

感覺我們都還沒有處理,就框架搭建好了?騙鬼呢,確實前面我們從獲取文件解析文件,然後創建工廠。都是做好準備工作。下面開始我們JDBC的實現。

SqlSession 具體實現

我們前面說SqlSeesion 的具體實現有下面5步
1、獲取數據庫連接
2、獲取sql,並對sql 進行解析
3、通過內省,將參數注入到preparedStatement 中
4、執行sql
5、通過反射將結果集封裝成對象

但是我們在DefaultSqlSeeion 中將實現交給了Executer來執行。所以我們就要在Executer中來實現這些操作。

我們首先來創建一個Executer 接口,並寫一個DefaultSqlSeeion中調用的query 方法。

public interface Executer {

    <E> List<E> query(Configuration configuration,Mapper mapper,Object...parm) throws Exception;

}

接着我們寫一個SimpleExecuter 類來實現Executer 。
然後SimpleExecuter.query()方法中,我們一步一步的實現。

獲取數據庫連接

因為數據庫連接信息保存在configuration,所以直接獲取就好了。

//獲取連接
        connection=configuration.getDataSource().getConnection();

獲取sql,並對sql 進行解析

我們這裏想一下,我們在Usermapper.xml寫的sql 是什麼樣子?

select * from user where username=#{username}

{username} 這樣的sql 我們改怎麼解析呢?

分兩步
1、將sql 找到#{***},並將這部分替換成 ?號

2、對 #{***} 進行解析獲取到裏面的參數對應的paramType 中的值。

具體實現用到下面幾個類。
GenericTokenParser類,可以看到有三個參數,開始標記,就是我們的“#{” ,結束標記就是 “}”, 標記處理器就是處理標記裏面的內容也就是username。

public class GenericTokenParser {

  private final String openToken; //開始標記
  private final String closeToken; //結束標記
  private final TokenHandler handler; //標記處理器

  public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
    this.openToken = openToken;
    this.closeToken = closeToken;
    this.handler = handler;
  }

  /**
   * 解析${}和#{}
   * @param text
   * @return
   * 該方法主要實現了配置文件、腳本等片段中佔位符的解析、處理工作,並返回最終需要的數據。
   * 其中,解析工作由該方法完成,處理工作是由處理器handler的handleToken()方法來實現
   */
  public String parse(String text) {
 	 //具體實現
 	 }
  }

主要的就是parse() 方法,用來獲取操作1 的sql。獲取結果例如:

select * from user where username=?

那上面用到TokenHandler 來處理參數。
ParameterMappingTokenHandler實現TokenHandler的類


public class ParameterMappingTokenHandler implements TokenHandler {
	private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();

	// context是參數名稱 #{id} #{username}

	@Override
	public String handleToken(String content) {
		parameterMappings.add(buildParameterMapping(content));
		return "?";
	}

	private ParameterMapping buildParameterMapping(String content) {
		ParameterMapping parameterMapping = new ParameterMapping(content);
		return parameterMapping;
	}

	public List<ParameterMapping> getParameterMappings() {
		return parameterMappings;
	}

	public void setParameterMappings(List<ParameterMapping> parameterMappings) {
		this.parameterMappings = parameterMappings;
	}

}

可以看到將參數名稱存放 ParameterMapping 的集合中了。
ParameterMapping 類就是一個實體,用來保存參數名稱的。

public class ParameterMapping {

    private String content;

    public ParameterMapping(String content) {
        this.content = content;
    }
	//getter()和setter() 方法。
}

所以我們在我們通過GenericTokenParser類,就可以獲取到解析后的sql,以及參數名稱。我們將這些信息封裝到BoundSql實體類中。

public class BoundSql {

    private String sqlText;
    private List<ParameterMapping> parameterMappingList=new ArrayList<>();
    public BoundSql(String sqlText, List<ParameterMapping> parameterMappingList) {
        this.sqlText = sqlText;
        this.parameterMappingList = parameterMappingList;
    }
    ////getter()和setter() 方法。
  }

好了,那麼分兩步走,先獲取,后解析
獲取
獲取原始sql 很簡單,sql 信息就存在mapper 對象中,直接獲取就好了。

String sql=mapper.getSql()

解析
1、創建一個ParameterMappingTokenHandler 處理器
2、創建一個GenericTokenParser 類,並初始化開始標記,結束標記,處理器
3、執行genericTokenParser.parse(sql);獲取解析后的sql‘’,以及在parameterMappingTokenHandler 中存放了參數名稱的集合。
4、將解析后的sql 和參數封裝到BoundSql 實體類中。

/**
     * 解析自定義佔位符
     * @param sql
     * @return
     */
    private BoundSql getBoundSql(String sql){
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{","}",parameterMappingTokenHandler);
        String parse = genericTokenParser.parse(sql);
        return new BoundSql(parse,parameterMappingTokenHandler.getParameterMappings());

    }

將參數注入到preparedStatement 中

上面的就完成了sql,的解析,但是我們知道上面得到的sql 還是包含 JDBC的 佔位符,所以我們需要將參數注入到preparedStatement 中。
1、通過boundSql.getSqlText()獲取帶有佔位符的sql.
2、接收參數名稱集合 parameterMappingList
3、通過mapper.getParmType() 獲取到參數的類。
4、通過getDeclaredField(content)方法獲取到參數類的Field。
5、通過Field.get() 從參數類中獲取對應的值
6、注入到preparedStatement 中

		BoundSql boundSql=getBoundSql(mapper.getSql());
        String sql=boundSql.getSqlText();
        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();

        //獲取preparedStatement,並傳遞參數值
        PreparedStatement preparedStatement=connection.prepareStatement(sql);
        Class<?> parmType = mapper.getParmType();

        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent();
            Field declaredField = parmType.getDeclaredField(content);
            declaredField.setAccessible(true);
            Object o = declaredField.get(parm[0]);
            preparedStatement.setObject(i+1,o);
        }
        System.out.println(sql);
        return preparedStatement;

執行sql

其實還是調用JDBC 的executeQuery()方法或者execute()方法

//執行sql
 ResultSet resultSet = preparedStatement.executeQuery();

通過反射將結果集封裝成對象

在獲取到resultSet 后,我們進行封裝處理,和參數處理是類似的。
1、創建一個ArrayList
2、獲取返回類型的類
3、循環從resultSet中取數據
4、獲取屬性名和屬性值
5、創建屬性生成器
6、為屬性生成寫方法,並將屬性值寫入到屬性中
7、將這條記錄添加到list 中
8、返回list

/**
     * 封裝結果集
     * @param mapper
     * @param resultSet
     * @param <E>
     * @return
     * @throws Exception
     */
    private <E> List<E> resultHandle(Mapper mapper,ResultSet resultSet) throws Exception{
        ArrayList<E> list=new ArrayList<>();
        //封裝結果集
        Class<?> resultType = mapper.getResultType();
        while (resultSet.next()) {
            ResultSetMetaData metaData = resultSet.getMetaData();
            Object o = resultType.newInstance();
            int columnCount = metaData.getColumnCount();
            for (int i = 1; i <= columnCount; i++) {
                //屬性名
                String columnName = metaData.getColumnName(i);
                //屬性值
                Object value = resultSet.getObject(columnName);
                //創建屬性描述器,為屬性生成讀寫方法
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName,resultType);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(o,value);
            }
            list.add((E) o);
        }
        return list;
    }

創建SqlSessionFactoryBuilder

我們現在來創建一個SqlSessionFactoryBuilder 類,來為使用端提供一個人口。

public class SqlSessionFactoryBuilder {

    private Configuration configuration;

    public SqlSessionFactoryBuilder(){
        configuration=new Configuration();
    }

    public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException, ClassNotFoundException {
        XmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder(configuration);
        configuration=xmlConfigBuilder.loadXmlConfig(in);

        SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
        return sqlSessionFactory;
    }
}

可以看到就一個build 方法,通過SqlMapConfig的文件流將信息解析到configuration,創建並返回一個sqlSessionFactory 。

到此,整個框架端已經搭建完成了,但是我們可以看到,只實現了select 的操作,update、inster、delete 的操作我們在我後面提供的源碼中會有實現,這裏只是將整體的設計思路和流程。

測試

終於到了測試的環節啦。我們前面寫了自定義的持久層,我們現在來測試一下能不能正常的使用吧。
見證奇迹的時刻到啦

我們先引入我們自定義的框架依賴。以及數據庫和單元測試

<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>
        <dependency>
            <groupId>cn.quellanan</groupId>
            <artifactId>myself-mybatis</artifactId>
            <version>1.0.0</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
        </dependency>

然後我們寫一個測試類
1、獲取SqlMapperConfig.xml的文件流
2、獲取Sqlsession
3、執行查找操作

@org.junit.Test
    public void test() throws Exception{
        InputStream inputStream= Resources.getResources("SqlMapperConfig.xml");
        SqlSession sqlSession = new SqlSessionFactoryBuilder().build(inputStream).openSqlSession();
        List<User> list = sqlSession.selectList("cn.quellanan.dao.UserDao.selectAll");

        for (User parm : list) {
            System.out.println(parm.toString());
        }
        System.out.println();

        User user=new User();
        user.setUsername("張三");
        List<User> list1 = sqlSession.selectList("cn.quellanan.dao.UserDao.selectByName", user);
        for (User user1 : list1) {
            System.out.println(user1);
        }

    }

可以看到已經可以了,看來我們自定義的持久層框架生效啦。

優化

但是不要高興的太早哈哈,我們看上面的測試方法,是不是感覺和平時用的不一樣,每次都都寫死statementId ,這樣不太友好,所以我們接下來來點騷操作,通用mapper 配置。
我們在SqlSession中增加一個getMapper方法,接收的參數是一個類。我們通過這個類就可以知道statementId .

/**
     * 使用代理模式來創建接口的代理對象
     * @param mapperClass
     * @param <T>
     * @return
     */
    public <T> T getMapper(Class<T> mapperClass);

具體實現就是利用JDK 的動態代理機制。
1、通過Proxy.newProxyInstance() 獲取一個代理對象
2、返回代理對象
那代理對象執行了哪些操作呢?
創建代理對象的時候,會實現一個InvocationHandler接口,重寫invoke() 方法,讓所有走這個代理的方法都會執行這個invoke() 方法。那這個方法做了什麼操作?
這個方法就是通過傳入的類對象,獲取到對象的類名和方法名。用來生成statementid 。所以我們在mapper.xml 配置文件中的namespace 就需要制定為類路徑,以及id 為方法名。
實現方法:

@Override
    public <T> T getMapper(Class<T> mapperClass) {

        Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSeeion.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                //獲取到方法名
                String name = method.getName();
                //類型
                String className = method.getDeclaringClass().getName();
                String statementid=className+"."+name;

                return selectList(statementid,args);
            }
        });


        return (T) proxyInstance;
    }

我們寫一個UserDao

public interface UserDao {
    List<User> selectAll();

    List<User> selectByName(User user);
}

這個是不是我們熟悉的味道哈哈,就是mapper層的接口。
然後我們在mapper.xml 中指定namespace 和id

接下來我們在寫一個測試方法

@org.junit.Test
    public void test2() throws Exception{
        InputStream inputStream= Resources.getResources("SqlMapperConfig.xml");
        SqlSession sqlSession = new SqlSessionFactoryBuilder().build(inputStream).openSqlSession();

        UserDao mapper = sqlSession.getMapper(UserDao.class);
        List<User> users = mapper.selectAll();
        for (User user1 : users) {
            System.out.println(user1);
        }

        User user=new User();
        user.setUsername("張三");
        List<User> users1 = mapper.selectByName(user);
        for (User user1 : users1) {
            System.out.println(user1);
        }

    }

番外

自定義的持久層框架,我們就寫完了。這個實際上就是mybatis 的雛形,我們通過自己手動寫一個持久層框架,然後在來看mybatis 的源碼,就會清晰很多。下面這些類名在mybatis 中都有體現。

這裏拋磚引玉,祝君閱讀源碼愉快。
覺得有用的兄弟們記得收藏啊。

厚顏無恥的求波點贊!!!

本文由博客一文多發平台 OpenWrite 發布!

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

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

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

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

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準

台中搬家公司費用怎麼算?

既能穿西裝又能幹苦活的全能选手,教授試駕全新菱智M5

賬面數據來看,它不會讓你有很高昂的駕駛激情,但實際駕駛過程中,也是足夠使用的。菱智M5的離合器匹配很成熟,離合踏板幅度較大,需要稍微適應一下,等離合器到位后順勢給油就能完美起步,熟練的司機可以做到毫無頓挫感。

自2007年第一代菱智面世以來,這款MpV已經在市場上走了9年之久。親民的定價、務實的用途,使得菱智在三四線城市中備受歡迎,而在大城市的貨運車中,也常能看見它的身影。但說實在的,把菱智系列當商務用車、甚至家用車的車主屈指可少,很大原因是因為它的產品價值被死死定在了貨運上,“撐不住面子”的它,很難上檯面。

在新款菱智M5上,我們能看到它悄悄打上了“新商務”的標籤,不僅可以貨物運輸,還能兼顧商務接待,這不禁讓對這次試駕充滿了期待,畢竟之前試駕都以乘用車為主,MpV還真的不多。

外觀設計、空間表現如何?

新款菱智M5的外觀設計比起舊款有了不少的提升,鷹眼似的前大燈看上去銳利無比,四條格柵呈“展翼”狀,採用鍍鉻處理。外觀的升級是值得肯定的,起碼整體的氣質變化很明顯。

菱智M5車身尺寸為:4745*1720*1940,軸距2800。內部空間是十分夠用的,特別是第三排座椅,1米83的身高坐進去一點壓迫感都沒有,別說更寬敞的第二排了。不過有利有弊,如果第三排座椅立起的情況下,後備箱的空間就顯得不足了。

不過如果放下後排座椅,這輛菱智M5也會很好地發揮它的貨運本能,可輕鬆放進一台電腦桌。

開起來是什麼感覺?

試駕的這款菱智M5,搭載的是三菱4A92發動機以及5MT變速箱,最大轉速6000轉,最大馬力122,最高扭矩153牛米。賬面數據來看,它不會讓你有很高昂的駕駛激情,但實際駕駛過程中,也是足夠使用的。

菱智M5的離合器匹配很成熟,離合踏板幅度較大,需要稍微適應一下,等離合器到位后順勢給油就能完美起步,熟練的司機可以做到毫無頓挫感。

試駕的時候車上載着5個成年人,並開啟着空調。油門響應比較平緩,需要稍微深踩,降檔地板油時,轉速也不會提高的很积極,所以超車能力一般,但滿載情況下,也滿足市區需求了。不過如果做商務接待的話,平穩的駕駛才是最重要的,即便是貨運的話,這個動力也完全足夠了。

菱智M5的底盤調教功底還是有的,採用雙橫臂扭桿彈簧獨立前懸架和鋼板彈簧后懸架。這樣的懸挂設計,側重承載能力,為商用物流運輸提供了可靠的保障,而且耐用性更能從容應對山路、爛路等複雜路況。

順帶說一句,新款菱智M5的隔音做得不錯,怠速抖動在接受範圍內,基本上只有細微的風噪和胎噪,做工用料值得肯定。

最後總結:

菱智系列一直以來的銷量都很穩定,今年以來一直在1萬浮動上下,這與它空間大、外觀好看、內飾美觀、底盤耐用…等等優點是分不開的。更何況菱智M5的定價僅為7.19萬~8.49萬,所以也不必用更苛刻的眼光去看待了。務實耐用,貨運和商務都兼顧,這款車已經做得足夠好了。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

※別再煩惱如何寫文案,掌握八大原則!

冷空氣來臨 開上這些車秋褲都不用穿就能度過寒冬

28-15。88萬長安CS75可謂算得上自主SUV老牌勁旅中的一員了,整體外觀很硬派卻又不失時尚,天使眼造型的大燈犀利又帥氣。內飾整體是家族式的設計,1。5T車型全系標配了自動啟停功能,還有自動駐車、無鑰匙進入/啟動、倒車影像、感應雨刷等配置一應俱全。

又到了冷空氣入侵的時候了,你媽媽有沒有喊你穿秋褲呢,雖然南方的小夥伴還可以沐浴在溫暖的陽光中,但是北方的小夥伴們已經在激烈的雪球大戰中了,凍成狗的小學生都裹着棉被去上學了。

擠公交地鐵上班的就稍微沒那麼難受了,起碼不會再穩到那股難聞的汗酸味,最舒服的要數開車上班的孩子了,風吹日晒雨淋完全沒關係,不過一些沒有座椅加熱的真皮座椅還是給人冰涼涼的感覺。

廣汽乘用車-傳祺GA6

指導價:10.28-19.68萬

說起自主品牌中型車,傳祺GA6絕對是一款代表作,車身造型有很強的設計感,凌雲翼家族式設計元素,圓潤飽滿的大燈有着很高的辨識度;內飾的設計布局和做工有着堪比合資車的水準,配置上無鑰匙進入/啟動、自動駐車、上坡輔助、方向盤換擋等配置非常豐富。

4850×1830×1485mm的車身尺寸,軸距2720mm,乘坐空間表現良好,儲物空間設計很人性化,隨手可及;有着阿爾法羅密歐技術調校的底盤是一大亮點,行駛品質在自主品牌車型中屬於一流水平,1.5T(最大功率152馬力)+7擋雙離合的動力組合兼顧了動力和燃油經濟性,是比較實惠的一款車型。

一汽奔騰-奔騰B70

指導價:9.98-14.98萬

奔騰B70改款後設計風格上有了明顯的轉變,時尚動感的外觀視覺效果上還是相當吸引人的,熏黑的尾燈、黑色車頂、后擾流板彰顯了其運動氣息;搪塑工藝處理的中控台質感出色,中控屏波浪開口設計很個性化,黑色內飾搭配紅色縫線的設計突出了運動氛圍。

長*寬*高為4800*1820*1472mm,軸距達到了2725mm,最具亮點的就是它使用了與馬自達6相同的動力系統和底盤,技術成熟,開起來有合資車的感覺,1.8T(最大功率186馬力)+6擋手自一體變速器的組合動力響應很积極,轉向精準,車身緊湊底盤硬朗,懸挂支撐到位,操控感覺非常棒。

長安汽車-長安CS75

指導價:9.28-15.88萬

長安CS75可謂算得上自主SUV老牌勁旅中的一員了,整體外觀很硬派卻又不失時尚,天使眼造型的大燈犀利又帥氣;內飾整體是家族式的設計,1.5T車型全系標配了自動啟停功能,還有自動駐車、無鑰匙進入/啟動、倒車影像、感應雨刷等配置一應俱全。

新推出的1.5T(最大功率170馬力)車型動力表現已經超越2.0L(最大功率158馬力)發動機的表現,低扭表現相當出色,輕鬆帶動1.6噸的車身,動力輸出的線性程度堪比自然吸氣發動機,齒比設定相對密集的6擋手動變速器使得各個擋位的動力銜接更加順暢,唯一比較遺憾的就是1.5T車型暫時沒配備自動變速器了。

長城汽車-哈弗H2

指導價:8.68-12.88萬

紅藍雙標的戰略在哈弗H2看來是非常的成功的,藍標外觀更加年輕時尚化,紅標側重豪華家用,適合不同人群的需求;內飾無論是做工還是用料都給人很豪華的感覺,配置上也是越級的享受,自動頭燈、車內氛圍燈、倒車影像、定速巡航、陡坡緩降等配置在一款11萬的車型都能擁有。

其車身尺寸為4335*1814*1695mm,顯得比較緊湊,2560mm的軸距帶來的空間感受並不會感到擁擠,大量皮革包裹的門邊觸感良好;動力上依舊是我們熟悉的1.5T(最大功率150馬力)發動機,搭配6AT/6MT,起步輕鬆,渦輪介入後勁足,整體加速較為線性。

總結:自主車型現在無論是外觀還是內飾配置,相比於合資車都有很大的優勢,三大件方面質量也在逐步提高,尤其是哈弗品牌車型,個個都內外兼修,自主品牌已經有了非常大的提升了,值得入手。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

加速7.5s+8.1L油耗,用上了1.5T發動機的本田CR-V究竟有多強?

而這次加入1。5T發動機,就讓CR-V在現今的緊湊型SUV中立於不敗地位,燃油經濟性以及動力都可以達到一個相當優秀的地步,外觀上的改變也是能吸引更多的年輕用戶。不過競爭對手中的馬自達CX-4以及CX-5在燃油經濟性以及性能並不輸給CRV-V,以及奇駿在空間上的優點,CR-V要想突破這番困局還是需要視乎具體售價以及具體價格,若是能維持現今2。

編者總結:

毫無疑問,2.0L發動機被取代是毫無疑問的,畢竟現今服役的2.0L發動機有着不短的“年頭”。而這次加入1.5T發動機,就讓CR-V在現今的緊湊型SUV中立於不敗地位,燃油經濟性以及動力都可以達到一個相當優秀的地步,外觀上的改變也是能吸引更多的年輕用戶。不過競爭對手中的馬自達CX-4以及CX-5在燃油經濟性以及性能並不輸給CRV-V,以及奇駿在空間上的優點,CR-V要想突破這番困局還是需要視乎具體售價以及具體價格,若是能維持現今2.0L的價格,相信能給對手造成不少的衝擊。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

※回頭車貨運收費標準

拉風泡妞神器 20多萬起就能圓你敞篷夢!

80萬其外觀犹如其名般,就是甲殼蟲的造型,外觀設計也非常可愛,圓圓的大燈,具有獨特的氣質,敞篷版甲殼蟲外形上和基本車型相比更拉風。車身比MX5還是霸氣不少,顯得更“強壯”些,復古式的輪圈設計十分扎眼,也更體現甲殼蟲其優雅端莊的形象。

汽車並不只是代步工具,還能成為另外一種生活方式、態度。說起敞篷車,我想每個人心中都想擁有屬於自己的敞篷車。沒有什麼比駕駛着敞篷車,沐浴在蔚藍海岸暖陽下,更為令人陶醉,享受當下!有些人會覺得這種生活離自己太遙遠、讓人觸手不及。但小編覺得只要心中所想,按着心裏想法去走,努力,必定實現。

馬自達(進口)-馬自達MX-5

(以下簡稱:MX-5)

平行進口價:34.5萬(手動)35.5萬(自動)

MX5給人第一感覺,非常親切,前臉就像一大大的微笑,很討人喜歡,車燈方面,更像雪亮雙眼,有神韻,感覺這就是一部充滿活力朝氣的車子。

車身很小巧、流暢車身線條,視覺效果動感十足。

內飾我想大家也非常熟悉,MX-5也不例外,內飾設計也是沿用馬自達一貫風格,但值得一提,MX-5螺旋狀出風口設計,顯得戰鬥力十足。

MX-5採用的軟頂敞篷設計,車身重量更輕。開蓬時間絕對要比上百萬級別要快!在2-3s就可以完成開蓬關蓬,夠快吧!這也完全取決你個人速度夠不夠快,因為MX-5開/關蓬是純手動開啟。

MX-5在動力方面,搭載了2.0L自然吸氣發動機,峰值扭矩201牛米,但由於車身較輕,和上代車型相比動力更出色。傳動系統配備6擋手動和6擋自動變速箱,手動車型動力輸出更直接,而且具有挑逗性。自動變速箱反應也是特別迅速,降檔积極,開起來很活躍。無論是購買手動還是自動,MX-5都能給你很好的駕駛樂趣。

大眾(進口)-甲殼蟲 2015款 180TSI 敞篷版

(以下簡稱:甲殼蟲)

指導價:28.80萬

其外觀犹如其名般,就是甲殼蟲的造型,外觀設計也非常可愛,圓圓的大燈,具有獨特的氣質,敞篷版甲殼蟲外形上和基本車型相比更拉風。

車身比MX5還是霸氣不少,顯得更“強壯”些,復古式的輪圈設計十分扎眼,也更體現甲殼蟲其優雅端莊的形象。

甲殼蟲內飾與外形設計一樣,很可愛,中控台設計也很簡潔,檔次感還是蠻高的。不同顏色裝飾板的點綴,讓其更具個性化。

甲殼蟲同樣和MX5一樣,採用了軟頂敞篷設計,但其開/關蓬時間卻要比MX5,慢了幾倍,車頂棚開啟時間為11s,而MX5,只需要2s,摺疊收起時間為9.5s,MX5隻需要2s!

甲殼蟲可在不超過50公里車速進行開關,而MX5卻是無論在任何車速都能隨時隨地去開關蓬!現在汽車開關蓬還分快慢?!而甲殼蟲更有逼格!逼格才是最重要!

這款甲殼蟲動力總成,搭載1.2T渦輪增壓發動機,最大功率77千瓦,峰值扭矩175牛米。或許你在想1.2T排量太小,但這你並無需擔心,因渦輪介入較早,油門初段很輕快,你甚至感覺這會是1.2T排量的車型嗎!時速在100公里以後,還是仍然感覺到較強的後勁,底盤行駛質感很紮實、從容。

編者點評:

當擁有足夠資金買一台敞篷車,其實更注重是其顏值與內飾是否高端大氣上檔次,而動力只是其次,開敞篷車,更多是一種情懷,另一種生活方式,可以享受比全景天窗更直接的陽光,比普通車型也更拉風。但在當今社會,敞篷車也有自身不足地方,例如空氣污染,要是天天敞開蓬駕駛,吸入的廢氣不容樂觀,有人說,戴口罩唄,那麼帶口罩開還買這車幹啥?還有就是中國的氣候,時冷時熱,敞篷駕駛,要麼冷死,要麼熱死曬死等問題,那麼當你去買一部敞篷車這些都是要去克服接納的問題,裝逼拉風可都是需要付出點代價啊。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

※回頭車貨運收費標準

聽說是15萬最帥的選擇!教授詳解思域和CX-4

主觀加速感受還行,渦輪遲滯現象輕微,在國內限速的範圍內,動力輸出杠杠的夠用。地板油時會一臉懵逼地問你:“主人,要加速。”,遲滯一會兒才開始賣力加速,激烈駕駛時稱不上得心應手。CX-4: 動力輸出持續、線性,給你一種恰到好處的加速感,容易掌控,會給你持續有力、綿密的推背感。

十代思域憑藉其強悍的動力、驚艷的外觀和強大的綜合產品力在緊湊型車市場里呼風喚雨,儼然就是不少年輕人、中年消費群體心目中的神車。加價提車、排期候車自不用說,競品車型紛紛降價促銷、改款換代提高競爭力來應對思域。

友商們不可能眼睜睜看着本田獨攬着龐大的新消費群體,時隔兩個月,倔強的馬自達不甘示弱,推出了為年輕人而打造的轎跑SUV——CX-4,概念車般的外觀設計以及大膽的流體線條,以錯位的車型與思域短兵相接。

下面為手拿15萬預算,躊躇于思域與CX-4之間的年輕人提供點選車建議。

車身尺寸

前臉設計

思域:集未來感、科技感於一身,“X”樣式的前臉設計,粗碩的鍍鉻飾條延伸至兩邊眼角,十分提神。

CX-4:繼續沿用家族式的魂動設計,流星眼LED大燈,低俯的車頭給人以時刻準備衝刺的感覺,魂動紅配色(2000元)更是彰顯個性的一抹艷麗。

車身腰線

思域:溜背的造型是外觀設計最大的亮點,尾部過渡自然,甚顯修長。

CX-4: 最小離地間隙196mm, 有轎車低矮扁平的發動機蓋高度,亦有SUV高人一等的的氣勢,車身比例恰到好處,越看越有韻味,令人愛不釋手。

車尾設計

思域:大膽的迴旋鏢狀尾燈設計,尾標與時俱進地改為220Turbo,緊隨德系車以扭矩數值作為尾標銘牌的潮流。十分可惜的是雙邊共兩出的排氣管採用隱藏式設計,這是刻意為年輕人提供改裝餘地嗎?改改改!

CX-4:貫穿兩邊車尾燈的鍍鉻飾條承托着銀色車標,恰到好處的美。2.0L車型同樣採用了雙邊兩出的排氣布局,相比其他車型要厚道。

內飾設計

思域:整體風格簡潔,符合大眾審美。用料看起來不錯,但實際體現一般,滿滿的塑料感,很多摸得到的地方例如擋把的邊上、車窗按鈕、內部門拉手、駕駛座大腿右側頂到的地方、A柱和B柱都是硬塑料。慶幸的是,金屬拉絲面板的運用彰顯出科技感。

CX-4:強調簡約、清新的內飾風格,相比過去長安昂克賽拉和CX-5的整體視覺好的不是一星半點。運用了最新的懸浮式中控屏,採用不少金屬拉絲面板進行點綴,觀感和質感都得到提升。

空間

思域:貫徹本田MM Concept理念:乘員空間最大化、机械空間最小化理念。空間表現比大多數競品車型要優秀,尤其橫向空間,絲毫沒有緊湊型車壓迫的感覺。溜背造型並未對頭部空間造成太大影響,淺色頂棚加上較薄的前排座椅,不會覺得壓抑。

CX-4:漂亮的流體車身線條,必然要犧牲乘坐空間,頭部空間方面受影響更大,工程師只能將座位高度盡可能調低,實際體驗只能算差強人意。後排中央地板隆起較高,且窗口面積較少、採光不理想,給乘坐人員壓迫的感覺。

實用配置

發動機

思域:實測百公里提速時間讓人瞠目結舌——7.3秒,同價位車型中難覓對手。主觀加速感受還行,渦輪遲滯現象輕微,在國內限速的範圍內,動力輸出杠杠的夠用。地板油時會一臉懵逼地問你:“主人,要加速?”,遲滯一會兒才開始賣力加速,激烈駕駛時稱不上得心應手。

CX-4: 動力輸出持續、線性,給你一種恰到好處的加速感,容易掌控,會給你持續有力、綿密的推背感。值得一提的是,馬自達工程師稱將CX-4的油門遲滯時間調校為0.3秒(與人體肌肉慣性相關),油門反應跟駕駛者預期處於一個基本同步的狀態。實測百公里提速時間9.8秒,對動力要求較高的選2.5L版本。

變速箱

思域:動力迅而不猛,因為駕駛者與發動機之間隔着台溫文爾雅的變速箱,CVT在行駛過程中存在感低,但不拖泥帶水,能化解生硬的頓挫,淡化了加速時的衝擊感。會通過轉速切換去模擬一些細小的換擋頓挫,轉速變化的換擋控制與駕駛者的意圖、預期判斷基本一致、同步,整體表現不錯。

CX-4: 這台創馳藍天6AT變速箱在車速超過8km/h后其離合器完全鎖止,防止動力輸出在液力變矩器上出現過多損耗,提高傳動效率和優化燃油經濟性。很明白駕駛員意圖,執行力非常到位,一路上用細微的油門變化去挑逗它,都能很好滿足,彷彿時刻在等待你的命令。

底盤

思域:前麥弗遜式獨立懸架、后多連桿獨立懸架。路面小顛簸過濾得不錯,但保留了一定的路感信息傳遞給駕駛者,中後段對車身的支撐到位,富有運動感的懸架。

CX-4:前麥弗遜式獨立懸架、后多連桿獨立懸架。懸架支撐性出色,過彎、掉頭時的車身姿態控制到位,保留了清晰的路感,行駛表現接近“彎道王”昂克賽拉,比絕大多數SUV的行駛質感要好。

操控

思域:路感被過濾得所剩無幾,懸架行程較長,有一定曠量,過彎、掉頭姿態傾側明顯,屬於舒適家用車的調校範疇。所幸轉彎半徑小,掉頭方便。轉向低速沉穩,但速度上來后變輕。

CX-4:定位轎跑SUV不單是在外觀做文章,CX-4在操控方面亦盡量嚮往轎車方面靠攏。電動助力,指向精準,車頭指向靈活,車尾循跡性好,和不錯的駕駛樂趣,開起來不像是一台SUV。

總結:目前年輕消費群體對思域、CX-4兩款車青睞有加,前瞻性的外觀設計、優秀的動力表現和出眾的操控都是它們的亮點,預算有限的建議選思域1.5T自動尊貴版,而預算充足不妨體驗下CX-4 2.5L 自動四驅藍天激情版,動力相比2.0L得到提升的同時還配備了四驅系統,適應更多路況的行駛。 它們就像是未來汽車走向的先行者,期待有更多的後起之秀面世,讓年輕消費者有更多的選擇。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

廣汽傳祺打出SUV“組合拳”,2017款GS4蛻變成全能选手

與此同時,還能通過手機遠程控制安防系統(T-BOX),隨時隨地了解控制車內外各種信息。新增的愛信6AT變速箱, 最大的優點就是平順性處於行業領先水平,質量穩定可靠,駕駛舒適性得到了提升。i-4WD智能適時四驅系統,具有兩驅、智能適時四驅及強制四驅等三種模式,極大地提高了傳祺GS4的越野性能。

如果說自主SUV的崛起,是得益於精品車型的話,那麼就不得不提傳祺GS4了,不俗的原創外觀+成熟的動力組合+大空間,正好符合了我們對一輛緊湊SUV的所有需求。

縱使傳祺GS4一經推出就取得了成功,但作為一款新車,還存在着很多進步的空間 。比如當時呼聲很高的“動力如果能有更多選擇就好了”,在今年初的時候,傳祺GS4 就順勢推出了235T(1.5T)動力配置,繼續鞏固了GS4銷量地位,在10月銷量中,傳祺GS4的成績已突破3.5萬。

後來,有更高要求的消費者又提出 “能不能增配6AT+四驅+後排出風口呢”?傳祺這次依舊沒讓我們失望!在半個月前,傳祺GS4就發布了2017新款,上述的功能都如我們所願,總共超過15項性能與配置升級!

2017款 傳祺GS4 235T 6AT版

指導價格:13.38-16.18萬

在廣州車展期間,小編也前往了傳祺展台目睹了新款GS4,但那個人山人海的場景依然歷歷在目,作為本地車企,在主場的人氣還是非常火爆的。

傳祺GS4 2017款15項配置升級中

最大的變化主要有以下3點

●車載互聯繫統

●愛信6AT+i-4WD

●後排出風口+USB接口

2017款GS4整合了百度CarLife車載互聯繫統,理論上支持所有Andriod系統手機,可實現語音控制,觸摸屏控制電話、音樂、地圖及其它App應用。

與此同時,還能通過手機遠程控制安防系統(T-BOX),隨時隨地了解控制車內外各種信息。

新增的愛信6AT變速箱, 最大的優點就是平順性處於行業領先水平,質量穩定可靠,駕駛舒適性得到了提升。

i-4WD智能適時四驅系統,具有兩驅、智能適時四驅及強制四驅等三種模式,極大地提高了傳祺GS4的越野性能。只需輕轉控制旋鈕,即可自由切換,兼顧燃油經濟性和通過性,最大爬坡度輕鬆≥40%。

後排新增出風口、雙USB接口與手機儲物盒,對於後排乘客而言,也豐富了乘車樂趣,因為都是非常實用的配置。同時,後排中央扶手及中央頭枕與後排隱私玻璃在2017款GS4中配車型即有所體現。

除這幾項大的升級之外,其餘的功能、性能升級在這就一一闡述了,想了解的朋友可以自己查一下產品資料。

小編最後點評

放眼所有汽車品牌,幾乎每個品牌都有屬於自己的品牌符號,像高爾夫之於大眾、卡羅拉之於豐田、3系之於寶馬。GS4之于于傳祺正是這樣,一款細分市場明星車型全面打響了品牌,成為了家喻戶曉的標杆產品。隨後又推出了GS8這款豪華大七座SUV,據小編了解,到目前為止已經收穫2萬個訂單了!在國產高端SUV銷量普遍不濟的情況下,GS8的勢頭非常奪人眼球。

在國產車裡,小編也不止一次推薦過GS4了,它和其他品牌不一樣,傳祺的原創設計、做工和自主核心技術非常成熟,不僅有了與合資同台競技的硬實力,還有了以後長久發展的根基,小編對它還是比較看好的。

2017款GS4的改款是成功的,豐富的產品線也趨於完善,配置升級非常實用,滿足了更多人的選車訴求。

更重要的是,價格方面也沒想象中大幅上漲,而是與1.5T 7速雙離合配置的價格保持了一致,聰明的定價相信也能俘虜不少新的消費者,繼續鞏固傳祺GS4在銷量榜的領先地位。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

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

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

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準

台中搬家公司費用怎麼算?

c#中的值類型和引用類型

值類型和引用類型,是c#比較基礎,也必須掌握的知識點,但是也不是那麼輕易就能掌握,今天跟着老胡一起來看看吧。
 

典型類型

首先我們看看這兩種不同的類型有哪些比較典型的代表。
 

典型值類型

int, long, float, double等原始類型中表示数字的類型都是值類型,表示時間的datatime也是值類型,除此之外我們還可以通過關鍵字struct自定義值類型。
 

典型引用類型

原始類型中,array, list, dictionary, queue, stack和string都是引用類型,除此之外我們通過關鍵字class自定義引用類型。
 

基類

c#中所有的類型都最終繼承自Object,這是沒有疑問的,但是這其中還有些微區別。
 

值類型基類

對於值類型來說,除了最終繼承自Object,還繼承自ValueType,繼承鏈如下

但是請不要誤解,這裏僅僅指的是值類型天然是ValueType,但是不代表值類型能夠這麼聲明

struct Struct1 : ValueType
{

}

這樣是會引起編譯錯誤的,值類型不能繼承任何其他類型,值類型只能實現接口,不能繼承自其它類型。只有引用類型既可以實現接口也能繼承自其它類型。順便說一下,還有一點比較重要的是,ValueType重寫了Object基類的Equals方法和GetHashCode方法,所以當使用Equals比較兩個值類型的時候,系統會比較兩個值類型的各個屬性是否相等,再返回結果,這就是所謂的相等性。與此相對,引用類型在使用Equals的時候,會在後台調用object.ReferenceEquals,換言之,引用類型在比較相等性的時候會考慮同一性
 

引用類型基類

對於引用類型就沒有那麼麻煩,引用類型不會繼承自ValueType。引用類型可以繼承其他類型。
 

在內存中的表現

我們都知道,C#將內存分為了兩部分,一個是Stack,另外一個是Managed Heap。一般來說,用於函數調用進棧,函數返回出棧,用的是Stack,而當創造一個新的實例時,會根據創建的實例屬於值類型還是引用類型決定使用Stack還是Managed Heap。
 

值類型在內存中

當創建一個值類型對象時,c#會在Stack上面創建一塊空間,這塊空間就存放這個值類型對象。
int是一個典型的值類型,如下語句

int age = 10;

會存在於內存中的Stack上面。

如果把值類型的實例賦值給另外一個值類型,那麼效果就是複製一個新的值類型實例。

int myAge = age;

 

引用類型在內存中

與值類型在內存中的表現不一樣,創建一個引用類型的實例,不但會在Stack上面新建一個引用,還會在Heap上面劃分出內存以容納該引用類型實例。用戶在使用的時候通過Stack上面的變量間接引用該實例。

class Author
{
	public string Name{get;set;}
	public int Age{get;set;}
}

Author author = new Author(){Name="deatharthas", Age= 32};

注意看和值類型在內存中的區別,引用類型通過Stack上的變量訪問位於Heap上面的實例。
在賦值的時候,拷貝的僅僅是Stack上面的變量,新拷貝出來的對象和舊的對象指向的是同一塊內存。

Author myAuthor = author;

這個時候,author和myAuthor指向同一塊內存,稱為同一性,通過調用

object.ReferenceEquals(myAuthor, author);

可以得到驗證。
 
但可能有細心的朋友會有疑問了,不是說int是值類型,值類型是存在於Stack上面的嗎?為什麼在author類裏面,它會在Heap裏面呢?贊一個細心!值類型一般存在於Stack上面,但如果某個值類型包含於引用類型,那麼它也會隨着那個引用類型存放在Heap上面。
 

當參數時的行為區別

c#中的參數傳遞默認都是傳值(by value),但是根據所傳遞對象是值類型還是引用類型,它們的行為還是有所區別,現在我們來看看。

值類型當參數

值類型當參數的時候,傳遞到函數內部的是一份值類型的拷貝,所以在函數內部修改這個拷貝不會影響原對象。除非我們在傳遞參數的時候使用了ref或者out。
 

引用類型當參數

如果參數是引用類型,傳遞到函數內部的依然是一份拷貝,但是這個拷貝是其在Stack上面的變量的拷貝,就像上面的賦值那個例子。所以這個時候這份拷貝其實和原對象指向同一塊內存(指向同一性),修改這個對象可以反映到原對象上面。
 

謹慎返回引用類型

編程是一項需要謹慎的工作,有時候我們經常會犯一些錯誤,而這些錯誤又是那麼的不明顯以至於不摔坑幾次,我們根本察覺不了,考慮下面一個例子。

    class People
    {
        public string Name { get; set; }
        public int Age { get; set; }
        private People _Father = null;
        public People Father { get { return _Father; } }
        public People(People father)
        {
            _Father = father;
        }
        public void ShowFather()
        {
            Console.WriteLine("father's name is " + Father.Name + " and his age is " + Father.Age);
        }
    }

    class Program
    {        
        static void Main(string[] args)
        {
            People father = new People(null) { Name = "father", Age = 60 };
            People son = new People(father);
            son.ShowFather();
            Console.ReadLine();
        }
    }

看起來沒什麼問題,對吧?Father沒有提供setter,似乎是安全的。但是我們試試下面的代碼。

	static void Main(string[] args)
        {
            People father = new People(null) { Name = "father", Age = 60 };
            People son = new People(father);
            var f = son.Father;
            f.Name="Changed";
            son.ShowFather();
            Console.ReadLine();
        }

看,發現了什麼,外部改變了本來應該被封裝所保護的Father屬性,封裝被破壞了!
稍微一想我們應該能明白這個道理,Father屬性返回的拷貝的變量和原Father變量指向同一塊實例。要想解決這個問題,我們要麼返回一個值類型,要麼返回一個全新的對象。修改Father屬性如下:

public People Father { get { return new People(_Father._Father) { Name = _Father.Name, Age = _Father.Age }; } }

再次測試,

這次封裝就沒問題了。
 

總結

我們大概知道了值類型和引用類型的區別,包括它們的行為,在內存的居住方式,以及使用引用類型時可能會遇到的暗坑,希望大家通過閱讀這篇文章,能夠加深一些對它們的了解,少走一些彎路。
今天也簡單的提到了比較時的同一性,和預防封裝被破壞所採用的返回一個新的實例拷貝的策略(這個時候適合使用DeepCopy),我們之後有機會再詳細聊。

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

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

※別再煩惱如何寫文案,掌握八大原則!

系統化學習多線程(一)

大綱

————————-學前必讀———————————-

學習不能快速成功,但一定可以快速入門
整體課程思路:
1.實踐為主,理論化偏少
2.課程筆記有完整的案例和代碼,(為了學習效率)再開始之前我會簡單粗暴的介紹知識點案例思路,
有基礎的同學聽了之後可以直接結合筆記寫代碼,
如果沒聽懂再向下看視頻,我會手把手編寫代碼和演示測試結果;
3.重要提示,學編程和學游泳一樣,多實踐學習效率才高,理解才透徹;
4.編碼功底差的建議每個案例代碼寫三遍,至於為什麼…<<賣油翁>>…老祖宗的智慧

————————————————————————-

 1.線程

1.1.什麼是線程

線程(英語:thread)是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程,每條線程并行執行不同的任務。在Unix System V及SunOS中也被稱為輕量進程(lightweight processes),但輕量進程更多指內核線程(kernel thread),而把用戶線程(user thread)稱為線程。(來自百度百科)

一個進程可以有很多線程,每條線程并行執行不同的任務。

1.2.多線程hello word

需求:模擬在計算上一邊聽歌一邊打遊戲

三種實現方案如下:

TestDemo

 1 package com.wfd360.thread;
 2 
 3 import com.wfd360.thread.demo01.GameRunnable;
 4 import com.wfd360.thread.demo01.MusicRunnable;
 5 import com.wfd360.thread.demo02.GameThread;
 6 import com.wfd360.thread.demo02.MusicThread;
 7 import org.junit.Test;
 8 
 9 /**
10  * @author 姿勢帝-博客園
11  * @address https://www.cnblogs.com/newAndHui/
12  * @WeChat 851298348
13  * @create 05/03 5:27
14  * @description 需求分析:
15  * 1.模擬一邊打遊戲一邊聽音樂,在控制台打印輸出模擬
16  * 2.把兩個業務封裝成獨立的線程,實現接口Runnable或繼承Thread,通過看源碼你會發現Thread類實現了接口Runnable,使用本質上這兩種方法時一樣的。
17  * 3.Thread類提供兩個方法,線程主題方法run,啟動線程方法start
18  */
19 public class TestDemo {
20     /**
21      * 方式1:實現接口Runnable
22      */
23     @Test
24     public void testRunnable() throws InterruptedException {
25         System.out.println("-------test start-------");
26         // 實例對象
27         MusicRunnable music = new MusicRunnable();
28         GameRunnable game = new GameRunnable();
29         // 創建線程
30         Thread musicThread = new Thread(music);
31         Thread gameThread = new Thread(game);
32         // 啟動線程
33         musicThread.start();
34         gameThread.start();
35         System.out.println("--------等待其他線程執行--------------");
36         Thread.sleep(5 * 1000);
37         System.out.println("-------test end-------");
38     }
39 
40     /**
41      * 方式2:繼承Thread
42      */
43     @Test
44     public void testThread() throws InterruptedException {
45         System.out.println("-------test start-------");
46         // 創建線程
47         MusicThread musicThread = new MusicThread();
48         GameThread gameThread = new GameThread();
49         // 啟動線程
50         musicThread.start();
51         gameThread.start();
52         System.out.println("--------等待其他線程執行--------------");
53         Thread.sleep(5 * 1000);
54         System.out.println("-------test end-------");
55     }
56 
57     /**
58      * 方式3:簡寫,這種寫法一般我們在做模擬測試的使用,在正式代碼中建議不使用,可讀性較差
59      */
60     @Test
61     public void testThreadSimple() throws InterruptedException {
62         System.out.println("-------test start-------");
63         // 創建線程
64         Thread musicThread = new Thread(() -> {
65             for (int i = 0; i < 100; i++) {
66                 System.out.println("=======聽音樂中============" + i);
67             }
68         });
69         Thread gameThread = new Thread(() -> {
70             for (int i = 0; i < 100; i++) {
71                 System.out.println("=======打遊戲中============" + i);
72             }
73         });
74         // 啟動線程
75         musicThread.start();
76         gameThread.start();
77         System.out.println("--------等待其他線程執行--------------");
78         Thread.sleep(5 * 1000);
79         System.out.println("-------test end-------");
80     }
81 }


 實現接口Runnable

 1 package com.wfd360.thread.demo01;
 2 
 3 /**
 4  * @author 姿勢帝-博客園
 5  * @address https://www.cnblogs.com/newAndHui/
 6  * @WeChat 851298348
 7  * @create 05/03 5:31
 8  * @description
 9  */
10 public class GameRunnable implements Runnable {
11     @Override
12     public void run() {
13         for (int i = 0; i < 100; i++) {
14             System.out.println("=======打遊戲中============" + i);
15         }
16     }
17 }

GameRunnable

 1 package com.wfd360.thread.demo01;
 2 
 3 /**
 4  * @author 姿勢帝-博客園
 5  * @address https://www.cnblogs.com/newAndHui/
 6  * @WeChat 851298348
 7  * @create 05/03 5:29
 8  * @description
 9  */
10 public class MusicRunnable implements Runnable {
11     @Override
12     public void run() {
13         for (int i = 0; i < 100; i++) {
14             System.out.println("=======聽音樂中============"+i);
15         }
16     }
17 }

MusicRunnable

 繼承Thread

 1 package com.wfd360.thread.demo02;
 2 
 3 /**
 4  * @author 姿勢帝-博客園
 5  * @address https://www.cnblogs.com/newAndHui/
 6  * @WeChat 851298348
 7  * @create 05/03 6:00
 8  * @description
 9  */
10 public class GameThread extends Thread {
11     @Override
12     public void run() {
13         for (int i = 0; i < 100; i++) {
14             System.out.println("-------遊戲中----------"+i);
15         }
16     }
17 }

GameThread GameThread

 1 package com.wfd360.thread.demo02;
 2 
 3 /**
 4  * @author 姿勢帝-博客園
 5  * @address https://www.cnblogs.com/newAndHui/
 6  * @WeChat 851298348
 7  * @create 05/03 6:00
 8  * @description
 9  */
10 public class MusicThread extends Thread {
11     @Override
12     public void run() {
13         for (int i = 0; i < 100; i++) {
14             System.out.println("-------音樂中----------" + i);
15         }
16     }
17 }


 總結

啟動線程兩種方式:

    1.通過繼承Thread類

    2.實現Runnable接口

 使用哪種方式更好?

區別: 

一個類如果繼承了其他類,就無法在繼承Thread類,在Java中,一個類只能繼承一個類,而一個類如果實現了一個接口,還可以實現其他接口,接口是可以多實現的,所以說

Runable的擴展性更強,但是繼承的方式更簡單,個人建議一般情況下使用Thread;

實現接口Runnable或繼承Thread,通過看源碼你會發現Thread類實現了接口Runnable,使用本質上這兩種方法是一樣的

啟動線程流程:

    創建啟動線程的方式一:繼承Thread類

       1.將業務方法封裝成線程對象,自定義類t extends Thread類; 

       2.覆寫run方法: 覆寫第一步中的run方法;

       3.創建自定義對象t

       4.啟動線程 t.start();

   創建啟動線程方式二:實現Runnable接口

      1.將業務方法封裝成線程對象,自定義類t implements Runnable接口;

      2.實現第一步中的run方法

      3.創建自定義對象t

      4.啟動線程 new Thread(t).start();

1.3.對主線程與創建線程執行順序的理解

問題:
直接寫一個簡單的HelloWorld 程序,有沒有線程?
==>有一個主線程,在垃圾回收的時候,有gc 線程。

 1 package com.wfd360.thread;
 2 
 3 import org.junit.Test;
 4 
 5 /**
 6  * @author 姿勢帝-博客園
 7  * @address https://www.cnblogs.com/newAndHui/
 8  * @WeChat 851298348
 9  * @create 05/04 11:09
10  * @description <p>
11  * 問題:
12  * 直接寫一個簡單的HelloWorld 程序,有沒有線程?
13  * ==>有一個主線程,在垃圾回收的時候,有gc 線程。
14  * 結論:一旦線程啟動起來之後就是獨立的,和創建環境沒有關係;
15  * 啟動線程不能直接調用run方法,必須調用start方法;
16  * </p>
17  */
18 public class TestDemo02 {
19     /**
20      * 如果把創建線程放在循環語句的 下 面,會交替出現嗎
21      * ==>否,因為主線程執行完成后才會啟動hello線程
22      *
23      * @throws Exception
24      */
25     @Test
26     public void test1() throws Exception {
27         System.out.println("---test start-------");
28         // 執行主線程
29         for (int i = 0; i < 100; i++) {
30             System.out.println("-----test1--------" + i);
31         }
32         // 啟動hello線程
33         new HelloThread().start();
34         System.out.println("=======等待執行完成===========");
35         Thread.sleep(5 * 1000);
36         System.out.println("---test end-------");
37     }
38 
39     /**
40      * 如果把創建線程放在循環語句的 上 面,會交替出現嗎
41      * ==>可能會,可能不會,可能出現for循環完之後,線程還沒有啟動完;
42      *
43      * @throws Exception
44      */
45     @Test
46     public void test2() throws Exception {
47         System.out.println("---test start-------");
48         // 啟動hello線程
49         new HelloThread().start();
50         // 執行主線程
51         for (int i = 0; i < 100; i++) {
52             System.out.println("-----test1--------" + i);
53         }
54         System.out.println("=======等待執行完成===========");
55         Thread.sleep(5 * 1000);
56         System.out.println("---test end-------");
57     }
58 
59     /**
60      * 採用內部類的方式定義一個hello線程對象
61      */
62     class HelloThread extends Thread {
63         @Override
64         public void run() {
65             for (int i = 0; i < 100; i++) {
66                 System.out.println("-----HelloThread--------" + i);
67             }
68         }
69     }
70 }

TestDemo02

結論:一旦線程啟動起來之後就是獨立的,和創建環境沒有關係;
啟動線程不能直接調用run方法,必須調用start方法;

 1.4.對sleep方法的理解

package com.wfd360.thread;

/**
 * @author 姿勢帝-博客園
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 05/04 11:34
 * @description <p>
 * Thread類的方法:
 * static void sleep(long millis) 在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行);
 * </p>
 */
public class TestSleep {
    /**
     * 做一個簡易倒計時,10秒鐘,控制台每一秒輸出一個数字,如10,9,8,7.....0
     */
    public static void main(String[] args) throws Exception {
        System.out.println("---test start-------");
        for (int i = 10; i >= 0; i--) {
            Thread.sleep(1 * 1000);
            System.out.println(i);
        }
        System.out.println("---test end-------");
    }
}

1.5.線程名稱的設置與獲取

繼承方式

簡單需求:使用多線程模擬多窗口售票

 1 package com.wfd360.thread.demo03Ticket;
 2 
 3 /**
 4  * @author 姿勢帝-博客園
 5  * @address https://www.cnblogs.com/newAndHui/
 6  * @WeChat 851298348
 7  * @create 05/04 11:55
 8  * @description <p>
 9  * 模擬多線程售票
10  * </p>
11  */
12 public class TicketThread extends Thread {
13     // 假定票總是100張
14     private static Integer num = 100;
15 
16     @Override
17     public void run() {
18         // 只要有票就一直售票
19         while (num > 0) {
20             System.out.println("正在出售第" + num + "張票");
21             --num;
22         }
23         System.out.println("===售票結束===");
24     }
25 }

TicketThread

test

/**
     * 測試模擬三個窗口售票
     * @throws InterruptedException
     */
    @Test
    public void testTicketThread() throws InterruptedException {
        System.out.println("---test start-------");
        // 模擬多3個窗口售票
        TicketThread ticketThread1 = new TicketThread();
        TicketThread ticketThread2 = new TicketThread();
        TicketThread ticketThread3 = new TicketThread();
        // 啟動線程售票
        ticketThread1.start();
        ticketThread2.start();
        ticketThread3.start();
        System.out.println("======等待售票============");
        Thread.sleep(5 * 1000);
        System.out.println("---test end-------");
    }

結果:

1.在售票過程中不能區分售出的票是那個窗口售出的,解決通過線程名稱判斷

2.有重複售出的票(後面的線程同步解決)

解決第一個問題,設置獲取線程名稱,通過Thread對象裏面自帶的getName,setName方法

 具體代碼

設置線程名稱

 獲取線程名稱

 上面講了繼承的方式獲取線程名稱,那麼實現接口Runnable的方式怎麼獲取設置勒

繼承Thread的方式,可以通過getName的方式獲取當前線程的名稱?
那使用Runnable的方式,能通過getName獲取嘛?

getName方法是Thread類的,但是TicketThread現在並沒有繼承Thread類,而是實現了Runnable接口.

問題:如果實現Runnable接口,怎麼獲取線程名稱?

 思考:TicketThread類裏面的代碼要執行,它肯定存在於某個線程中, 就比如寫個helloword打印語句,是不是也處於一個主線程中,那這裏怎麼獲取線程名稱?
通過動態獲取,當程序正在執行的時候,獲取當前正在執行的線程名稱。怎麼獲取?
在Thread類裏面有個靜態的方法currentThread() 方法,返回當前正在執行的線程引用;

Thread.currentThread().getName

那怎麼設置線程名稱?

Thread類裏面有個name字段,相當於Thread類把它包裝了一下:

通過源碼可以發現,構造方法裏面還有可以傳一個名字:

具體實現代碼如下

 

 總結:

繼承方式設置\獲取線程名稱通過 Thread對象裏面的 setName,getName方法;

實現接口方式設置名稱通過 new Thread(‘線程實例對象’, “線程名稱”),獲取線程名稱通過:Thread.currentThread().getName

1.6.Thread的join方法

void join() 方法 :等待該線程終止
void join(long millis) 方法 :等待該線程終止的時間最長為millis毫秒

需求: 當主線程運行到20的時候(i =20)的時候,讓JoinThread線程加進來直到執行完成,在執行主線程.

 1 package com.wfd360.thread;
 2 
 3 import org.junit.Test;
 4 
 5 /**
 6  * @author 姿勢帝-博客園
 7  * @address https://www.cnblogs.com/newAndHui/
 8  * @WeChat 851298348
 9  * @create 05/04 6:31
10  * @description
11  */
12 public class Test05Join {
13     /**
14      * 需求:
15      * 當主線程for循環到i=20時,等JoinThread線程執行完成后,在執行for循環的線程
16      * @throws InterruptedException
17      */
18     @Test
19     public void testJoinThread() throws InterruptedException {
20         System.out.println("---test start-------");
21         // 開啟線程
22         JoinThread thread = new JoinThread();
23         thread.start();
24         // 循環打印線程
25         for (int i = 0; i < 100; i++) {
26             System.out.println("======testJoinThread=========="+i);
27             Thread.sleep(1);
28             if (i==20){
29                 // 等線程JoinThread執行完成
30                 thread.join();
31             }
32         }
33         System.out.println("=============等待線程執行完成===================");
34         Thread.sleep(10*1000);
35         System.out.println("---test end-------");
36     }
37     
38     class JoinThread extends Thread {
39         @Override
40         public void run() {
41             for (int i = 0; i < 100; i++) {
42                 System.out.println("=====JoinThread=======" + i);
43                 // 模擬處理很多業務耗時1毫秒
44                 try {
45                     Thread.sleep(1);
46                 } catch (InterruptedException e) {
47                     e.printStackTrace();
48                 }
49             }
50         }
51     }
52 }

Test05Join

1.7.線程優先級

直接上代碼

 1 package com.wfd360.thread;
 2 
 3 /**
 4  * @author 姿勢帝-博客園
 5  * @address https://www.cnblogs.com/newAndHui/
 6  * @WeChat 851298348
 7  * @create 05/05 8:17
 8  * @description <p>
 9  * 1.==>線程優先級的理解:
10  * 線程的優先級和生活中類似,高優先級線程的執行優先於低優先級線程;
11  * 並不是絕對的,可能優先級高的線程優先 比 優先級低的線程先執行,只能說,高優先級的線程優先執行的幾率更多;
12  * (比如兩個線程,一個優先級高,一個優先級低,如果一共運行一個小時,優先級高的線程執行遠遠大於優先級低的但是並不是說優先級高的先執行完,
13  * 在執行優先級低的)
14  * 2.==>重新設置線程優先級
15  * int getPriority() 返回線程的優先級。
16  * void setPriority(int newPriority) 更改線程的優先級。Java線程的優先級從1到10級別,值越大優先級越高.
17  * 3.==>線程的默認優先級受創建線程的環境影響,默認值5,自定義線程的默認優先級和創建它的環境的線程優先級一致
18  * </p>
19  */
20 public class Test06Priority {
21     /**
22      * 測試獲取線程優先級,設置線程優先級,驗證線程優先級受創建環境影響
23      * @param args
24      */
25     public static void main(String[] args) {
26         Thread threadMain = Thread.currentThread();
27         // 獲取默認優先級数字
28         System.out.println("main線程默認優先級:" +threadMain.getPriority());// 5
29         // 重新設置默認優先級数字
30         threadMain.setPriority(8);
31         // 再次重新獲取優先級数字
32         System.out.println("main線程修改后的優先級:" +threadMain.getPriority());// 8
33         // 創建一個線程查看優先級
34         Thread thread = new Thread();
35         System.out.println("thread線程的優先級:" +thread.getPriority());// 8 受創建環境影響
36     }
37 }

1.8.後台線程,即守護線程

直接看代碼

 1 package com.wfd360.thread;
 2 
 3 import com.wfd360.thread.demo04Daemon.DaemonThreaad;
 4 
 5 /**
 6  * @author 姿勢帝-博客園
 7  * @address https://www.cnblogs.com/newAndHui/
 8  * @WeChat 851298348
 9  * @create 05/05 9:12
10  * @description <p>
11  * 後台線程,即守護線程
12  * 後台線程:指為其他線程提供服務的線程,也稱為守護線程。JVM的垃圾回收線程就是一個後台線程。
13  * 需求:嘗試把線程標記為後台線程或者標記為(前台)線程;
14  * Thread類提供的方法:
15  * 方法1: void setDaemon(boolean on) 將該線程標記為守護線程或用戶線程,true為後台線程,false為用戶線程(前台線下)
16  * 怎樣測試該線程是否是守護線程?
17  * 方法2:isDaemon()  測試該線程是否為守護線程. true為後台線程,false為用戶線程(前台線下)
18  * <p>
19  * 結論1:活動的線程(已經在執行的線程t.start())不能設置後台線程,即主線程不能設置為後台線程。
20  * 結論2: 自定義線程的默認狀態和環境有關,後台線程中創建的線程默認是後台線程,前台線程中創建的線程為前台線程.
21  * 結論3: 前台線程執行完后,會直接關閉後台線程,即自定義的後台線程不一定能執行完成
22  * </p>
23  */
24 public class Test07Daemon {
25     /**
26      * 測試1
27      * 查看主線程的狀態,嘗試更改
28      * 結論:活動的線程不能設置為後台線程
29      *
30      * @param args
31      */
32     public static void main1(String[] args) {
33         Thread threadMain = Thread.currentThread();
34         System.out.println("是後台線程么:" + threadMain.isDaemon());// false
35         threadMain.setDaemon(true); // 報錯,活動的線程不能設置為後台線程
36         System.out.println("修改后是後台線程么:" + threadMain.isDaemon());
37     }
38 
39     /**
40      * 測試2
41      * 查看主線程中 創建線程的狀態,嘗試更改;
42      *
43      * @param args
44      */
45     public static void main2(String[] args) {
46         Thread thread = new Thread();
47         // false
48         System.out.println("是後台線程么:" + thread.isDaemon());
49         // 修改為後台線程
50         thread.setDaemon(true);
51         System.out.println("修改后是後台線程么:" + thread.isDaemon());
52     }
53 
54     /**
55      * 測試3
56      * 查看主線程中 創建線程的狀態,嘗試更改,讓線程處於活動狀態在修改->報錯;
57      *
58      * @param args
59      */
60     public static void main3(String[] args) {
61         DaemonThread thread = new DaemonThread();
62         // 讓線程處於活躍狀態
63         thread.start();
64         // false
65         System.out.println("是後台線程么:" + thread.isDaemon());
66         // 修改為後台線程,報錯,當前已經是活躍狀態(thread.start())不能修改為後台線程
67         thread.setDaemon(true);
68         System.out.println("修改后是後台線程么:" + thread.isDaemon());
69     }
70 
71     /**
72      * 測試4
73      * 前台線程執行完后,會直接關閉後台線程,即如果後台線程不一定能執行完成
74      * 可以通過修改等待執行時間來觀察DaemonThread線程的數組輸出變化
75      *
76      * @param args
77      */
78     public static void main(String[] args) throws InterruptedException {
79         DaemonThread thread = new DaemonThread();
80         // 修改為後台線程
81         thread.setDaemon(true);
82         // 讓線程處於活躍狀態
83         thread.start();
84         System.out.println("========等待後台線程執行============");
85         Thread.sleep(5 * 1000);
86     }
87 }
 1 package com.wfd360.thread.demo04Daemon;
 2 
 3 /**
 4  * @author 姿勢帝-博客園
 5  * @address https://www.cnblogs.com/newAndHui/
 6  * @WeChat 851298348
 7  * @create 05/05 9:27
 8  * @description
 9  */
10 public class DaemonThread extends Thread {
11     @Override
12     public void run() {
13         for (int i = 0; i < 10; i++) {
14             System.out.println("===="+i);
15             try {
16                 Thread.sleep(1000);
17             } catch (InterruptedException e) {
18                 e.printStackTrace();
19             }
20         }
21     }
22 }

DaemonThread

線程基礎相關的方法定義就先到這裏,下一篇我們將進入線程同步.

https://www.cnblogs.com/newAndHui/p/12831089.html

系統化的在線學習:點擊進入學習

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

【其他文章推薦】

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

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

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

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

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!