【Spring技术原理】带你看看那些可能你还不知道的Spring特性技巧哦!

简介: 【Spring技术原理】带你看看那些可能你还不知道的Spring特性技巧哦!

前提介绍


本文主要介绍相关Spring框架的一些新特性问题机制,包含了一些特定注解方面的认识。


@Lazy可以延迟依赖注入


@Lazy注解修饰在类层面!

@Lazy
@Service
public class UserService extends BaseService<User> { }
复制代码


可以把@Lazy放在@Autowired之上,即依赖注入也是延迟的;当我们调用userService时才会注入。即延迟依赖注入到使用时。同样适用于@Bean。

@Lazy
@Autowired
private UserService userService;
复制代码



@Conditional


@Conditional类似于@Profile


  • 一般用于如有开发环境、测试环境、正式机环境,为了方便切换不同的环境可以使用@Profile指定各个环境的配置。
  • 通过某个配置来开启某个环境,方便切换,但是@Conditional的优点是允许自己定义规则,可以指定在如@Component、@Bean、@Configuration等注解的类上,以绝对Bean是否创建等。

首先来看看使用@Profile的用例,假设我们有个用户模块:


  1. 在测试/开发期间调用本机的模拟接口方便开发;
  2. 在部署到正式机时换成调用远程接口;
public abstract class UserService extends BaseService<User> { }
@Profile("local")
@Service
public class LocalUserService extends UserService {}
@Profile("remote")
@Service
public class RemoteUserService extends UserService {}
复制代码


我们在写测试用例时,可以指定我们使用哪个Profile:

@ActiveProfiles("remote")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations =  "classpath:spring-config.xml")
public class ServiceTest {
    @Autowired
    private UserService userService;
}
复制代码


如果想自定义如@Profile之类的注解等,那么@Conditional就派上用场了,假设我们系统中有好多本地/远程接口,那么我们定义两个注解@Local和@Remote注解要比使用@Profile方便的多;如:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Conditional(CustomCondition.class)
public @interface Local { }
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Conditional(CustomCondition.class)
public @interface Remote {}
复制代码
public class CustomCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        boolean isLocalBean = metadata.isAnnotated("com.xxx.Local");
        boolean isRemoteBean = metadata.isAnnotated("com.xxx.Remote");
        //如果bean没有注解@Local或@Remote,返回true,表示创建Bean
        if(!isLocalBean && !isRemoteBean) {
            return true;
        }
        boolean isLocalProfile = context.getEnvironment().acceptsProfiles("local");
        //如果profile=local 且 bean注解了@Local,则返回true 表示创建bean
        if(isLocalProfile) {
            return isLocalBean;
        }
        // 否则默认返回注解了@Remote或没有注解@Remote的Bean
        return isRemoteBean;
    }
}
复制代码


然后我们使用这两个注解分别注解我们的Service:

@Local
@Service
public class LocalUserService extends UserService { }
@Remote
@Service
public class RemoteUserService extends UserService {}
复制代码


  • 首先在@Local和@Remote注解上使用@Conditional(CustomCondition.class)指定条件。
  • 然后使用@Local和@Remote注解我们的Service,这样当加载Service时,会先执行条件然后判断是否加载为Bean。


@Profile实现的Condition是:org.springframework.context.annotation.ProfileCondition。



AsyncRestTemplate非阻塞异步(已废弃WebClient代替之)


提供AsyncRestTemplate用于客户端非阻塞异步支持。


服务器端
@RestController
public class UserController {
    private UserService userService;
    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }
    @RequestMapping("/api")
      public Callable<User> api() {
        return new Callable<User>() {
            @Override
            public User call() throws Exception {
                Thread.sleep(10L * 1000); //暂停两秒
                User user = new User();
                user.setId(1L);
                user.setName("haha");
                return user;
            }
        };
    }
}
复制代码


非常简单,服务器端暂停10秒再返回结果(但是服务器也是非阻塞的)。



客户端

public static void main(String[] args) {
    AsyncRestTemplate template = new AsyncRestTemplate();
    //调用完后立即返回(没有阻塞)
    ListenableFuture<ResponseEntity<User>> future = template.getForEntity("http://localhost:9080/rest/api", User.class);
    //设置异步回调
    future.addCallback(new ListenableFutureCallback<ResponseEntity<User>>() {
        @Override
        public void onSuccess(ResponseEntity<User> result) {
            System.out.println("======client get result : " + result.getBody());
        }
        @Override
        public void onFailure(Throwable t) {
            System.out.println("======client failure : " + t);
        }
    });
    System.out.println("==no wait");
}
复制代码


