spring編程框架

spring boot, spring data, spring framework

spring / spring boot

@Profile('prod'|'dev'|'other')(伴隨@Bean)特定profile下的bean (config: spring.profiles.active=prod, application-prod.properties)
@Profile('dev') 開發模式
@Profile('prod') 生產模式html

@Configuration
class Beans {
    @Bean
    @Profile("dev")
    public SomeType devSomeType() {
        return new SomeType("in dev");
    }
    @Bean
    @Profile("prod")
    public SomeType prodSomeType(){
        return new SomeType("in prod");
    }
}

SpEL:spring expression language.java

@Transient 此字段不作持久化(必須放在字段上,不能getter)mysql

@Field('name')定義數據庫中的字段名(默認爲java類字段名)web

@Id會將該字段映射到_id,所以@Field("name")不起做用。mongorepo中findbyid始終在_id上查找。ajax

@ConfigurationPropertiesspring

@ConfigurationProperties(prefix="a.b.x") for class/method
這裏的property應理解爲java類的property:field+getter+setter,註解功能是指根據類字段名字找到配置文件中對應key的值來自動注入到字段,找key時使用@ConfigurationProperties中的prefix指定的字符串做爲前綴。sql

全部getXXX(不管何種訪問權限)的方法默認都被認爲有對應綁定配置(尤爲對於返回非基本類型的getXXX方法,會認爲有綁定的內嵌屬性,若是實際配置中沒有,則spring報錯)。數據庫

設定@ConfiguraionProperties標記類的字段默認值??以字段初始化方式實現。express

@ConfigurationProperties標記的類中不要使用@Value標記字段。編程

@ConfigurationProperties支持嵌套類,嵌套類字段對應的配置名是下級結構。

@ConfigurationProperties("a")
class A {
    int num;    // a.num
    Host host;  //
}
static Host {
    String host;    // a.host
    int port;       // a.port
}

可否標記interface,如JPA中的自聲明的Repo,那麼該Repo在被自動提供實現時相關的被標記@ConfigurationProperties的屬性會使用自聲interface上的@ConfigurationProperties的prefix嗎? 不能。(jpa repo中的數據源配置前綴的指定須要在定義數據源bean時指定)

@ConfigProperties和@Value被設計爲無關聯的,也就說@ConfigProperties中的prefix對@Value標記的字段值的查找不起做用。

@ConfigurationProperties還可標記到方法上,此時,簡單看來,該方法中(直接或間接用到的)被標記了@ConfigurationProperties的class的前綴將被方法上@ConfigurationProperties中的prefix參數覆蓋。
具體地,@ConfigurationProperties標記的方法(該標記中的prefix姑且稱爲此方法中的上下文配置前綴)在運行時返回的對象的實際類型必須是被標記了@ConfigurationProperties的類的實例(同時也是編譯期/源代碼聲明類型或子類),以後spring boot會根據方法的上下文配置前綴及配置類字段名讀取spring環境配置值,爲返回的實例對象設置字段值,僅設置對應的配置鍵已存於上下文配置環境的字段(也就說對於對應配置缺失的java類字段,其初始化值或者方法返回實例前被設置的值均可以生效,而不是被spring設爲等效0/null,能夠達到設置@ConfigurationProperties配置類字段默認值的目的)。

@Configuration
public class C {
    @Bean("anothorDatasource")
    @ConfigurationProperties("druid.second")
    // 更改了DruidDataSource默認的配置前綴
    public DataSource secondData() {
                return DruidDataSourceBuilder.create().build();
    }
}

//TODO @ConfiguratioProperties標註到返回POJO的方法上,POJO類沒有任何註解,也意味着沒有@ConfigurationProperties及@Value等spring boot註解。

與之相關的InitializingBean接口(惟一方法void afterPropertiesSet()),實現了該接口的@ConfigurationProperties配置類會在spring根據配置設置完字段值後被調用接口方法。

@ConfigurationProperties配置類的字段被標記了@NotNull時不容許配置環境中缺失該配置鍵。

@Component('bean-name') <--> context.getBean('name')

@SpringBootApplication // spring boot main

@Bean("name", initMethod="init")定義bean和初始化方法,在構建完對象實例後須要調用initMethod指定的初始化方法。

@Bean, @Component, @Service, @Repository都是定義bean,只是表達應用場景不一樣,標記type時使用type名做爲bean名字。

@Bean結合@Scope('singlton'|'prototype'),定義bean爲單例或者非單例。

@Service 提供interface的實現。 註解到interfac的implementation上(interface無需其餘註解),使用對應接口時,在字段上標註@Autowried,spring提供對應實現。

