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

相关文章
相关标签/搜索