承接上面的内容:Future增强,提供了一个ListenableFuture,其是jdk的Future的封装,用来支持回调(成功/失败),借鉴了com.google.common.util.concurrent.ListenableFuture。

@Test
public void test() throws Exception {
    ListenableFutureTask<String> task = new ListenableFutureTask<String>(new Callable() {  
        @Override  
        public Object call() throws Exception {  
            Thread.sleep(10 * 1000L);  
            System.out.println("=======task execute");  
            return "hello";  
        }  
    });  
    task.addCallback(new ListenableFutureCallback<String>() {  
        @Override  
        public void onSuccess(String result) {  
            System.out.println("===success callback 1");  
        }  
        @Override  
        public void onFailure(Throwable t) {  
        }  
    });  
    task.addCallback(new ListenableFutureCallback<String>() {  
        @Override  
        public void onSuccess(String result) {  
            System.out.println("===success callback 2");  
        }  
        @Override  
        public void onFailure(Throwable t) {  
        }  
    });  
    ExecutorService executorService = Executors.newSingleThreadExecutor();  
    executorService.submit(task);  
    String result = task.get();  
    System.out.println(result);  
}
复制代码


  • 可以通过addCallback添加一些回调,当执行成功/失败时会自动调用。


  • 此处使用Future来完成非阻塞,这样的话我们也需要给它一个回调接口来拿结果;
  • Future和Callable是一对,一个消费结果,一个产生结果。调用完模板后会立即返回,不会阻塞;有结果时会调用其回调。

  • AsyncRestTemplate默认使用SimpleClientHttpRequestFactory,即通过java.net.HttpURLConnection实现;
  • 另外可以使用apache的http components,使用template.setAsyncRequestFactory(new HttpComponentsAsyncClientHttpRequestFactory()),设置即可。




Spring对Java8的时间类型支持


对jsr310的支持,只要能发现java.time.LocalDate,DefaultFormattingConversionService就会自动注册对jsr310的支持,只需要在实体/Bean上使用DateTimeFormat注解:

@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateTime;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate date;
@DateTimeFormat(pattern = "HH:mm:ss")
private LocalTime time;
复制代码


比如我们在springmvc中:

@RequestMapping("/test")
public String test(@ModelAttribute("entity") Entity entity) {
    return "test";
}
复制代码



当前端页面请求:
localhost:9080/spring4/test?dateTime=2013-11-11 11:11:11&date=2013-11-11&time=12:12:12
复制代码


会自动进行类型转换

另外spring4也提供了对TimeZone的支持,比如在springmvc中注册了LocaleContextResolver相应实现的话(如CookieLocaleResolver),我们就可以使用如下两种方式得到相应的TimeZone:

RequestContextUtils.getTimeZone(request)
LocaleContextHolder.getTimeZone()
复制代码



不过目前的缺点是不能像Local那样自动的根据当前请求得到相应的TimeZone,如果需要这种功能需要覆盖相应的如CookieLocaleResolver中的如下方法来得到:

protected TimeZone determineDefaultTimeZone(HttpServletRequest request) {  
    return getDefaultTimeZone();  
} 
复制代码
  • 另外还提供了DateTimeContextHolder,其用于线程绑定DateTimeContext;而DateTimeContext提供了如:Chronology、ZoneId、DateTimeFormatter等上下文数据,如果需要这种上下文信息的话,可以使用这个API进行绑定。
  • 比如在进行日期格式化时,就会去查找相应的DateTimeFormatter,因此如果想自定义相应的格式化格式,那么使用DateTimeContextHolder绑定即可。




泛型操作控制

随着泛型用的越来越多,获取泛型实际类型信息的需求也会出现,如果用原生API,需要很多步操作才能获取到泛型,比如:

ParameterizedType parameterizedType =   
    (ParameterizedType) ABService.class.getGenericInterfaces()[0];  
Type genericType = parameterizedType.getActualTypeArguments()[1];  
复制代码


Spring提供的ResolvableType API,提供了更加简单易用的泛型操作支持,如:


接口层的泛型处理
ResolvableType resolvableType1 = ResolvableType.forClass(ABService.class);
resolvableType1.as(Service.class).getGeneric(1).resolve();
复制代码


