@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定义。