Spring 註解學習 詳細程式碼示例

黑貓的黑貓黑貓發表於2020-08-03

學習Sping註解,編寫示例,最終整理成文章。如有錯誤,請指出。

該文章主要是針對新手的簡單使用示例,講述如何使用該註釋,沒有過多的原理解析。

已整理的註解請看右側目錄。寫的示例程式碼也會在結尾附出。

配置類相關注解

@Configuration

宣告當前類為配置類,把一個類作為一個IoC容器,它的某個方法頭上如果註冊了@Bean,就會作為這個Spring容器中的Bean。該類等價 與XML中配置beans。

@Configuration
public class MyConfig {
}

@Bean

註解在方法上,宣告當前方法的返回值為一個Bean。返回的Bean對應的類中可以定義init()的方法和destroy()方法,然後在@Bean(initMethod="init",destroyMethod="destroy")定義,在構造之後執行init,在銷燬之前執行destory。

@Configuration
@ComponentScan("com.blackcat.annotation.bean")
public class MyConfig {

/**
* <p> 描述 : @Bean的形式是使用的話, bean的預設名稱是方法名
* @author : blackcat
* @date : 2020/5/23 16:20
* @see App ctx.getBean("user") 得到bean
*/
@Bean(initMethod="init",destroyMethod="destroy")
public User user(){
return new User();
}
}
/**
 * <p> 描述 :Bean
 * @author : blackcat
 * @date : 2020/5/23 16:06
 * @see MyConfig  需要看MyConfig
 *
 */
@Data
public class User {
    private String name="zhang";

    public void init(){
        System.out.println("init");
    }

    public void destroy(){
        System.out.println("destroy");
    }
}

@ComponentScan

註解用於啟用元件掃描,其作用同xml中配置<context:component-scan>。若不配置其value值,它會以配置類所在的包作為基礎包(base package)來掃描元件。

引數說明:

basePackages:掃描的路徑
excludeFilters:排除 過濾條件
includeFilters:包含 過濾條件
useDefaultFilters:若使用包含的用法,需要把useDefaultFilters屬性設定為false(true表示掃描全部的)

FilterType的型別:

FilterType.ANNOTATION: @Controller @Service @Repository @Compent
FilterType.ASSIGNABLE_TYPE:指定元件
FilterType.CUSTOM: 自定義的
FilterType.REGEX: 正規表示式的(不常用,沒寫示例)
FilterType.ASPECTJ: aspectj型別的(不常用,沒寫示例)

最簡單示例

自動掃描指定包com.blackcat.annotation.compentscan下所有使用@Service,@Component,@Controller,@Repository的類並註冊。

@ComponentScan(basePackages = {"com.blackcat.annotation.compentscan"})
@Configuration
public class MyConfig {

}

排除excludeFilters

將不會載入所有Controller類 及UserService類(指定類)。

@ComponentScan(basePackages = {"com.blackcat.annotation.componentscan"},excludeFilters = {
    @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class}),
    @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,value = {UserService.class})
})
@Configuration
public class MyConfig {

}

包含includeFilters

載入除所有使用@Service,@Component,@Controller,@Repository外,以及包含過濾條件的MyFilterType,需要把useDefaultFilters屬性設定為false(true表示掃描全部的)。

@ComponentScan(basePackages = {"com.blackcat.annotation.componentscan"},includeFilters = {
    @ComponentScan.Filter(type = FilterType.CUSTOM,value = MyFilterType.class)
},useDefaultFilters = false)
@Configuration
public class MyConfig {

}
/**
 * <p> 描述 :FilterType.CUSTOM 自定義型別使用
 * @author : blackcat
 * @date : 2020/5/23 16:40
 */
public class MyFilterType implements TypeFilter {

    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        //獲取當前類的註解源資訊
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();

        //獲取當前類的class的源資訊
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        //獲取當前類的資源資訊
        Resource resource =  metadataReader.getResource();
        System.out.println("類的路徑:"+classMetadata.getClassName());
        if(classMetadata.getClassName().contains("dao")) {
            return true;
        }
        return false;
    }
}

組合使用

將excludeFilters與includeFilters組合使用。