对于获取更复杂的泛型操作ResolvableType更加简单。


假设我们的API是:


public interface Service<N, M> { }
@org.springframework.stereotype.Service
public class ABService implements Service<A, B> { }
@org.springframework.stereotype.Service
public class CDService implements Service<C, D> {}
复制代码


得到类型的泛型信息

ResolvableType resolvableType1 = ResolvableType.forClass(ABService.class);
复制代码


通过如上API,可以得到类型的ResolvableType,如果类型被Spring AOP进行了CGLIB代理,请使用ClassUtils.getUserClass(ABService.class)得到原始类型,可以通过如下得到泛型参数的第1个位置(从0开始)的类型信息

resolvableType1.getInterfaces()[0].getGeneric(1).resolve()
复制代码


  • 泛型信息放在 Service<A, B> 上,所以需要resolvableType1.getInterfaces()[0]得到;
  • 通过getGeneric(泛型参数索引)得到某个位置的泛型;

resolve()把实际泛型参数解析出来



得到字段级别的泛型信息


假设我们的字段如下:

@Autowired
 private Service<A, B> abService;
 @Autowired
 private Service<C, D> cdService;
 private List<List<String>> list;
 private Map<String, Map<String, Integer>> map;
 private List<String>[] array;
复制代码


通过如下API可以得到字段级别的ResolvableType

ResolvableType resolvableType2 =  
                ResolvableType.forField(ReflectionUtils.findField(GenricInjectTest.class, "cdService"));  
复制代码


然后通过如下API得到Service<C, D>的第0个位置上的泛型实参类型,即C

resolvableType2.getGeneric(0).resolve()
复制代码


比如 List<List> list;是一种嵌套的泛型用例,我们可以通过如下操作获取String类型:

ResolvableType resolvableType3 =
                ResolvableType.forField(ReflectionUtils.findField(GenricInjectTest.class, "list"));
resolvableType3.getGeneric(0).getGeneric(0).resolve();
复制代码


更简单的写法

resolvableType3.getGeneric(0, 0).resolve(); //List<List<String>> 即String
复制代码


比如,Map<String, Map<String, Integer>> map;我们想得到Integer,可以使用:

ResolvableType resolvableType4 =
                ResolvableType.forField(ReflectionUtils.findField(GenricInjectTest.class, "map"));
resolvableType4.getGeneric(1).getGeneric(1).resolve();
复制代码


更简单的写法

resolvableType4.getGeneric(1, 1).resolve()  
复制代码



得到方法返回值的泛型信息


private HashMap<String, List<String>> method() {  
    return null;  
}  
复制代码

得到Map中的List中的String泛型实参:

ResolvableType resolvableType5 = ResolvableType.forMethodReturnType(ReflectionUtils.findMethod(GenricInjectTest.class, "method"));
resolvableType5.getGeneric(1, 0).resolve();
复制代码




得到构造器参数的泛型信息


假设我们的构造器如下:

public Const(List<List<String>> list, Map<String, Map<String, Integer>> map) {  }
复制代码


我们可以通过如下方式得到第1个参数( Map<String, Map<String, Integer>>)中的Integer:

ResolvableType resolvableType6 = ResolvableType.forConstructorParameter(ClassUtils.getConstructorIfAvailable(Const.class, List.class, Map.class), 1);
resolvableType6.getGeneric(1, 0).resolve();
复制代码




得到数组组件类型的泛型信息


如对于private List[] array; 可以通过如下方式获取List的泛型实参String:

ResolvableType resolvableType7 = ResolvableType.forField(ReflectionUtils.findField(GenricInjectTest.class, "array"));
resolvableType7.isArray();//判断是否是数组  
resolvableType7.getComponentType().getGeneric(0).resolve();  
复制代码


自定义泛型类型

ResolvableType resolvableType8 = ResolvableType.forClassWithGenerics(List.class, String.class);
        ResolvableType resolvableType9 = ResolvableType.forArrayComponent(resolvableType8);  
resolvableType9.getComponentType().getGeneric(0).resolve();  
ResolvableType.forClassWithGenerics(List.class, String.class)相当于创建一个List<String>类型;
ResolvableType.forArrayComponent(resolvableType8);:相当于创建一个List<String>[]数组;
resolvableType9.getComponentType().getGeneric(0).resolve():得到相应的泛型信息;
复制代码