@Bean on method, 默認使用方法名做爲bean名字。

@Bean(name="beanId") ; @Autowired @Qualifier("beanId")
在字段上使用@Qualifier時尤爲注意同時須要@Autowired,@Qualifier不蘊含@Autowired,若是上文中沒用@Autowired語境(@Bean定義蘊含@Autowired、自動調用的構造函數須要標註@Autowired也就蘊含@Autowired等),則需額外加上@Autowired。

spring相關注解的參數是否能夠是SPEL運行時值,如@Qualifier("${beanNameFromConfigFile}")。 不能。就@Qualifier而言,其參數被直接認爲是bean名字字面量。

@Configuration (for class),標記包含了若干bean定義的類。類中通常用@Bean標記方法以定義一個bean。結合@ComponentScan('package-to-scan'),定義須要掃描的包。

@Configuration用於bean定義容器,不要與@ConfigurationProperties同時使用。

@PropertySource('resource-path'),指定接下來的值搜索資源,僅支持.property文件,不支持.yml。(如@Value查找的配置資源)。

for field/parameters
@Value('value literal')
@Value("${conf-key-name}") 配置值
@Value("${placeholder}")這種形式在配置未定義時不返回null,而是拋出異常,要得到未配置時返回null的功能經過提供默認值實現@Value("${key:#{null}}")
@Value("${key:defaultVal}") 配置值,提供默認值
@Value("SpEL-expression") 其餘SpEL表達式
expression中可使用${key}獲取property
@Value("#{systemProperties['os.name']}") 系統屬性(獲取系統屬性另可考慮@Autowired Environment env;)
@Value("#{T(java.lang.Math).random()*10}") 帶java表達式的值
@Value("#{someBean.someField}") 對象字段值
@Value("resource-path") for type Resource/String/InputStream,對String類型註解時會讀取資源內容;注入到InputStream/Stream時,若是文件資源不存在會致使異常,注入Resource時可根據Resource.exists()判斷資源是否存在。

@Value('http://xxx'|'classpath:'|'file:')

@Value讀取配置值時是否能夠指定配置文件???

@Bean(initMethod='', destryMethod='')

@PostConstruct 註解方法爲構造器後調用的初始化方法(init-method)

@PreDestroy destroy方法,bean銷燬以前調用(destroy-method)

@Import(X.class) 將類注入spring容器

能夠注入Set,List
提供默認值null:@Value("${key:#{null}}");
提供默認集合:@Value("${key:#{{'a','b'}}}"),注意嵌套的花括號,#{}表示其中的內容是SpEL,內嵌的花括號{}表示這是一個內聯字面量集合/列表。(拉倒吧,根本無法成功爲@Value標記的集合注入值,測試環境:spring boot 2.0, .yml, .properties文件)

yaml中配置對於@Value注入的集合值!!!沒法成功(spring-boot版本2.0, spring 5.0.6)!!!,不管值格式配爲[x,y]、x,y、 - x <換行> - y的形式均沒法成功。如配置爲「x,y」(不含引號)形式時,獲得的是一個只包含一個元素的List/Set,該元素是字符串「x,y」;配置爲「[x,y]」(不含引號)形式時使用了默認值(事實上以值以方括號開始時甚至不能被注入爲String);配置爲「- x <換行> - y」時也會使用默認值。

.properties定義也如此,不能以逗號拼接的串注入集合(.properties定義列表的格式 key[0]=val1 <換行> key[1]=val2)

逗號拼接串可注入數組類型(String[], Double[]之類)。

注入List只在@ConfigurationProperties標註的類的字段狀況下才能成功,支持靈活的配置形式,包括[x,y]、x,y、 - x - y。(需提供字段setter)

@Value("${key:#{null}}") //默認值null
Set<String> p;

@Value("${key:#{{'a','b'}}}") //默認值{a,b}
Set<String> p;

Spring boot(2.0, spring 5.0)未提供String轉java.time.Duration的內置轉換器,所以同時也沒法@Value注入Duration值。

若是須要spring容器相關資源,那將bean類聲明繼承 XXXAware(BeanNameAware, ResourceLoaderAware等)接口,實現相關setXXX方法,由spring容器調用,資源做爲實參提供。

併發、Executor、多線程、異步方法:
@EnableAsync for class, @Async for class(for all class methods)/method, implements AsyncConfigurer, TaskExecutor, ThreadPoolTaskExecutor.
方法異步執行行爲定義在一個非final的public方法上,經過標註@Async完成,spring經過繼承方法所在類以及用Executor異步執行目標方法來實現。目標方法應返回void或者Future,返回Future狀況應用在須要取得異步執行結果的或控制異步執行的場景,在方法定義中,經過用new AsyncResult<>(...)(該類繼承自Future)包裝方法執行結果數據以返回Future。