@ComponentScan(basePackages = {"com.blackcat.annotation.componentscan"},
    excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class})},
    includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,value = Repository.class)
})
@Configuration
public class MyConfig {

}

@Bean的屬性支援

@Scope

設定Spring容器如何新建Bean例項(方法上,得有@Bean) 作用域 。在不指定@Scope的情況下,所有的bean都是單例項的bean,而且是餓漢載入(容器啟動例項就建立好了)。

其設定型別包括:Singleton,Protetype,Request(無程式碼示例) ,Session (無程式碼示例) ,GlobalSession(無程式碼示例) 

    @Bean
    @Scope(value = "singleton")
    public User user4(){
        System.out.println("容器開始建立bean.........");
        return new User();
    }

Singleton

單例項的(預設) 全域性有且僅有一個例項。

Protetype

表示為多例項的,每次獲取Bean的時候會有一個新的例項,而且還是懶漢模式載入(IOC容器啟動的時候,並不會建立物件,而是 在第一次使用的時候才會建立)。

每次連線請求,都會生成一個bean例項,也會導致一個問題,當請求數越多,效能會降低,因為建立的例項,導致GC頻繁,gc時長增加。

request

同一次請求 ,表示該針對每一次HTTP請求都會產生一個新的bean,同時該bean僅在當前HTTP request內有效  (web階段時使用,無示例)。

session

同一個會話級別,表示該針對每一次HTTP請求都會產生一個新的bean,同時該bean僅在當前HTTP session內有效  (web階段時使用,無示例)。

globalsession

全域性session中的一般不常用。給每一個 global http session新建一個Bean例項  類似於標準的HTTP Session作用域,不過它僅僅在基於portlet的web應用中才有意義 。

@PostConstruct

由JSR-250提供,在建構函式執行完之後執行,等價於xml配置檔案中bean的initMethod,用於指定初始化方法  標註在方法上方,該方法在建構函式執行完成之後執行。

被@PostConstruct修飾的方法會在伺服器載入Servle的時候執行,並且只會被伺服器執行一次。

伺服器載入:Servlet -> servlet 建構函式的載入 -> postConstruct ->init(init是在service 中的初始化方法. 建立service 時發生的事件.) ->Service->destory->predestroy->伺服器解除安裝serlvet。

如果想在生成物件時候完成某些初始化操作,而偏偏這些初始化操作又依賴於依賴注入,那麼就無法在建構函式中實現。為此,可以使用@PostConstruct註解一個方法來完成初始化,@PostConstruct註解的方法將會在依賴注入完成後被自動呼叫。

public class User {
    private String name="zhang";

    public void init(){
        System.out.println("init");
    }

    public void destroy(){
        System.out.println("destroy");
    }

    @PostConstruct
    public void PostConstruct(){
        System.out.println("@PostConstruct將在依賴注入完成後被自動呼叫");
    }
}

@PreDestroy

由JSR-250提供,在Bean銷燬之前執行,等價於xml配置檔案中bean的destroyMethod,用於指定銷燬方法(用在方法上)  摧毀註解 預設 單例  啟動就載入 。

伺服器載入:Servlet -> servlet 建構函式的載入 -> postConstruct ->init(init是在service 中的初始化方法. 建立service 時發生的事件.) ->Service->destory->predestroy->伺服器解除安裝serlvet。

根據上個註釋程式碼,就不過多貼上。

@PreDestroy
public void PreDestroy(){
    System.out.println("XXX 正在被容器刪除");
}

@Lazy

用於標識bean是否需要延遲載入。主要針對單例項的bean 容器啟動的時候,不建立物件,在第一次使用的時候才會建立該物件。

    @Bean
    @Lazy
    public User user4(){
        System.out.println("容器開始建立bean.........");
        return new User();
    }

@Primary

自動裝配時當出現多個Bean候選者時,被註解為@Primary的Bean將作為首選者。

該示例請看:com.blackcat.annotation.autowired包下的程式碼。


