| 知乎专栏 |
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": []
}