@Component
public class MyAsyncTask {
    @Async
    public void doItAsyncIgnoreResult() {   // 不關心返回結果
        System.out.println("done");
    }
    
    @Async
    public Future<Double> doHeavyComputation(double s) {
        //利用輸入異步進行復雜運算,返回一個結果
        double r = 0;
        ...     //複雜計算
        return new AsyncResult<>(r);
    }
}

注意:對異步方法的類的字段的讀取,不能直接經過.<field>獲取,必定須要經過方法獲取(getter),不然沒法獲取到值,這是由於spring注入了生成代理子類後多態問題致使。

計劃任務:
@Service for class, then @Scheduled(fixRate= | cron='unix cron format') for method; @EnableScheduling;自動被初始化和調用。

@Scheduled中時間配置能夠是單位爲ms的long類型,也可配字符串,字符串能夠爲spring配置注入表達式("${xx.xx}")。時間字符串格式能夠是數值(同long類型參數),也能夠是Duration格式。

@Service
class Svc {
    @Scheduled(fixRate=5000)
    public void f(){}
    @Scheduled(fixDelayString="${s.rate}")
    public void g(){}
}
@Configuration
@ComponentScan('')
@EnableScheduling
class Cnf{}

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

@Conditional(ConditionImpl.class) 條件行爲(知足條件才建立bean,spring boot經常使用),條件接口Condition。

能夠聲明被其餘註解標註的註解(@interface),標註在註解上的註解稱爲元註解,被標註的註解稱組合註解(composite annotation),組合註解具有全部元註解功能。組合註解的參數覆蓋元註解的參數。能夠簡單理解爲一組元註解的別名。

獲取配置值的容器Environment,已被定義爲bean(能被@Autowired)。

定義bean的銷燬順序?好比某些業務bean在銷燬時須要進行數據庫操做,此時要求數據庫鏈接bean在業務bean以後關閉。 <=== spring建立bean自己記錄了依賴關係,銷燬時按建立時的依賴順序反序銷燬。

spring程序啓動時排除掃描特定類:

@ComponentScan(excludeFilters = {@ComponentScan.Filter(type = ASSIGNABLE_TYPE, value = {MyUnneccessaryConfig.class})})

spring data / spring jpa

spring boot data dependecy -> artifact: spring-boot-starter-jdbc

Spring Data是一個用於簡化數據庫訪問,並支持雲服務的開源框架。其主要目標是使得對數據的訪問變得方便快捷,並支持map-reduce框架和雲計算數據服務。 Spring Data 包含多個子項目:

  • Commons - 提供共享的基礎框架,適合各個子項目使用,支持跨數據庫持久化
  • JPA - 簡化建立 JPA 數據訪問層和跨存儲的持久層功能
  • Hadoop - 基於 Spring 的 Hadoop 做業配置和一個 POJO 編程模型的 MapReduce 做業
  • Key-Value - 集成了 Redis 和 Riak ,提供多個經常使用場景下的簡單封裝
  • Document - 集成文檔數據庫:CouchDB 和 MongoDB 並提供基本的配置映射和資料庫支持
  • Graph - 集成 Neo4j 提供強大的基於 POJO 的編程模型
  • Graph Roo AddOn - Roo support for Neo4j
  • JDBC Extensions - 支持 Oracle RAD、高級隊列和高級數據類型
  • Mapping - 基於 Grails 的提供對象映射框架,支持不一樣的數據庫
  • Examples - 示例程序、文檔和圖數據庫
  • Guidance - 高級文檔

spring jpa接口中的實體字段名幾乎都是指ORM映射以後的類字段名,如repo中的方法名關乎的字段名、Sort相關用到的字段名。

spring jpa中把提供數據庫CRUD的interface稱爲Repository,能夠定義繼承自JpaRepository<T,ID>的interface,類型參數中的T是數據庫表實體類,ID是主鍵類型,Repo標註上@Repository,spring jpa將自動生成繼承自SimpleJpaRepository的代理實現,Repo接口中方法的名字定義功能(若是在方法上無@Query等註解),方法名與功能實現的對應規則以下

  • findOneByXXX, findAllByXXX查詢數據;existsByXXX存在性檢查;deleteByXXX刪除;countByXXX計數;
  • findAllByXxxAndYyy(XXXType xxx, YYYType yyy, Pageable)定義經過字段Xxx和Yyy查詢數據,字段對應類型爲XXXType和YYYType,Pageable是分頁查詢參數,返回對象頁數據Page<>,字段Xxx是jpa java實體類的字段名,按camel case規則定義大小寫轉換方法,另可定義不帶Pageable的該方法,功能爲根據字段查詢全部知足數據,返回List。
  • findOneByXxx,根據惟一性字段(字段組合)查詢數據,返回Optional<>。
  • findAllByXxxContaining(String xxx),字符串型字段包含(部分匹配)查詢
  • findAllByXxxContainingIgnorcase(String xxx),不區分大小寫查詢。
  • deleteByXxx,根據字段刪除,須要標註@Transactional。
  • updateXXXByXXX,須要標註@Transactional。

