@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=
@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 boot data dependecy -> artifact: spring-boot-starter-jdbc
Spring Data是一個用於簡化數據庫訪問,並支持雲服務的開源框架。其主要目標是使得對數據的訪問變得方便快捷,並支持map-reduce框架和雲計算數據服務。 Spring Data 包含多個子項目:
spring jpa接口中的實體字段名幾乎都是指ORM映射以後的類字段名,如repo中的方法名關乎的字段名、Sort相關用到的字段名。
spring jpa中把提供數據庫CRUD的interface稱爲Repository,能夠定義繼承自JpaRepository<T,ID>
的interface,類型參數中的T是數據庫表實體類,ID是主鍵類型,Repo標註上@Repository
,spring jpa將自動生成繼承自SimpleJpaRepository
的代理實現,Repo接口中方法的名字定義功能(若是在方法上無@Query
等註解),方法名與功能實現的對應規則以下
能夠不經過方法名定義功能,使用自定義查詢。經過在接口方法上標註@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引入自動配置機制,根據條件自動定義某些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
支持其餘格式配置源: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
自定義類型做爲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 } }
<!--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定義。