知乎专栏 |
Service 的变量是共享的,这是与 new Object 的区别。
同步执行
package cn.netkiller.service; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.Cacheable; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @Service @Slf4j @Data public class TestService { private String share; }
@GetMapping("get") public String get() { return testService.getShare(); } @GetMapping("set") public String set(@RequestParam("value") String value) { testService.setShare(value); return testService.getShare(); }
neo@MacBook-Pro-M2 ~/w/watch (main)> curl -s 'http://localhost:8080/test/set?value=aaa' aaa⏎ neo@MacBook-Pro-M2 ~/w/watch (main)> curl -s 'http://localhost:8080/test/get' aaa⏎ neo@MacBook-Pro-M2 ~/w/watch (main)> curl -s 'http://localhost:8080/test/set?value=bbb' bbb⏎ neo@MacBook-Pro-M2 ~/w/watch (main)> curl -s 'http://localhost:8080/test/get' bbb⏎
我们可以看到 Service 是 singleton 单例模式
neo@MacBook-Pro-M2 ~/w/watch (main)> curl -s 'http://neo:chen@localhost:8080/actuator/beans' |jq '.contexts.[].beans.testService' { "aliases": [], "scope": "singleton", "type": "cn.netkiller.service.TestService$$SpringCGLIB$$0", "resource": "file [/Users/neo/workspace/watch/target/classes/cn/netkiller/service/TestService.class]", "dependencies": [] }
在多线程或者异步执行的情况会更糟
package cn.netkiller.service; import cn.netkiller.domain.Chat; import cn.netkiller.repository.ChatRepository; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @Service @Slf4j public class TestService { private String test; @Async public void test1() { this.test = "Test 1"; } @Async public void test2() { this.test = "Test 2"; } @Async public void test() { log.info(this.test); } }
@Autowired private TestService testService; @GetMapping("test") private Mono<String> test() { chatService.test(); return Mono.just("OK"); } @GetMapping("/test1") public Mono<String> test1() { String test = "测试"; chatService.test1(); return Mono.just(test); } @GetMapping("/test2") public Mono<String> test2() { chatService.test2(); return Mono.just("OK"); }
2024-01-01T14:09:10.022+08:00 INFO 59782 --- [watch-development] [ task-1] cn.netkiller.service.TestService : null 2024-01-01T14:09:24.694+08:00 INFO 59782 --- [watch-development] [ task-3] cn.netkiller.service.TestService : Test 1 2024-01-01T14:10:04.394+08:00 INFO 59782 --- [watch-development] [ task-8] cn.netkiller.service.TestService : Test 2
要实现 Service 多例模式很简单,只需要在 Service 根 Controller 中同时增加 @Scope("prototype") 即可
package cn.netkiller.service; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.Cacheable; import org.springframework.context.annotation.Scope; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @Service @Slf4j @Data @Scope("prototype") public class TestService { private String share = "default"; public TestService() { this.share = "init"; } }
package cn.netkiller.controller; import cn.netkiller.ai.AiChain; import cn.netkiller.annotation.TokenPass; import cn.netkiller.annotation.TokenVerification; import cn.netkiller.component.StreamService; import cn.netkiller.domain.Picture; import cn.netkiller.domain.PicturePsychoanalysis; import cn.netkiller.domain.embeddable.StreamTopic; import cn.netkiller.service.AiService; import cn.netkiller.service.PicturePsychoanalysisService; import cn.netkiller.service.TestService; import cn.netkiller.utils.ResponseJson; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.MediaType; import org.springframework.http.codec.ServerSentEvent; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.*; import org.springframework.web.client.RestClient; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Flux; import reactor.core.publisher.FluxSink; import reactor.core.publisher.Mono; import reactor.core.publisher.ParallelFlux; import reactor.core.scheduler.Schedulers; import java.io.IOException; import java.security.Principal; import java.util.*; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.IntStream; import java.util.stream.Stream; @RestController @Slf4j @RequestMapping("/test") @Scope(value = "prototype") public class TestController { @Autowired private TestService testService; @GetMapping("get") public String get() { return testService.getShare(); } @GetMapping("set") public String set(@RequestParam("value") String value) { testService.setShare(value); return testService.getShare(); } }
neo@MacBook-Pro-M2 ~/w/watch (main)> curl -s 'http://localhost:8080/test/get' init⏎ neo@MacBook-Pro-M2 ~/w/watch (main)> curl -s 'http://localhost:8080/test/set?value=bbb' bbb⏎ neo@MacBook-Pro-M2 ~/w/watch (main)> curl -s 'http://localhost:8080/test/get' init⏎ neo@MacBook-Pro-M2 ~/w/watch (main)> curl -s 'http://localhost:8080/test/get' init⏎ neo@MacBook-Pro-M2 ~/w/watch (main)> curl -s 'http://localhost:8080/test/set?value=aaa' aaa⏎ neo@MacBook-Pro-M2 ~/w/watch (main)> curl -s 'http://localhost:8080/test/get' init⏎
去掉构造方法
package cn.netkiller.service; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.Cacheable; import org.springframework.context.annotation.Scope; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @Service @Slf4j @Data @Scope("prototype") public class TestService { private String share = "default"; public TestService() { this.share = "init"; } }
测试结果
neo@MacBook-Pro-M2 ~/w/watch (main)> curl -s 'http://localhost:8080/test/get' default⏎ neo@MacBook-Pro-M2 ~/w/watch (main)> curl -s 'http://localhost:8080/test/set?value=aaa' aaa⏎ neo@MacBook-Pro-M2 ~/w/watch (main)> curl -s 'http://localhost:8080/test/get' default⏎
neo@MacBook-Pro-M2 ~/w/watch (main)> curl -s 'http://neo:chen@localhost:8080/actuator/beans' |jq '.contexts.[].beans.testController' { "aliases": [], "scope": "prototype", "type": "cn.netkiller.controller.TestController$$SpringCGLIB$$0", "resource": "file [/Users/neo/workspace/watch/target/classes/cn/netkiller/controller/TestController.class]", "dependencies": [ "picturePsychoanalysisService", "streamService", "testService", "aiService" ] } neo@MacBook-Pro-M2 ~/w/watch (main)> curl -s 'http://neo:chen@localhost:8080/actuator/beans' |jq '.contexts.[].beans.testService' { "aliases": [], "scope": "prototype", "type": "cn.netkiller.service.TestService$$SpringCGLIB$$0", "resource": "file [/Users/neo/workspace/watch/target/classes/cn/netkiller/service/TestService.class]", "dependencies": [] }