能夠不經過方法名定義功能,使用自定義查詢。經過在接口方法上標註@Query("JPA語句"),語句能夠是查詢、更新、刪除語句,更新、刪除語句須要@Transactional支持(方法上標註該註解),更新語句還必須標註@Modifying代表操做將修改數據。語句中涉及的數據字段名是jpa實體類的字段名,不是SQL表中的字段名。在語句中引用接口方法參數的方式有兩種:一種是經過參數順序引用,?<數字>引用數字對應第n個參數(從1開始),如?1引用第一個參數;另外一種是經過綁定的名字引用,在方法參數列表中對要綁定的參數標註@Param("name"),標記爲名字"name",在@Query()中經過:<名字>引用,如利用「:name」引用經過@Param標記的名爲「name」的參數。

能否直接引用java方法參數名(未經過@Param定義綁定名)? <=== 不能夠,方法參數名在運行時自己已不存在。

能否以非基本類型(自定義類)做爲參數,在@Query中引用參數的字段(或字段getter)? <=== 利用名字綁定+SpEL,如@Query("... where id=:#{#u.uid}") int count(@Param("u") MyUser my)。

@Query中使用SpEL:@Query中引用參數時經過 ?#:#觸發SpEL方式引用機制。利用?#或:#後緊隨的花括號裹挾SpEL表達式。

findAll返回List<>類型,分頁的findAll(有Pageable參數)返回Page<>類型,findOne返回Optinal<>。自定義的findAllByXXX可定義返回List<>或流類型Stream<>,Jpa父類的findAll(無參)返回List<?>,若是想添加一個返回Stream<>功能,須要額外加方法(如Stream<> streamAll()),同時添加註解@Query("select t from EntityClass t")(由於已有List<> findAll(),且不能經過返回類型重載方法)。

使用返回類型爲Stream<>的Jpa方法時,其調用函數(caller method)須要標註@Transactional(readonly=true),在Jpa方法上標註無用。(因此對於調用函數上不能有註解,或者調用函數中有屢次調用Jpa方法而@Transactional應該只指一次事務的狀況怎麼辦呢?)

JpaRepository中的save()(以及saveXXX())用以向數據庫表中插入數據,若是存在相同數據(違反惟一性約束),將拋異常。

.save()在記錄已存在時將拋異常,那如何作insert if not exists? <=== 利用@Transactional和Repo寫代碼級的事務。(Jpa不容許自寫insert語句)

@Transactional可標註在spring管理的class的方法上(不管是本身實現的Jpa Repo class仍是非Repo class,該方法利用Jpa Repo實現事務),以實現一個數據庫事務。

在@Configuration類上,標註@EnableJpaRepositories@EntityScan,前者定義掃描Jpa Repository類(包)及相關配置,後者定義掃描的Jpa實體類(包)。
@EnableJpaRepositories(basePackageClasses = SomeoneJpaRepo.class(定義掃描的包),entityManagerFactoryRef="beanName",transactionManagerRef="beanName"),entityManagerFactoryRef和transactionManagerRef用於自定義EntityManager和TransactionManager,自定義DataSource的bean須要用到這兩個配置。
@EntityScan(basePackageClasses = SomeoneJpaEntity.class(定義掃描的包)
Jpa實體類需標註@Entity,並經過@Table(name="",indexes={@Index})映射表信息(表名、索引、數據約束等)。(對應SQL數據表定義應提早在數據庫中完成,Jpa實體類中的字段名、索引等定義只用於支持框架jdbc查詢,不用於建立數據庫。)

示例

//spring @Configuration類
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackageClasses = UserEntityTrackingService.class, entityManagerFactoryRef = "el.tracking.entityManagerFactory", transactionManagerRef = "el.tracking.transactionManager")
@EntityScan(basePackageClasses = UserEntityTracking.class)
public class MyJpaConfig {
    @Bean("el.tracking.datasourceRef")
    @ConfigurationProperties(prefix = "el.tracking.datasource")
    public DataSource elTrackingDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean("el.tracking.entityManagerFactory")
    public LocalContainerEntityManagerFactoryBean elTrackingEntityManagerFactory(@Qualifier("el.tracking.datasourceRef") DataSource elTrackingDataSource,
                                                                                 EntityManagerFactoryBuilder builder,
                                                                                 JpaProperties jpaProperties) {
        return createEntityManagerFactoryBean(elTrackingDataSource, builder, jpaProperties);
    }
    