@Configuration
@ComponentScan(basePackages = "com.blackcat.annotation.autowired")
public class MyConfig {

@Primary
@Bean
public UserDao userDao2() {
UserDao userDao = new UserDao();
userDao.setFlag(2);
return userDao;
}

@Bean
public UserDao userDao() {
UserDao userDao = new UserDao();
userDao.setFlag(1);
return userDao;
}
}
@Service
public class BaiDuService {

@Autowired
private UserDao userDao;

@Override
public String toString() {
return "BaiDuService{" +
"userDao=" + userDao +
'}';
}

}
public class UserDao {

    private int flag=1;

    @Override
    public String toString() {
        return "UserDao{" +
                "flag=" + flag +
                '}';
    }
}

@DependsOn

定義Bean初始化及銷燬時的順序。有很多場景需要bean B應該被先於bean A被初始化,從而避免各種負面影響。我們可以在bean A上使用@DependsOn註解,告訴容器bean B應該先被初始化。

例如:bean A 間接依賴 bean B。如Bean B應該需要更新一些全域性快取,可能通過單例模式實現且沒有在spring容器註冊,bean A需要使用該快取;因此,如果bean B沒有準備好,bean A無法訪問。

示例通過事件機制說明,釋出者和監聽者,然後通過spring配置執行。

/**
 * <p> 描述 : 事件管理類,維護監聽器列表,通過單例方法獲取事件管理器,可以增加監聽器或釋出事件。
 * @author : blackcat
 * @date  : 2020/7/31 10:03
*/
public class EventManager {

    private final List<Consumer<String>> listeners = new ArrayList<>();

    private EventManager() {
    }

    private static class SingletonHolder {
        private static final EventManager INSTANCE = new EventManager();
    }

    public static EventManager getInstance() {
        return SingletonHolder.INSTANCE;
    }

    public void publish(final String message) {
        listeners.forEach(l -> l.accept(message));
    }

    public void addListener(Consumer<String> eventConsumer) {
        listeners.add(eventConsumer);
    }
}
/**
 * <p> 描述 : 事件監聽者,可以增加監聽器。
 * @author : blackcat
 * @date  : 2020/7/31 10:04
*/
public class EventListenerBean {

    private void initialize() {
        EventManager.getInstance().
                addListener(s ->
                        System.out.println("事件監聽者 : " + s));
    }
}
/**
 * <p> 描述 : 事件釋出類,通過EventManager類釋出事件。
 * @author : blackcat
 * @date  : 2020/7/31 10:04
*/
public class EventPublisherBean {

    public void initialize() {
        System.out.println("事件釋出類 initializing");
        EventManager.getInstance().publish("event published from EventPublisherBean");
    }
}

總結

如果我們註釋掉@DependsOn("eventListener"),我們可能不確定獲得相同結果。嘗試多次執行main方法,偶爾我們將看到EventListenerBean 沒有收到事件。為什麼是偶爾呢?因為容器啟動過程中,spring按任意順序載入bean。

那麼當不使用@DependsOn可以讓其100%確定嗎?可以使用@Lazy註解放在eventListenerBean ()上。因為EventListenerBean在啟動階段不載入,當其他bean需要其時才載入。這次我們僅EventListenerBean被初始化。

EventPublisherBean initializing

現在從新增加@DependsOn,也不刪除@Lazy註解,輸出結果和第一次一致,雖然我們使用了@Lazy註解,eventListenerBean在啟動時仍然被載入,因為@DependsOn表明需要EventListenerBean。 

該示例參考文章:https://blog.csdn.net/neweastsun/article/details/78775371

宣告bean的註解

@Component

泛指元件,當元件不好歸類的時候,我們可以使用這個註解進行標註。可通過@Component("XX")宣告bean的名字,預設名稱是類名頭字母小寫。

@Component
public class MyLog {
    private String name="123456";
}
@Component("info")
public class MyInfo {
private String name="123456";
}
@Configuration
@ComponentScan(basePackages = "com.blackcat.annotation.component")
public class MyConfig {

}
public static void main(String[] args) {
        // 容器中讀取Bean
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyConfig.class);
        System.out.println(ctx.getBean(MyLog.class));
        System.out.println(ctx.getBean("info"));
    }

@Service

在業務邏輯層使用(service層),對應的是業務層Bean。用於標註業務層元件。可通過@Service("XX")宣告bean的名字,預設名稱是類名頭字母小寫。