泛型等价比较:

resolvableType7.isAssignableFrom(resolvableType9)
复制代码

如下创建一个List[]数组,与之前的List[]数组比较,将返回false。

ResolvableType resolvableType10 = ResolvableType.forClassWithGenerics(List.class, Integer.class);
ResolvableType resolvableType11= ResolvableType.forArrayComponent(resolvableType10);  
resolvableType11.getComponentType().getGeneric(0).resolve();  
resolvableType7.isAssignableFrom(resolvableType11);  
复制代码

从如上操作可以看出其泛型操作功能十分完善,尤其在嵌套的泛型信息获取上相当简洁。目前整个Spring环境都使用这个API来操作泛型信息。



注解方面的改进


Spring对注解API和ApplicationContext获取注解Bean做了一点改进,取注解的注解,如@Service是被@Compent注解的注解,可以通过如下方式获取@Componet注解实例:

Annotation service = AnnotationUtils.findAnnotation(ABService.class, org.springframework.stereotype.Service.class);  
Annotation component = AnnotationUtils.getAnnotation(service, org.springframework.stereotype.Component.class);  
复制代码


获取重复注解:


比如在使用hibernate validation时,我们想在一个方法上加相同的注解多个,需要使用如下方式:

@Length.List(  
        value = {  
                @Length(min = 1, max = 2, groups = A.class),  
                @Length(min = 3, max = 4, groups = B.class)  
        }  
)  
public void test() {}
复制代码


可以通过如下方式获取@Length:

Method method = ClassUtils.getMethod(AnnotationUtilsTest.class, "test");  
Set<Length> set = AnnotationUtils.getRepeatableAnnotation(method, Length.List.class, Length.class);
复制代码


当然,如果你使用Java8,那么本身就支持重复注解,比如spring的任务调度注解,

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})  
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {}
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})  
@Retention(RetentionPolicy.RUNTIME)  
@Documented
public @interface Schedules {
    Scheduled[] value();
}
复制代码


这样的话,我们可以直接同时注解相同的多个注解:

@Scheduled(cron = "123")
@Scheduled(cron = "234")
public void test
复制代码


但是获取的时候还是需要使用如下方式:

AnnotationUtils.getRepeatableAnnotation(ClassUtils.getMethod(TimeTest.class, "test"), Schedules.class, Scheduled.class)
复制代码


ApplicationContext和BeanFactory提供了直接通过注解获取Bean的方法:

@Test
public void test() {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(GenericConfig.class);
    ctx.refresh();
    Map<String, Object> beans = ctx.getBeansWithAnnotation(org.springframework.stereotype.Service.class);  
    System.out.println(beans);
}
复制代码


另外和提供了一个AnnotatedElementUtils用于简化java.lang.reflect.AnnotatedElement的操作。




ScriptEvaluator脚本的支持


spring也提供了类似于javax.script的简单封装,用于支持一些脚本语言,核心接口是:

public interface ScriptEvaluator {
    Object evaluate(ScriptSource script) throws ScriptCompilationException;
    Object evaluate(ScriptSource script, Map<String, Object> arguments) throws ScriptCompilationException;
}
复制代码


比如我们使用groovy脚本的话,可以这样:

@Test
public void test() throws ExecutionException, InterruptedException {
    ScriptEvaluator scriptEvaluator = new GroovyScriptEvaluator();
    //ResourceScriptSource 外部的
    ScriptSource source = new StaticScriptSource("i+j");
    Map<String, Object> args = new HashMap<>();
    args.put("i", 1);
    args.put("j", 2);
    System.out.println(scriptEvaluator.evaluate(source, args));
}
复制代码


另外还提供了BeanShell(BshScriptEvaluator)和javax.script(StandardScriptEvaluator)的简单封装。



MvcUriComponentsBuilder


MvcUriComponentsBuilder类似于ServletUriComponentsBuilder,但是可以直接从控制器获取URI信息,如下所示: 假设我们的控制器是:

@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/{id}")
    public String view(@PathVariable("id") Long id) {
        return "view";
    }
    @RequestMapping("/{id}")
    public A getUser(@PathVariable("id") Long id) {
        return new A();
    }
}
复制代码


注:如果在真实mvc环境,存在两个@RequestMapping("/{id}")是错误的。当前只是为了测试。

  1. 需要静态导入 import static org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.*;