    @Bean("el.tracking.transactionManager")
    public PlatformTransactionManager elTrackingTransactionManager(@Qualifier("el.tracking.entityManagerFactory") EntityManagerFactory elEntityManagerFactory) {
        return new JpaTransactionManager(elEntityManagerFactory);
    }
    
    private static LocalContainerEntityManagerFactoryBean createEntityManagerFactoryBean(DataSource dataSource, EntityManagerFactoryBuilder entityManagerFactoryBuilder, JpaProperties jpaProperties) {
    return entityManagerFactoryBuilder
            .dataSource(dataSource)
            .properties(jpaProperties.getHibernateProperties(new HibernateSettings()))
            .packages(UserEntityTracking.class) //設置實體類所在位置
            .persistenceUnit("defaultPersistenceUnit")  //任意名字
            .build();
    }
}
//jpa實體類
@Entity
@Table(name = TableName,
        indexes = {@Index(columnList = UserId), @Index(columnList = UserId + "," + EntityId, unique = true)})
public class UserEntityTracking {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)    // for mysql, IDENTITY is ok,AUTO will not, while, the latter is the default
            Long id;
    // uk: (userId+entityId)
    @Column(name = UserId, nullable = false)
    String userId;
    @Column(name = EntityId, nullable = false)
    String entityId;
    @Column(name = EntityName, nullable = false)
    String entityName;
    // getter/setter's follow here
}
//數據庫表名、字段名常量
public interface UserEntityTrack {
    String TableName = "user_entity_track";
    String UserId = "user_id";
    String EntityId = "entity_id";
    String EntityName = "entity_name";
}
//Repo類
@Repository
public interface UserEntityTrackingService extends JpaRepository<UserEntityTracking, Long> {
    long countByUserId(String userId);
    boolean existsByUserIdAndEntityId(String userId, String entityId);
    Page<UserEntityTracking> findAllByUserId(String userId, Pageable pageable);
    List<UserEntityTracking> findAllByUserId(String userId);
    Optional<UserEntityTracking> findOneByUserIdAndEntityId(String userId, String entityId);
    Page<UserEntityTracking> findAllByUserIdAndEntityNameContainingIgnoreCase(String userId, String entityName, Pageable pageable);

    //@Query("select t.entityName from UserEntityTracking t where t.userId=?1 and t.entityId=?2")
    //List<UserEntityTracking> myFindAll(String userId, String entityId);
    
    @Transactional  // <-- Transactional
    void deleteByUserIdAndEntityId(String userId, String entityId);

    @Transactional  // <--
    @Modifying  // <--
    // maybe a better method name
    @Query("update UserEntityTracking t set t.entityName=?3 where t.userId=?1 and t.entityId=?2")
    void myUpdateName(String userId, String entityId, String entityName);
    // bound parameters
    //  @Query("update UserEntityTracking t set t.entityName=:name where t.userId=:uid and t.entityId=:eid")
    // void myUpdateName(@Param("uid")String userId, @Param("eid")String entityId, @Param("name")String entityName)
    
    @Query("select t from UserEntityTracking t")
    Stream<UserEntityTracking> streamAll(); // 不能定義Stream<> findAll();由於父類已有方法List<> findAll();
    
    //
    @Query
}


//數據庫事務
//或者在本身實現的Repo class
@Repository
public MyRepoImpl {
    @Autowired
    EntityManager entityManager;
    @Transactional
    public boolean insertIfNotExisting(String mydata) {
        if(entityManager.exists(...)) return false;
        else {
            entityManager.persist(...);
            return true;
        }
    }
}
//或者在任意spring管理的class
@Service
public class MyService {
    @Autowired
    Repo myrepo;
    @Transactional
    public boolean insertIfNotExisting(String mydata) {
        if(myrepo.exists(...)) return false;
        else {
            myrepo.save(...);
            return true;
        }
    }
}

時常須要配置或自定義RMDBS鏈接池管理類,尤爲多數據源管理場景,經過自定義EntityManager、TransactionManager實現。

PageRequest.OrderBy 與帶下劃線的類字段;Repo中帶下劃線的方法