public interface UserService {
.....
}
@Service
public class UserServiceImpl implements UserService {
.....
}
// 注入userService
@Resourceprivate UserService userService;

@Repository

在資料訪問層使用(dao層),對應資料訪問層Bean,用於標註資料訪問元件,即DAO元件。可通過@Repository("XX")宣告bean的名字,預設名稱是類名頭字母小寫。

public interface BaseDao<T> {
}
@Repository("userDao")
public class UserDaoImpl implements BaseDao<User> {
}
// 注入userDao
@Resource(name = "userDao")
private BaseDao<User> userDao;

@Controller

在展現層使用,控制器的宣告。@Controller對應表現層的Bean,也就是Action。

@Controller
public class UserController {
}

注入bean的註解

@Autowired

由Spring提供。預設按型別裝配。

自動裝配首先時按照型別進行裝配,若在IOC容器中發現了多個相同型別的元件,那麼就按照 屬性名稱來進行裝配。
例如:現在容器中有兩個userDao型別的元件,一個叫userDao 一個叫userDao2,我們通過@AutoWired 來修飾的屬性名稱時userDao,就載入容器的userDao元件,若屬性名稱為 userDao2 那麼他就載入的時userDao2元件。

public class UserDao {
    private int flag=1;
    @Override
    public String toString() {
        return "BaseDao{" +
                "flag=" + flag +
                '}';
    }
}
@Configuration
@ComponentScan(basePackages = "com.blackcat.annotation.autowired")
public class MyConfig {
    @Bean
    public UserDao userDao2() {
        UserDao userDao = new UserDao();
        userDao.setFlag(2);
        return userDao;
    }

    @Bean
    public UserDao userDao() {
        UserDao userDao = new UserDao();
        userDao.setFlag(1);
        return userDao;
    }

    @Bean(autowire = Autowire.BY_NAME)
    public UserService userService() {
        return new UserService();
    }
}
public class UserService {
    private UserDao userDao;
    @Override
    public String toString() {
        return "UserService{" +
                "userDao=" + userDao +
                '}';
    }
}

 

如果我們想使用按名稱裝配,可以結合@Qualifier註解一起使用, @Autowired @Qualifier("XX") 存在多個例項配合使用。

@Service
public class BaiDuService {

    @Qualifier("userDao")
    @Autowired
    private UserDao userDao2;

    @Override
    public String toString() {
        return "BaiDuService{" +
                "userDao=" + userDao2 +
                '}';
    }

}

@Autowired(required = false) 的意思是裝配的上就裝,裝不上就不裝。

示例:@Qualifier("userDao3") 容器中即沒有userDao3也沒有userDao2,那麼在裝配的時候就會丟擲異常。若我們想不拋異常 ,我們需要指定 required為false的時候可以了。

 

 

@Autowired 可標註在set方法上或構造方法上。

public class MyAspect {

    private MyLog myLog;

    @Override
    public String toString() {
        return "MyAspect{" +
                "myLog=" + myLog +
                '}';
    }

    /**
     * <p> 描述 : 標註在set方法上
     * @author : blackcat
     * @date  : 2020/5/26 14:56
     */
    //@Autowired
    public void setMyLog(MyLog myLog) {
        this.myLog = myLog;
    }

    /**
     * <p> 描述 : 標註在構造方法上
     * @author : blackcat
     * @date  : 2020/5/26 14:56
    */
    @Autowired
    public MyAspect(MyLog myLog) {
        this.myLog = myLog;
    }
} 
@Component
@ToString
public class MyLog {

}

@Autowired 也可標註在配置類上的入參中(可以不寫@Autowired)。

    @Bean
    public MyAspect myAspect(@Autowired MyLog myLog) {
        MyAspect myAspect = new MyAspect(myLog);
        return myAspect;
    }

@Inject

由JSR-330提供。需要匯入jar包依賴。

        <!--JSR330規範-->
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>    
@Service
public class UserService {

    /**
     * 需要匯入jar包依賴
     * 支援@Primary功能 ,但是沒有Require=false的功能
     */
    @Inject
    private UserDao userDao;

