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') 生产模式

@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.

@Transient 此字段不做持久化(必须放在字段上,不能getter)

@Field('name')定义数据库中的字段名(默认为java类字段名)

@Id会将该字段映射到_id,因此@Field("name")不起作用。mongorepo中findbyid始终在_id上查找。

@ConfigurationProperties

@ConfigurationProperties(prefix="a.b.x") for class/method

这里的property应理解为java类的property:field+getter+setter,注解功能是指根据类字段名字找到配置文件中对应key的值来自动注入到字段,找key时使用@ConfigurationProperties中的prefix指定的字符串作为前缀。

所有getXXX(无论何种访问权限)的方法默认都被认为有对应绑定配置(尤其对于返回非基本类型的getXXX方法,会认为有绑定的内嵌属性,如果实际配置中没有,则spring报错)。

设定@ConfiguraionProperties标记类的字段默认值??以字段初始化方式实现。

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

最新文章

  1. TypeScript - 基本类型系统
  2. 45个JavaScript小技巧
  3. JAVA魔法堂:读取.properties配置文件
  4. poj 2449 第k短路径
  5. http://blog.csdn.net/shirdrn/article/details/6270506
  6. [python]魔术方法
  7. scrapy使用爬取多个页面
  8. EnterpriseArchitectect 软件的勾选的几个选项对应的中文意思
  9. 5 秒创建 k8s 集群 - 每天5分钟玩转 Docker 容器技术(115)
  10. webpack构建本地服务器
  11. C# winform在关闭窗体的时候及时释放内存问题
  12. Lumen框架使用Redis与框架Cache压测比较
  13. SaltStack 理解
  14. memory prefix inter,intra,intro,iso out 5
  15. SpringBoot集成Swagger2实现Restful(类型转换错误解决办法)
  16. sql-mybatis-多表查询不查的字段一定不要查
  17. [转帖]22个必须学习的Linux安全命令
  18. Python安装Windows的pip包
  19. Optimized Flow Migration for NFV Elasticity Control
  20. ACE-Streams架构简介及应用

热门文章

  1. IM聊实现客户端之间信息交互需求文档
  2. JVM即时编译(JIT)
  3. [bzoj2097][Usaco2010 Dec]Exercise 奶牛健美操_贪心_树形dp_二分
  4. zoj——1311 Network
  5. 中文命名之Hibernate 5演示 - 使用注解(annotation)而非xml定义映射
  6. js二叉树,前序/中序/后序(最大最小值,排序)
  7. netty学习(一)--linux下的网络io模型简单介绍
  8. 【Nginx】进程模型
  9. Java线程池几个參数的理解
  10. 精确计算java中float和double的精度