@OneToOne @OneToMany @ManyToOne @ManyToMany

聯合表中找不到數據狀況:@NotFound(IGNORE|Exception)

spring boot

spring boot引入自動配置機制,根據條件自動定義某些bean,其觸發條件及bean定義過程定義在某個類中,通常命名爲XxxAutoConfiguration,而後在META-INF/spring.factories文件中配置該爲一種spring-boot的自動配置類,spring將會掃描該文件讀取該類,根據條件決定是否生成其中定義的bean。

在應用程序不須要某些自動配置類時,須要排除這種自動配置(好比程序無SQL庫時,咱們就不須要spring-jpa依賴包中的DataSourceAutoConfiguration,不排除自動配置類的話其將讀取配置鏈接數據,但會鏈接失敗致使程序異常),可在程序入口用註解編碼式地排除@EnableAutoConfiguration(exclude=),或@SpringBootAppliction(exclude=)。也可經過配置文件排除spring.autoconfigure.exclude: <class-name>

不建議自動注入類字段:類字段聲明同時初始化時不得使用自動注入的類字段,由於聲明初始化時標記爲自動注入的類字段實際還未被注入,應將聲明初始化分離初始化到構造函數。

spring boot程序可使用程序參數覆蓋配置文件中的配置。(java -jar xxx.jar --someKey=someVal,參數需在-jar後,也就說那是程序參數並不是jvm參數)

artifact org.spring*.boot:autoconfigure中有@ConditionalOnXXX(OnBean存在bean, Class存在類, MissingClass缺失類, Property存在配置等)的組合註解。相關文件:META-INF/spring.factories。

idea -> spring initializer-> maven project-> (web, ...) ->...

若是用gradle,可能會遇到問題,下載包,至關慢,沒發現下載到本地maven repo也不知道下載到哪兒,手動用mvn下載:
org.springframework.boot:spring-boot-dependencies:1.5.2.RELEASE:pom
org.springframework.boot:spring-boot-loader-tools:1.5.2.RELEASE
io.spring.gradle:dependency-management-plugin:1.0.0.RELEASE
……

使用spring-boot-data-jpa,須要在應用啓動器上標註@EnableJpaRepositories(basePackageClasses = XXRepo.class),爲了實體管理器管理實體類(表、索引相關),須要註冊實體類,經過@EntityScan(basePackageClasses = XX.class)實現,不然報錯Not a managed type。

表在程序執行前應存在於數據庫中。

關於主鍵的異常:com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'portal.hibernate_sequence' doesn't exist,主鍵生成策略更改成@GeneratedValue(strategy = GenerationType.IDENTITY)可解決問題。

spring boot 引入多個properties/yml文件???

@Configuration類裏不能@Autowired ConversionService。

暴露關閉程序應用的接口(優雅停機),引入依賴org.springframework.boot:spring-boot-starter-actuator,在程序配置中寫入

# spring boot 2.0之前的版本的配置鍵不同
# 默認僅info,health,不含shtudown,所以需顯式引入
management.endpoints.web.exposure.include = info, health, shutdown
# 默認未開啓,顯式開啓
management.endpoint.shutdown.enabled = true

對actuator管理端口下的/shutdown地址發送http POST請求,無需請求參數,便可提交關閉應用的請求,會收到一條跟請求者說再見的http響應消息。

maven依賴管理、打包插件pom配置(請注意其中註釋提示):

<!--pom.xml-->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring.boot.version}</version><!--2.0.0.RELEASE-->
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!--
        該配置管理spring-boot相關依賴的版本時很方便,但必定注意所以引起覆蓋其餘地方定義的依賴的版本,如將org.elasticsearch.client:transport:6.3.0的依賴定義順序置於spring-boot以前,項目仍會使用spring-boot:2.0.0中es:5.6.8的版本。
        -->
    </dependencies>
</dependencyManagement>

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <version>${spring.boot.version}</version>
    <executions>
        <execution>
            <goals>
                <goal>repackage</goal>
            </goals>
        </execution>
    </executions>
</plugin>

org.springframework.boot:spring-boot-maven-plugin 該打包插件打出的包(這裏稱爲項目jar包)結構不一樣於通常的jar包(主要是對依賴包、項目class的存放),它經過將項目全部依賴的jar包打到/BOOT-INF/libs/目錄,並且仍以jar存放,沒有所有解壓出依賴jar包中內容(包括層級目錄和.class文件)放到項目包根目錄,項目的全部.class所有放在/BOOT-INF/classes/目錄中,項目jar包的根目錄下放的是spring boot launcher包(由插件本身引入)的類。項目包根目錄下的文件目錄結構(JVM能直接讀到classpath的目錄結構),跟一般打出的包比較來看,有點相似項目只spring boot launcher包,spring boot應用啓動時,launcher在運行時本身去加載/BOOT-INF下的jar和.class,使得運行時項目class及其依賴class對jvm可見。