@Test  
public void test() {  
    MockHttpServletRequest req = new MockHttpServletRequest();  
    RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(req));  
    //MvcUriComponentsBuilder类似于ServletUriComponentsBuilder,但是直接从控制器获取  
    //类级别的  
    System.out.println(  
            fromController(UserController.class).build().toString()  
    );  
    //方法级别的  
    System.out.println(  
            fromMethodName(UserController.class, "view", 1L).build().toString()  
    );  
    //通过Mock方法调用得到  
    System.out.println(  
            fromMethodCall(on(UserController.class).getUser(2L)).build()  
    );  
}
复制代码

注意:当前MvcUriComponentsBuilder实现有问题,只有JDK环境支持,大家可以复制一份,然后修改:method.getParameterCount() (Java 8才支持) 到method.getParameterTypes().length




Socket支持


提供了获取Socket TCP/UDP可用端口的工具,如

SocketUtils.findAvailableTcpPort()
SocketUtils.findAvailableTcpPort(min, max) 
SocketUtils.findAvailableUdpPort()




相关文章
|
6月前
|
Java 数据库 Spring
Spring事务的传播机制(行为、特性)
Spring事务的传播机制(行为、特性)
127 0
|
6月前
|
存储 监控 Java
|
6月前
|
JSON Dubbo Java
微服务框架(二十)Dubbo Spring Boot 生产就绪特性
  此系列文章将会描述Java框架Spring Boot、服务治理框架Dubbo、应用容器引擎Docker,及使用Spring Boot集成Dubbo、Mybatis等开源框架,其中穿插着Spring Boot中日志切面等技术的实现,然后通过gitlab-CI以持续集成为Docker镜像。   本文为Dubbo Spring Boot 生产就绪特性
|
1月前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
42 4
|
1月前
|
Java API 数据库
Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐
本文通过在线图书管理系统案例,详细介绍如何使用Spring Boot构建RESTful API。从项目基础环境搭建、实体类与数据访问层定义,到业务逻辑实现和控制器编写,逐步展示了Spring Boot的简洁配置和强大功能。最后,通过Postman测试API,并介绍了如何添加安全性和异常处理,确保API的稳定性和安全性。
36 0
|
3月前
|
安全 前端开发 Java
随着企业应用复杂度提升,Java Spring框架以其强大与灵活特性简化开发流程,成为构建高效、可维护应用的理想选择
随着企业应用复杂度提升,Java Spring框架以其强大与灵活特性简化开发流程,成为构建高效、可维护应用的理想选择。依赖注入使对象管理交由Spring容器处理,实现低耦合高内聚;AOP则分离横切关注点如事务管理,增强代码模块化。Spring还提供MVC、Data、Security等模块满足多样需求,并通过Spring Boot简化配置与部署,加速微服务架构构建。掌握这些核心概念与工具,开发者能更从容应对挑战,打造卓越应用。
43 1
|
6月前
|
设计模式 前端开发 Java
玩转Spring—Spring5新特性之Reactive响应式编程实战
玩转Spring—Spring5新特性之Reactive响应式编程实战
229 0
|
6月前
|
XML 监控 安全
Spring特性之一——AOP面向切面编程
Spring特性之一——AOP面向切面编程
78 1
|
2月前
|
安全 Java 开发者
强大!Spring Cloud Gateway新特性及高级开发技巧
在微服务架构日益盛行的今天,网关作为微服务架构中的关键组件,承担着路由、安全、监控、限流等多重职责。Spring Cloud Gateway作为新一代的微服务网关,凭借其基于Spring Framework 5、Project Reactor和Spring Boot 2.0的强大技术栈,正逐步成为业界的主流选择。本文将深入探讨Spring Cloud Gateway的新特性及高级开发技巧,助力开发者更好地掌握这一强大的网关工具。
226 6
|
3月前
|
存储 Java 开发者
使用Spring Boot 3.3全新特性CDS,启动速度狂飙100%!
【8月更文挑战第30天】在快速迭代的软件开发周期中,应用的启动速度是开发者不可忽视的一个重要指标。它不仅影响着开发效率,还直接关系到用户体验。随着Spring Boot 3.3的发布,其中引入的Class Data Sharing(CDS)技术为应用的启动速度带来了革命性的提升。本文将围绕这一全新特性,深入探讨其原理、使用方法以及带来的实际效益,为开发者们带来一场技术盛宴。
188 2