    @Override
    public String toString() {
        return "UserService{" +
                "userDao=" + userDao +
                '}';
    }
}

@Resource

由JSR-250提供。預設按名稱裝配,當找不到與名稱匹配的bean才會按型別裝配。

@Service
public class UserService {

    /**
     * 功能和@AutoWired的功能差不多一樣,但是不支援@Primary 和 @Qualifier的支援
     */
    @Resource
    private UserDao userDao;

    @Override
    public String toString() {
        return "UserService{" +
                "userDao=" + userDao +
                '}';
    }
}

@Value註解

/**
 * <p> 描述 : Value註解
 * @author : blackcat
 * @date  : 2020/5/25 17:45
*/
@Data
@Component
public class User {

    /** 注入普通字元 */
    @Value("cat")
    private String userName;

    /** spel方式來賦值 */
    @Value("#{28-3}")
    private Integer age;

/** * 注入作業系統屬性 * 需要有系統配置檔案類SystemProperties */ @Value("#{systemProperties['os.name']}") private String osName; /** 注入表示式結果 */ @Value("#{ T(java.lang.Math).random() * 100 }") private String randomNumber; /** * 注入配置檔案 * 1.編寫配置檔案 test.properties * 2.載入配置檔案類@PropertySource */ @Value("${book.name}") private String book; /** 注入網站資源 */ @Value("http://www.baudu.com") private Resource url; }
/**
 * <p> 描述 :
 * @author : blackcat
 * @date  : 2020/5/25 17:45
*/
@Configuration
@PropertySource(value = {"classpath:test.properties"})// 指定外部檔案的位置
public class MyConfig {

    @Bean
    public User user() {
        return new User();
    }
}

test.properties

book.name=電子書

條件註解

以@ConditionalOnXX 為SpringBoot註解。瞭解完Conditional註解的原理之後,就方便了解ConditionalOnXX註解的原理了。

@Conditional

它的作用是按照一定的條件進行判斷,滿足條件給容器註冊bean。

@Conditional的定義:

//此註解可以標註在類和方法上
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME) 
@Documented
public @interface Conditional {
    Class<? extends Condition>[] value();
}

 

從程式碼中可以看到,需要傳入一個Class陣列,並且需要繼承Condition介面。

示例:現在有兩個元件MyAspect,MyLog。現在有個條件判斷,當容器內沒有MyAspect就不注入MyLog。

public class MyAspect {
    public MyAspect() {
        System.out.println("MyAspect元件");
    }
}
public class MyLog {
    public MyLog() {
        System.out.println("MyLog元件");
    }
}

條件判斷類MyCondition,需要實現Condition介面,並重寫方法來自定義match規則。

/**
 * <p> 描述 :自定義條件判斷
 *
 * @author : blackcat
 * @date : 2020/5/24 14:05
 */
public class MyCondition  implements Condition {

    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        //判斷容器中是否有MyAspect的元件
        if(conditionContext.getBeanFactory().containsBean("myAspect")) {
            return true;
        }
        return false;
    }
}

從下面的執行結果可以看出,當MyAspect注入成功時,MyLog注入成功。而MyAspect沒注入成功,MyLog也沒有注入成功。

條件的判斷是根據MyCondition類中判斷,判斷MyAspect是否存在。

標註在方法上:

一個方法只能注入一個bean例項,所以@Conditional標註在方法上只能控制一個bean例項是否注入。

標註在類上:

一個類中可以注入很多例項,@Conditional標註在類上就決定了一批bean是否注入。

多個條件類:

前言中說,@Conditional註解傳入的是一個Class陣列,存在多種條件類的情況。

第一個條件類實現的方法返回true,第二個返回false,則結果false,不注入進容器。

第一個條件類實現的方法返回true,第二個返回true,則結果true,注入進容器中。

@ConditionalOnBean

當給定的在bean存在時,則例項化當前Bean。