這種打包方式對storm項目不可行,storm nimbus在建立storm組件(spout, bout)實例後的分發supervisor過程當中會因找不到項目class及其依賴class而致使分發失敗。
緣由(待從新梳理驗證):【將此jar包添加到classpath後jvm沒法感知項目自身class和依賴的class,由於spring boot launcher還未被執行,classpath中尚未項目class和依賴class的信息】
同時,項目main入口類的getClass.getClassLoader的工做目錄成了項目jar包下的/BOOT-INF

打包插件的layout配置: TODO

  • ?(默認)
  • ZIP/DIR
  • WAR

支持其餘格式配置源:TODO

//監聽應用初始化,向spring環境中添加本身的屬性解析器(配置源)
public class MyAppCtxInit implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(@NonNull ConfigurableApplicationContext configurableApplicationContext) {
        configurableApplicationContext
            .getEnvironment()
            .getPropertySources()
            .addLast(new MyPropertySource());
            // .addLast添加在列表尾, .addFirst添加在列表頭, .addBefore .addAfter添加在其餘解析器的先後位置
    }
}
//本身的屬性解析器,至少實現方法Object getProperty(String name)
//MyConfigClass類型參是本身的代理配置類型(好比com.typesafe.config.Config以支持解析.conf配置文件)
class MyPropertySource  extends PropertySource<MyConfigClass> {
    ...
    @Override
    public Object getProperty(@NonNull String name) {
        return ...; //
    }
}

文件 META-INF/spring.factories :

org.springframework.context.ApplicationContextInitializer=MyAppCtxInit

spring web

自定義類型做爲request參數類型或返回類型,如何註冊解析器或轉換器?

返回的字符串消息(如錯誤信息)適應多語言環境??

如下的「響應方法」controller類中響應web請求的java方法。@註解 後加的(for class/method/parameter等)指的是該註解用在類、響應方法、響應方法參數等位置。

@RequestMapping("/xxx") for controller class(類上標註的), 含義是該controller接受的響應請求路徑的前綴,加上@RequestMapping("yyy") for method(方法上標註的)的路徑造成最終能響應的請求路徑。spring不支持方法上的註解忽略類上註解中的路徑,也就說當一個控制器中的不少處理方法都具備某種路徑前綴,所以把該路徑前綴標註到類上時,而某個方法不能具備該前綴時,沒有一種策略使得該方法能夠放棄繼承類上的路徑前綴。

@RequestMapping(method=...)可設置可響應的Http方法(GET,POST等),同時也有相應的@XxxMapping註解快捷表示方法,其中「Xxx」表示方法名,若有@GetMapping, @PostMapping等。

@RequestMapping中未設置method時,表示可接受全部Http方法。

@GetMapping @PostMapping等至關於method爲對應的GET,POST等的@RequestMapping。

若是響應的java方法參數中有原子類型(int,boolean等),那麼web請求中必須提供該參數,不然報錯,若是要實現參數可選,使用對應的包裝類型(Integer, Boolean),對象類型參數在web請求中未提供時值爲null。

請求參數是多值型時,使用對應類型的數組類型(T[])或集合類型(List,Set,Collection)。

Restful請求中的路徑參數定義使用花括號包裹,如@RequestMapping("/user/info/{userId}"),參數值捕獲使用 void f(@PathVariable("userId") String userId)。

@CookieValue(value="JSESSIONID", defaultValue="")(for parameter),獲取Cookie中的值 。

@RequestParam(name/value="paramName", required=true|false, defaultValue="")(for parameter)標記的參數爲表單(application/x-www-form-urlencoded)提交方式下web request body中的字段或URL中的參數。
@RequestParam可標記Map,綁定全部參數和值。

@SessionAttribute (for parameter) 獲取HttpSession中的屬性。

@SessionAttributes (for class)

@ModelAttribute (for method)

@RequestBody (for parameter,只准用一次)標記類型爲POJO的響應方法參數,要求web請求的content-type爲application/json,須要一個從json格式字符串轉換到POJO的解析器,通常用com.aliababa:fastjson或jackson。
@RequestBody能夠標記Map,綁定全部鍵值。

@RequestBody可與@RequestParam同時使用,content-type要求爲application/json,@RequestBody標記的POJO由web rquest body中的json格式串解析而來,@RequestParam標記的參數由URL參數解析而來。