@Data
public class City {
    /** 城市名稱 */
    private String cityName;
    /** 城市code */
    private Integer cityCode;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class People {
    /** 姓名 */
    private String name;
    /** 年齡 */
    private Integer age;
    /** 城市資訊 */
    private City city;
}

@ConditionalOnMissingBean

當給定的在bean不存在時,則例項化當前Bean。

@ConditionalOnClass

當給定的類名在類路徑上存在,則例項化當前Bean。原理同@ConditionalOnBean差不多。City2並不存在,所以People沒有注入成功。

@ConditionalOnMissingClass

當給定的類名在類路徑上不存在,則例項化當前Bean。結果同ConditionalOnClass相反。City2並不存在,所以People注入成功。

@ConditionalOnExpression

基於SpEL表示式作為判斷條件。當括號中的內容為true時,使用該註解的類被例項化。

@ConditionalonalOnJava

基於JVM版本作為判斷條件。只有執行指定版本的 Java 才會載入 Bean。

    /**
     * 只有執行指定版本的 Java 才會載入 Bean
     */
    @ConditionalOnJava(JavaVersion.EIGHT)
    @Bean
    public People people() {
        return new People();
    }

@ConditionalOnJndi

在JNDI存在的條件下查詢指定的位置。只有指定的資源通過 JNDI 載入後才載入 bean。JNDI(Java Naming and Directory Interface,Java命名和目錄介面)。

    @ConditionalOnJndi("java:comp/env/foo")
    @Bean
    public People people() {
        return new People();
    }

@ConditionalOnWebApplication

當前專案是web專案的情況下。只有執行在 web 應用裡才會載入這個 bean。

    @ConditionalOnWebApplication
    @Bean
    public People people() {
        return new People();
    }

@ConditionalOnNotWebApplication

當前專案不是web專案的條件下。與@ConditionalOnWebApplication相反,在非 web 環境才載入 bean。

    @ConditionalOnNotWebApplication
    @Bean
    public People people() {
        return new People();
    }

@ConditionalOnResource

類路徑是否有指定的值。如果我們要載入的 bean 依賴指定資源是否存在於 classpath 中,那麼我們就可以使用這個註解。

@ConditionalOnSingleCandidate

當指定Bean在容器中只有一個,後者雖然有多個但是指定首選的Bean。

只有指定類已存在於 BeanFactory 中,並且可以確定單個候選項才會匹配成功 BeanFactory 存在多個 bean 例項,但是有一個 primary 候選項被指定(通常在類上使用 @Primary 註解),也會匹配成功。

沒有寫示例,額.....因為我還不會。有會的人可以告訴一下。我學習一下後面再補上。

方法原註釋:

@Conditional,僅當指定類的bean已包含在BeanFactory中並且可以確定單個候選時匹配。如果BeanFactory中已經包含多個匹配的bean例項,但是已經定義了一個主候選例項,那麼這個條件也將匹配;實際上,如果一個bean與定義的型別自動連線成功,那麼條件匹配。

該條件只能匹配到目前為止由應用程式上下文處理的bean定義,因此,強烈建議僅在自動配置類上使用此條件。如果候選bean可能由另一個自動配置建立,請確保使用此條件的bean在之後執行。

非同步相關

@Async

在實際執行的bean方法使用該註解來申明其是一個非同步任務(方法上或類上所有的方法都將非同步,需要@EnableAsync開啟非同步任務)。

@Async 必須不同類間呼叫: A類--》B類.C方法()(@Async註釋在B類/方法中),如果在同一個類中呼叫,會變同步執行,例如:A類.B()-->A類.@Async C(),

原因是:底層實現是代理對註解掃描實現的,B方法上沒有註解,沒有生成相應的代理類。

@Configuration
@EnableAsync
public class ThreadPoolConfig {

    private static int corePoolSize=30;

    private static int maxPoolSize=100;

    private static int queueCapacity=100;

    private static int keepAliveSeconds=300;

    @Bean
    public TaskExecutor jobExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 設定核心執行緒數
        executor.setCorePoolSize(corePoolSize);
        // 設定最大執行緒數
        executor.setMaxPoolSize(maxPoolSize);
        // 設定佇列容量
        executor.setQueueCapacity(queueCapacity);
        // 設定執行緒活躍時間(秒)
        executor.setKeepAliveSeconds(keepAliveSeconds);
        // 設定預設執行緒名稱
        executor.setThreadNamePrefix("async-job-thread-");
        // 設定拒絕策略rejection-policy:當pool已經達到max size的時候,如何處理新任務 CALLER_RUNS:不在新執行緒中執行任務,而是有呼叫者所在的執行緒來執行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任務結束後再關閉執行緒池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        return executor;
    }

    @Bean
    public AsyncBean asyncBean(){
        return new AsyncBean();
    }

}
public class AsyncBean {

    /**
     * Async 必須不同類間呼叫: A類--》B類.C方法()(@Async註釋在B類/方法中),
     * 如果在同一個類中呼叫,會變同步執行,例如:A類.B()-->A類.@Async C()
     *
     * 如果在同一個類中呼叫,會變同步執行
    */
    @Async("jobExecutor")
    public void asyncMethod() throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+"非同步執行");
        Thread.sleep(1000);
    }

    public static void main(String[] args) throws InterruptedException {
        /** 如果在同一個類中呼叫,會變同步執行 */
        System.out.println(Thread.currentThread().getName()+"主執行緒請求非同步執行asyncMethod");
        AsyncBean asyncBean = new AsyncBean();
        asyncBean.asyncMethod();
        System.out.println(Thread.currentThread().getName()+"主執行緒請求非同步執行syncMethod結束");
    }
}

@Enable*註解說明

這些註解主要用來開啟對xxx的支援。 此處沒有程式碼示例。

@EnableAspectJAutoProxy

開啟對AspectJ自動代理的支援。

@EnableAsync

開啟非同步方法的支援。@EnableAsync 開啟spring非同步執行器,類似xml中的task標籤配置(其實是一樣的,如果同時存在還會報錯),需要聯合@Configuration註解一起使用 。

@EnableScheduling

開啟計劃任務的支援。

@EnableWebMvc

開啟Web MVC的配置支援。

@EnableConfigurationProperties

開啟對@ConfigurationProperties註解配置Bean的支援。

@EnableJpaRepositories

開啟對SpringData JPA Repository的支援。

@EnableTransactionManagement

開啟註解式事務的支援。

@EnableCaching

開啟註解式的快取支援。

定時任務相關

@EnableScheduling和@Scheduled為SpringBoot註解。這裡給出示例程式碼片段,示例程式碼中並未有示例。

@EnableScheduling

在配置類上使用,開啟計劃任務的支援(類上)。

@SpringBootApplication
@EnableScheduling //開啟定時任務
public class MainApplication {

    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }
}

@Scheduled

來申明這是一個任務,包括cron,fixDelay,fixRate等型別(方法上,需先開啟計劃任務的支援)。

@Component
public class Jobs {
    //表示方法執行完成後5秒
    @Scheduled(fixedDelay = 5000)
    public void fixedDelayJob() throws InterruptedException {
        System.out.println("fixedDelay 每隔5秒" + new Date());
    }

    //表示每隔3秒
    @Scheduled(fixedRate = 3000)
    public void fixedRateJob() {

        System.out.println("fixedRate 每隔3秒" + new Date());
    }

    //表示每天8時30分0秒執行
    @Scheduled(cron = "0 0,30 0,8 ? * ? ")
    public void cronJob() {
        System.out.println(new Date() + " ...>>cron....");
    }
}

環境切換

@Profile

通過設定Environment的ActiveProfiles來設定當前context需要使用的配置環境。

package com.blackcat.annotation.profiles.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;
import org.springframework.util.StringValueResolver;

import javax.sql.DataSource;

/**
 * <p> 描述 : 通過@Profile註解 來根據環境來啟用標識不同的Bean
 * @author : blackcat
 * @date  : 2020/5/25 17:45
 *
 * Profile標識在類上,那麼只有當前環境匹配,整個配置類才會生效
 * Profile標識在Bean上 ,那麼只有當前環境的Bean才會被啟用
 * 沒有標誌為Profile的bean 不管在什麼環境都可以被啟用
*/
@Configuration
@PropertySource(value = {"classpath:ds.properties"})
public class MyConfig  implements EmbeddedValueResolverAware {

    @Value("${ds.username}")
    private String userName;

    @Value("${ds.password}")
    private String password;