@PathVariable(for parameter)獲取URL中訪問路徑部分中的變量。如@RequestMapping("/book/{type}")中的"type"變量。後接冒號:加上取值限制,如限制值在A,B,C中選一個@PathVariable("/{type:A|B|C}")

@PathParam(for parameter)獲取URL中問號後面的指定參數。如"/book?id=xxx"中的"id"的值。

@RequestParam (for parameter)獲取URL中的查詢參數鍵或表單指定參數名的值。

若是ajax請求時傳入的是json對象,響應方法中的參數是用@RequestParam標記的,而這樣的方式能成功請求/響應,則需確認瀏覽器實際傳輸請求時用的content-type是application/json仍是application/x-www-form-urlencoded,另外查看ajax使用的JS框架有沒有將json對象自動轉爲URL參數或轉爲form表單形式提交(若是默認的ajax請求的content-type是application/x-www-form-urlencoded,JS框架可能會這麼作,jQuery就是個例子)。

RestTemplate:該類可用做web請求客戶端,線程安全。其.get*().post*()等方法對應使用HttpMethod GET, POST等方式,其中有參數(String url,..., Map<String,> uriVariables),鍵值對參數uriVariables用於擴展url中的「變量」(以花括號裹挾命名),而不只僅請求參數(url中問號?後的鍵值參數),該參數不是將uriVariables中全部鍵值對拼接爲url的請求參數。如url="http://127.0.0.1/?foo={foo}"時會將Map uriVariable中鍵爲foo的值擴展到url中,假設foo對應值爲myval,則爲http://127.0.0.1/?foo=myval,而若是url="http://127.0.0.1/",則不會獲得http://127.0.0.1/?foo=myval。url參數中的待擴展「變量」能夠定義到任意位置(路徑的一部分、端口等),而不限於請求參數。

控制器加強 controller advice
利用AOP對加強控制器,如對特定類型異常進行統一處理。

控制器異常處理器(exception handler):定義控制器加強。
使用@ExceptionHandler(Array[Exception])標註方法,使方法成爲加強方法,在方法參數列表中定義相應的Exception類型參數以捕獲被拋出的異常,定義WebRequest捕獲web請求。

TODO 捕獲拋出異常的方法??? 爲不一樣請求路徑、不一樣的控制器/方法指定不一樣的異常處理器???

@RestControllerAdvice
class ControllerExceptionHandler {
  private final val log = LoggerFactory.getLogger(getClass)

  @ExceptionHandler(Array(classOf[IOException]))
  //@ResponseStatus(HttpStatus.?) //定義返回狀態碼
  def ioExceptionHandler(e: IOException, webRequest: WebRequest) = {
    log.error("io err, request path: {}, params: {}", webRequest.getContextPath, wrapParamValueArray(webRequest.getParameterMap), e)
    DataPackage.fail()
  }

  // 若是是單值則將類型變爲字符串,若是多值,則轉爲java.util.List。這麼轉換是由於數組的.toString不會輸出各元素值,而List會
  def wrapParamValueArray(params: java.util.Map[String, Array[String]]): java.util.Map[String, AnyRef] = {
    val wrapped = new java.util.HashMap[String, AnyRef]()
    params.keySet().foreach(key => {
      val v = params.get(key)
      if (v != null && v.length == 1) {
        wrapped.put(key, v(0))
      } else if (v != null && v.length > 1) { // to list
        wrapped.put(key, java.util.Arrays.asList(v: _*))
      } else { // null
        wrapped.put(key, null)
      }
    })
    wrapped
  }
}

spring boot test

<!--pom.xml-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>${spring.boot.version}</version>
    <scope>test</scope>
</dependency>
// with junit
@RunWith(SpringRunner.class)
@SpringBootTest(classes = AppMain.class)
@WebAppConfiguration  // for spring boot web, or @SpringBootTest(classes=, webEnvironment=)
public class AppTest {
}

public class WebTest extends AppTest {
    @Autowired
    WebApplicationContext webApplicationContext;

    MockMvc mockMvc;

    @Before
    public void setUpMockMvc() {
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}

測試組件(沒有main入口類)如業務JpaRepo類

@RunWith(SpringRunner.class)
@SpringBootTest(classes=MyJpaConfig.class)
@EnableAutoConfiguration
//@EnableApolloConfig("my-namespace") //若是須要使用ctrip-apollo
public class MyTest{}

定義測試類間測試順序:

定義測試類下測試方法的測試順序: 經過junit定義。

相關文章
相關標籤/搜索