    private String jdbcUrl;

    private String classDriver;

    @Override
    public void setEmbeddedValueResolver(StringValueResolver stringValueResolver) {
        this.jdbcUrl = stringValueResolver.resolveStringValue("${ds.jdbcUrl}");
        this.classDriver = stringValueResolver.resolveStringValue("${ds.classDriver}");
    }

    // 標識為測試環境才會被裝配
    @Bean
    @Profile(value = "test")
    public DataSource testDs() {
        return buliderDataSource(new DruidDataSource());
    }

    // 標識開發環境才會被啟用
    @Bean
    @Profile(value = "dev")
    public DataSource devDs() {
        return buliderDataSource(new DruidDataSource());
    }

    // 標識生產環境才會被啟用
    @Bean
    @Profile(value = "prod")
    public DataSource prodDs() {
        return buliderDataSource(new DruidDataSource());
    }

    private DataSource buliderDataSource(DruidDataSource dataSource) {
        dataSource.setUsername(userName);
        dataSource.setPassword(password);
        dataSource.setDriverClassName(classDriver);
        dataSource.setUrl(jdbcUrl);
        return dataSource;
    }
}

切面相關注解

Spring支援AspectJ的註解式切面程式設計。這裡只寫了示例程式碼,沒有controller呼叫過程。

@Aspect 

宣告一個切面(類上) 使用@After、@Before、@Around定義建言(advice),可直接將攔截規則(切點)作為引數。

@PointCut

宣告切點 在java配置類中使用@EnableAspectJAutoProxy註解開啟Spring對AspectJ代理的支援(類上)。

Pointcut是植入Advice的觸發條件。每個Pointcut的定義包括2部分,一是表示式,二是方法簽名。方法簽名必須是 public及void型。可以將Pointcut中的方法看作是一個被Advice引用的助記符,因為表示式不直觀,因此我們可以通過方法簽名的方式為 此表示式命名。因此Pointcut中的方法只需要方法簽名,而不需要在方法體內編寫實際程式碼。

@Around

屬於環繞增強,能控制切點執行前,執行後,,用這個註解後,程式拋異常,會影響@AfterThrowing這個註解。

@AfterReturning

切點方法返回後執行。後置增強,相當於AfterReturningAdvice,方法正常退出時執行。

@Before

標識一個前置增強方法,相當於BeforeAdvice的功能。在切點方法之前執行。

@AfterThrowing

切點方法拋異常執行。異常丟擲增強,相當於ThrowsAdvice。

@After

在切點方法之後執行。 @Before 在方法執行之前執行(方法上) @Around 在方法執行之前與之後執行(方法上)。final增強,不管是丟擲異常或者正常退出都會執行。

程式碼

package com.blackcat.annotation.aspect.component;

import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * <p> 描述 : aop通知註解
 * @author : blackcat
 * @date  : 2020/8/3 16:30
*/
@Aspect
@Component
public class UserAspect {
    /**
     * @Pointcut 定義一個切點,避免重複引用
     */
    @Pointcut("execution(* com.blackcat.annotation.aspect.component.UserServiceImpl.test(..))")
    public void print(){}

    @Before("print()")
    public void before(){
        System.out.println("我是前置通知");
    }

    @After("print()")
    public void After(){
        System.out.println("我是後置通知");
    }

    @AfterReturning("print()")
    public void AfterReturning(){
        System.out.println("我是返回通知");
    }

    @AfterThrowing("print()")
    public void AfterThrowing(){
        System.out.println("我是異常通知");
    }

}
public interface  UserService {
    void test();
}
@Service
public class UserServiceImpl implements UserService  {

    @Override
    public void test() {
        System.out.println("測試");
    }
}
@Controller
public class TestAction {

    @Autowired
    private UserService userService;

    @RequestMapping("/test.do")
    public String info(HttpServletRequest request, HttpServletResponse response){
        userService.test();
        return "index";
    }
}

當呼叫  /test.do  時,會列印一下內容。

 本節程式碼示例參考:https://blog.csdn.net/qq_34775355/article/details/88431247

示例程式碼

https://gitee.com/kylin_lawliet/learn-spring

 

相關文章