preface
Recently, the company asked me to maintain the old project of Spring+Servlet+Hibernate+Spring Security+Jsp, which can exercise my business logic and project control ability. Although the project is very old, there is still a lot I can learn from it.
Optimization of e-commerce projects
1. We roughly optimized the second kill interface: Redis pre-reduce inventory to reduce database access; Memory tag less access to Redis; Rabbitmq queue buffering, asynchronous ordering, enhanced user experience. So the specific steps are as follows.
1. The Controller that processes the slasher is ready to load within the Spring container cycle. That is, implement the InitializingBean, load the inventory of goods into Redis in the afterPropertiesSet() method, and set a flag in memory to set whether the goods end in seconds.
@override public void afterPropertiesSet() throws Exception {List<GoodsVo> goodsVoList = goodsService.listGoodsVo();if (CollectionUtils.isEmpty(goodsVoList)) {
return;
}
goodsVoList.forEach(goodsVo -> {
redisService.set(GoodsKey.getMiaoshaGoodsStock, "" + goodsVo.getId(), goodsVo.getStockCount());
localOverMap.put(goodsVo.getId(), false);
});
}
Copy the code
2. After receiving the seckill request, the background firstly checks the memory flag and then reduces the inventory of goods in Redis. If the kill ends, set a flag for the kill end in the memory. If the commodity kill is still in progress, proceed to the next step.
3. Buffer the message of the kill product into the queue and return directly. Instead of returning success, you are returning to the queue. In this case, the front desk cannot directly indicate that the second kill succeeded. Instead, it starts the timer and checks whether the second kill succeeded after a period of time.
4. Message out, modify the inventory in db, and create a kill order.
2. The solution of distributed Session is to generate unique tokens that identify users, write the tokens into cookies, and then write the token+ user information into Redis. The expiration time of tokens in Redis must be consistent with the expiration time of cookies. Each time a user logs in, the Session validity period and Cookie validity period are delayed.
3. From the perspective of caching, we can carry out page caching +URL caching + object caching to achieve the purpose of optimization. We can manually render the Thymeleaf template and cache the item details page and item list page in Redis, using the item list page as an example.
@RequestMapping(value = "/to_list", produces = "text/html; charset=UTF-8")
@ResponseBody
public String list(MiaoshaUser miaoshaUser) throws IOException {
modelMap.addAttribute("user", miaoshaUser); String htmlCached = redisservice. get(goodskey.getgoodslist,"", String.class);
if(! StringUtils.isEmpty(htmlCached)) {return htmlCached;
}
List<GoodsVo> goodsVoList = goodsService.listGoodsVo();
modelMap.addAttribute("goodsList", goodsVoList);
SpringWebContext springWebContext = new SpringWebContext(request, response, request.getServletContext(),
request.getLocale(), modelMap, applicationContext);
String html = thymeleafViewResolver.getTemplateEngine().process("goods_list", springWebContext);
if(! StringUtils.isEmpty(html)) { redisService.set(GoodsKey.getGoodsList,"", html);
}
return html;
}
Copy the code
4. From the perspective of static resources, we carried out page statics, front and back end separation, static resource optimization and CDN node optimization. Static resource optimization is used as an example.
1.JS/CSS compression reduces traffic. Multiple JS/CSS combinations reduce the number of connections. 3.CDN access nearby, reducing the request time. 4. Cache some interfaces to the user’s browser.
5. Safety optimization. The password is salted twice, the first salt is fixed, written in Java code. The second addition of salt is random and stored in the database. On the commodity kill page, add a mathematical formula captcha to disperse user requests. Add the current limiting and brush prevention mechanism to the interface. This section uses the interface traffic limiting and brush prevention mechanism as an example.
1. Define AccessLimit annotations for methods.
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AccessLimit {
int seconds();
int maxCount();
boolean needLogin() default true;
}
Copy the code
2. Define an AccessInterceptor interceptor to get the parameters in the AccessLimit annotation of the method. The request reqEusturi is the key in redis, and seconds is the expiration time of the key. Increments each request by one, and returns “Too frequently visited” if the URL is accessed more times than the maxCount set within the specified time.
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
MiaoshaUser user = getUser(request, response);
UserContext.setUser(user);
HandlerMethod hm = (HandlerMethod) handler;
AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
if (Objects.isNull(accessLimit)) {
return true;
}
int seconds = accessLimit.seconds();
int maxCount = accessLimit.maxCount();
boolean needLogin = accessLimit.needLogin();
String key = request.getRequestURI();
if (needLogin) {
if (Objects.isNull(user)) {
render(response, CodeMsg.SESSION_ERROR);
return false;
}
}
AccessKey ak = AccessKey.withExpire(seconds);
Integer count = redisService.get(ak, key, Integer.class);
if (Objects.isNull(count)) {
redisService.set(ak, key, 1);
} else if (count < maxCount) {
redisService.incr(ak, key);
} else {
render(response, CodeMsg.ACCESS_LIMIT_REACHED);
return false; }}return true;
}
Copy the code
6. Deployment optimization. LVS+Keepalived Dual hot backup mode +Nginx+Tomcat.
Intelli J IDEA using skills
Global search Ctrl +Shift + F 2. Global replace Ctrl +Shift + R
Tips for using Vim editor
1. Search in the Vim editor.
Command mode Enter/string, for example, /xiaoma. 2. To go to the next query, press n.
Redis sets the password
If requirepass ${password} is not set in redis.conf, the console will throw a connection denial exception.
HTTP
The use of Cache Control
No cache: Forces each request to be sent directly to the source server without local cache version validation. Max-age > 0: fetched directly from the browser cache.
RabbitMQ
1. Advance Message Queuing Protocol (AMQP) is an application-layer standard advanced Message Queuing Protocol that provides unified messaging services.
2.Exchange acts as a switch and a route in RabbitMQ. It can also be thought of as a RabbitMQ filter. RabbitMQ has four modes.
1.Direct: The Routing Key is routed to the specified Queue. 2.Topic: Similar to Direct, but can match multiple keywords. 3.Fanout: No Routing Key concept. It is equivalent to broadcast mode, which distributes messages to queues in all bound FanoutExchanges. 4.Header: Different from the preceding three, add the key-value attribute to match.
3. Write RabbitMQ code
Configure the four modes for RabbitMQ
/**
* @author cmazxiaoma
* @version V1.0
* @Description: TODO
* @date 2018/6/4 11:36
*/
@Configuration
public class MQConfig {
public static final String MIAOSHA_QUEUE = "miaosha.queue";
public static final String QUEUE = "queue";
public static final String TOPIC_QUEUE1 = "topic.queue1";
public static final String TOPIC_QUEUE2 = "topic.queue2";
public static final String HEADER_QUEUE = "header.queue";
public static final String TOPIC_EXCHANGE = "topicExchange";
public static final String FANOUT_EXCHANGE = "fanoutExchange";
public static final String HEADERS_EXCHANGE = "headersExchange"; /** * Direct mode * @return
*/
@Bean
public Queue queue() {
return new Queue(QUEUE, true);
}
@Bean
public Queue miaoshaoQue() {
return new Queue(MQConfig.MIAOSHA_QUEUE, true); } /** * Topic mode * @return
*/
@Bean
public TopicExchange topicExchange() {
return new TopicExchange(TOPIC_EXCHANGE);
}
@Bean
public Queue topicQueue1() {
return new Queue(TOPIC_QUEUE1, true);
}
@Bean
public Queue topicQueue2() {
return new Queue(TOPIC_QUEUE2, true);
}
@Bean
public Binding topicBinding1() {
return BindingBuilder
.bind(topicQueue1())
.to(topicExchange())
.with("topic.key1");
}
@Bean
public Binding topicBinding2() {
return BindingBuilder
.bind(topicQueue2())
.to(topicExchange())
.with("topic.#"); } /** * Fanout mode * @return
*/
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange(FANOUT_EXCHANGE);
}
@Bean
public Binding fanoutBinding1() {
return BindingBuilder.bind(topicQueue1())
.to(fanoutExchange());
}
@Bean
public Binding fanoutBinding2() {
returnBindingBuilder.bind(topicQueue2()) .to(fanoutExchange()); } /** * Header mode * @return
*/
@Bean
public HeadersExchange headersExchange() {
return new HeadersExchange(HEADERS_EXCHANGE);
}
@Bean
public Queue headerQueue1() {
return new Queue(HEADER_QUEUE, true);
}
@Bean
public Binding headerBinding() {
Map<String, Object> map = new HashMap<>();
map.put("header1"."value1");
map.put("header2"."value2");
returnBindingBuilder.bind(headerQueue1()).to(headersExchange()) .whereAll(map).match(); }}Copy the code
Configuring message producers
/**
* @author cmazxiaoma
* @version V1.0
* @Description: TODO
* @date 2018/6/4 13:05
*/
@Service
@Slf4j
public class MQSender {
@Autowired
private AmqpTemplate amqpTemplate;
public void sendMiaoshaMessageDirect(MiaoshaMessage miaoshaMessage) {
String msg = RedisService.beanToString(miaoshaMessage);
log.info("send direct message = {}", msg);
amqpTemplate.convertAndSend(MQConfig.MIAOSHA_QUEUE, msg);
}
public void sendDirect(Object message) {
String msg = RedisService.beanToString(message);
log.info("send direct message = {}", msg);
amqpTemplate.convertAndSend(MQConfig.QUEUE, msg);
}
public void sendTopic(Object message) {
String msg = RedisService.beanToString(message);
log.info("send topic message = {}", msg);
amqpTemplate.convertAndSend(MQConfig.TOPIC_EXCHANGE, "topic.key1", msg + "1");
amqpTemplate.convertAndSend(MQConfig.TOPIC_EXCHANGE, "topic.key2", msg + "- 2");
}
public void sendFanout(Object message) {
String msg = RedisService.beanToString(message);
log.info("send fanout message = {}", msg);
amqpTemplate.convertAndSend(MQConfig.FANOUT_EXCHANGE, "", msg);
}
public void sendHeader(Object message) {
String msg = RedisService.beanToString(message);
log.info("send header message = {}", msg);
MessageProperties messageProperties = new MessageProperties();
messageProperties.setHeader("header1"."value1");
messageProperties.setHeader("header2"."value2");
Message newMessage = new Message(msg.getBytes(), messageProperties);
amqpTemplate.convertAndSend(MQConfig.HEADERS_EXCHANGE, "", newMessage); }}Copy the code
Configuring message consumers
/**
* @author cmazxiaoma
* @version V1.0
* @Description: TODO
* @date 2018/6/4 13:47
*/
@Service
@Slf4j
public class MQReceiver {
@Autowired
private RedisService redisService;
@Autowired
private GoodsService goodsService;
@Autowired
private OrderService orderService;
@Autowired
private MiaoshaService miaoshaService;
@RabbitListener(queues = MQConfig.MIAOSHA_QUEUE)
public void receiveMiaoshaMessageDirect(String message) {
log.info("receive direct miaosha message = {}", message);
MiaoshaMessage miaoshaMessage = RedisService.stringToBean(message, MiaoshaMessage.class);
MiaoshaUser miaoshaUser = miaoshaMessage.getMiaoshaUser();
Long goodsId = miaoshaMessage.getGoodsId();
GoodsVo goodsVo = goodsService.getGoodsVoByGoodsId(goodsId);
int stock = goodsVo.getStockCount();
if (stock <= 0) {
return; } / / determine whether have been seconds kill MiaoshaOrder order. = the orderService getMiaoshaOrderByUserIdGoodsId (miaoshaUser. GetId (), goodsId);if(! Objects.isNull(order)) {return; } // Write the second kill order miaoshaService. Miaosha (miaoshaUser, goodsVo); } @RabbitListener(queues = MQConfig.QUEUE) public void receiveDirect(String message) { log.info("receive direct message = {}", message);
}
@RabbitListener(queues = MQConfig.TOPIC_QUEUE1)
public void receiveTopic1(String message) {
log.info("receive topic queue1 message = {}", message);
}
@RabbitListener(queues = MQConfig.TOPIC_QUEUE2)
public void receiveTopic2(String message) {
log.info("receive topic queue2 message = {}", message);
}
@RabbitListener(queues = MQConfig.HEADER_QUEUE)
public void receiveHeader(byte[] message) {
log.info("receive header message = {}", new String(message)); }}Copy the code
Test RabbitMQ’s Controller
/** * @author cmazxiaoma * @version V1.0 * @description: TODO * @date 2018/5/29 16:36 */ @controller @requestMapping ("/rabbitmq")
public class RabbitmqController extends BaseController {
@Autowired
private MQSender mqSender;
@GetMapping("/header")
@ResponseBody
public Result<String> header() {
mqSender.sendHeader("hello, header");
return Result.success("hello, header");
}
@GetMapping("/fanout")
@ResponseBody
public Result<String> fanout() {
mqSender.sendFanout("hello, fanout");
return Result.success("hello, fanout");
}
@GetMapping("/topic")
@ResponseBody
public Result<String> topic() {
mqSender.sendTopic("hello, topic");
return Result.success("hello, topic");
}
@GetMapping("/direct")
@ResponseBody
public Result<String> direct() {
mqSender.sendDirect("hello, direct");
return Result.success("hello, direct"); }}Copy the code
Nginx
Nginx commands are forgotten after a while. I’ll just put it in Jane’s book. Activation: / usr/local/nginx/sbin/nginx – C/usr/local/nginx/conf/nginx. Conf closed: / usr/local/nginx/sbin/nginx – s stop
We configure the max_fail and fail_TIMEOUT parameters in nginx.conf. When the number of failures exceeds max_fail, nginx sends subsequent requests to other Real servers for processing. Fail_timeout Indicates the waiting time for failure. If a request fails, the system waits for fail_timeout to check whether the request succeeds.
Git is confusing
Workspace: includes the actual changes to the file, the current change does not add into the staging area of the file change information. Temporary storage: Temporary storage of file change information
Git reset filename: Clears the changes to the filename file submitted by the add command to the staging area. Git checkout –filename: Undoes changes to the workspace.
JS Basics
As we all know, Java has three major features: encapsulation, inheritance, and polymorphism. We can use JS protoType to inject these three features into Java objects.
<script type="text/javascript">
var myObject = {
foo: "bar",
func: function() {
var self = this;
console.log("outer func:this.foo=" + this.foo);
console.log("outer func:self.foo=" + self.foo);
(function() {
console.log("inner func:this.foo=" + this.foo);
console.log("inner func:self.foo=" + self.foo);
}());
}
};
myObject.func();
Java = function() {};
Java.prototype = {
oriented: function() {
console.log(Object oriented);
},
fengzhuang: function() {
console.log("Packaging");
},
extend: function() {
console.log("Inheritance"); }}; java = new Java(); java.oriented(); java.fengzhuang(); java.extend(); </script>Copy the code
Spring MVC is a surprise note
Produces data in “text/ HTML “format and responds to the ContentType of the bar. We call addDefaultHeaders() to set the ContentType and ContentLength properties in the response bar before writing the message to return the response.
protected void addDefaultHeaders(HttpHeaders headers, T t, MediaType contentType) throws IOException{
if (headers.getContentType() == null) {
MediaType contentTypeToUse = contentType;
if (contentType == null || contentType.isWildcardType() || contentType.isWildcardSubtype()) {
contentTypeToUse = getDefaultContentType(t);
}
else if(MediaType.APPLICATION_OCTET_STREAM.equals(contentType)) { MediaType mediaType = getDefaultContentType(t); contentTypeToUse = (mediaType ! = null ? mediaType : contentTypeToUse); }if(contentTypeToUse ! = null) {if (contentTypeToUse.getCharset() == null) {
Charset defaultCharset = getDefaultCharset();
if (defaultCharset != null) {
contentTypeToUse = new MediaType(contentTypeToUse, defaultCharset);
}
}
headers.setContentType(contentTypeToUse);
}
}
if(headers.getContentLength() < 0 && ! headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) { Long contentLength = getContentLength(t, headers.getContentType());if(contentLength ! = null) { headers.setContentLength(contentLength); }}}Copy the code
2.@ResponseBody this annotation is used to format the return object of the Controller method according to the Accept in the request header of HttpRequest with the appropriate HttpMessageConverter conversion. Write to the body data area of the Response object (HttpOutputMessage). If the consume method is specified as “application/json”, then the method only processes requests whose ContentType attribute value in the request header is “application/json”.
3. Check whether a method has specified annotations, whether the class of a method has specified annotations, and whether the parameters of a method have specified annotations.
parameter.hasParameterAnnotation(RequestBody.class)
AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class)
returnType.hasMethodAnnotation(ResponseBody.class)
Copy the code
4. @ ModelAttribute use
1. When applied to the parameters of the method, the parameters passed by the client will be injected into the specified object according to the name, and the object will be automatically added to modelMap for the convenience of view layer invocation
2. When applied to a method, it will be executed before each @requestMapping method. If there is a return value, it will be automatically added to modelMap. I generally use it to wrap BaseController
public abstract class BaseController { protected HttpServletRequest request; protected HttpServletResponse response; protected HttpSession session; protected ModelMap modelMap; @ModelAttribute protected void initSpringMvc(HttpServletRequest request, HttpServletResponse response, HttpSession session, ModelMap modelMap) { this.request = request; this.response = response; this.session = session; this.modelMap = modelMap; }}Copy the code
5. Scheduled task. We annotate @enablesCheduling in WebApplication class to enable scheduled task. The parameters of the CRon expression from left to right are seconds, minutes, hours, days, months, weeks, and years. For a detailed use of cron expressions, see http://cron.qqe2.com/
@Component
public class TestTask {
@Scheduled(cron = "4-40 * * * *?")
public void reportCurrentTime() {
System.out.println("Present time :" + DateFormatUtils.format(new Date(),
"yyyy-MM-dd HH:mm:ss")); }}Copy the code
6. To enable asynchronous tasks, we annotate @enableAsync in the WebApplication class.
We can write an AsyncTask task class
@Component
public class AsyncTask {
@Async
public Future<Boolean> doTask1() throws Exception {
long start = System.currentTimeMillis();
Thread.sleep(1000);
long end = System.currentTimeMillis();
System.out.println("Task 1 Time :" + (end - start));
return new AsyncResult<>((true));
}
@Async
public Future<Boolean> doTask2() throws Exception {
long start = System.currentTimeMillis();
Thread.sleep(2000);
long end = System.currentTimeMillis();
System.out.println("Task 2 Time :" + (end - start));
return new AsyncResult<>((true));
}
@Async
public Future<Boolean> doTask3() throws Exception {
long start = System.currentTimeMillis();
Thread.sleep(3000);
long end = System.currentTimeMillis();
System.out.println("Task 3 Time :" + (end - start));
return new AsyncResult<>((true)); }}Copy the code
Then write TaskController
@RestController
@RequestMapping("/tasks")
public class TaskController extends BaseController {
@Autowired
private AsyncTask asyncTask;
@RequestMapping("test")
public Result test() throws Exception {
long start = System.currentTimeMillis();
Future<Boolean> a = asyncTask.doTask1();
Future<Boolean> b = asyncTask.doTask2();
Future<Boolean> c = asyncTask.doTask3();
while(! a.isDone() || ! b.isDone() || ! c.isDone()) {if (a.isDone() && b.isDone() && c.isDone()) {
break;
}
}
long end = System.currentTimeMillis();
String times = "All tasks completed, total time :" + (end - start) + "毫秒";
return Result.success(times); }}Copy the code
We can see that the total time of these three tasks is 3000ms, which proves that the tasks are executed asynchronously. If @async is removed, the execution of these three tasks is synchronous, and the total time should be more than 6000 ms.
{"code": 0."data":"All tasks completed, total time :3005 ms"."msg":""}
Copy the code
7. Deploy SpringBoot to external Tomcat and configure the POM file. Set the Tomcat scope to provided, which indicates that it is only used at compiler and test time because we are deployed to external Tomcat, which is supported at run time.
<! Tomcat is not required by default, but is required when the current Web application needs to be deployed to an external servlet container, and scope is configured to provided when the default JAR needs to be started. Remove the provided, Provided indicates use only at compile time and during testing --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency>Copy the code
Remember to change the packaging method from JAR to WAR
< the groupId > com. Cmazxiaoma < / groupId > < artifactId > seckillSystem < / artifactId > < version > 0.0.1 - the SNAPSHOT < / version > <packaging>war</packaging>Copy the code
I’m going to rewrite the SpringApplication startup class, so I’m going to create a new class called WebApplication
@enablesCheduling @enableAsync Public Class WebApplication extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {returnbuilder.sources(WebApplication.class); }}Copy the code
Then Build Artifacts.
OSI
OSI是开放式系统互联,英文是Open System Interconnection
Application layer Presentation layer Session layer Transport layer Network layer Data link layer Physical layer
TCP/IP model
Application layer = HTTP(Hypertext Transfer Protocol), TFTP(Simple File Transfer Protocol), SMTP(Simple Mail Transfer Protocol), DNS(domain name System), SNMP(Simple Network Management Protocol), NFS(network file system), Telnet(terminal login) Transport layer = TCP, IP network layer = IP, ICMP, ARP, RARP Data link layer = PPP
HttpMessageConverter Exception thrown
When I request /login/to_login it returns the login view, and the login screen loads the background image. At this time, we did not configure the resource mapping, so the background image will request the Controller at the back end. If no suitable Controller is found to handle the request, the global exception catcher enters exception handling. In RequestResponseBodyMethodProcessor writeWithMessageConverters () method, we will call getProducibleMediaTypes () method to obtain the request all return message format type.
HttpServletRequest request = inputMessage.getServletRequest();
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
if(outputValue ! = null && producibleMediaTypes.isEmpty()) { throw new IllegalArgumentException("No converter found for return value of type: " + valueType);
}
Copy the code
Since we do not explicitly set the PRODUCES property in the global exception catcher HandlerMapping, we can only iterate over all the HttpMessageConverter, Use the canWrite() method to find the HttpMessageConverter that supports parsing Java objects, and add the mediaType it supports to the mediaTypes collection.
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<? > valueClass, Type declaredType) { Set<MediaType> mediaTypes = (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);if(! CollectionUtils.isEmpty(mediaTypes)) {return new ArrayList<MediaType>(mediaTypes);
}
else if(! this.allSupportedMediaTypes.isEmpty()) { List<MediaType> result = new ArrayList<MediaType>();for(HttpMessageConverter<? > converter : this.messageConverters) {if(converter instanceof GenericHttpMessageConverter && declaredType ! = null) {if (((GenericHttpMessageConverter<?>) converter).canWrite(declaredType, valueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}
else if(converter.canWrite(valueClass, null)) { result.addAll(converter.getSupportedMediaTypes()); }}return result;
}
else {
returnCollections.singletonList(MediaType.ALL); }}Copy the code
We get that producibleMediaTypes are all about “application/ JSON “format, and we loop for twice, comparing requestedMediaTypes to producibleMediaTypes one by one, Derive compatible compatibleMediaTypes. If the request message format and return message format without a match, it throws HttpMediaTypeNotAcceptableException anomalies.
Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
for (MediaType requestedType : requestedMediaTypes) {
for (MediaType producibleType : producibleMediaTypes) {
if(requestedType.isCompatibleWith(producibleType)) { compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType)); }}}if (compatibleMediaTypes.isEmpty()) {
if(outputValue ! = null) { throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes); }return;
}
Copy the code
The solution
Configure automatic static resource mapping in the application-dev.properties file
spring.resources.add-mappings=true
Copy the code
Or manually configure resource mappings
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
super.addResourceHandlers(registry);
}
Copy the code
Java Basics
The PreparedStatement object has addBatch() and executeBatch() methods, which are used for batch inserts.
Connection conn = DBUtil.getConn();
String sql = "insert into miaosha_user(login_count, nickname, register_date, salt, password, id)values(? ,? ,? ,? ,? ,?) ";
PreparedStatement pstmt = conn.prepareStatement(sql);
for (int i = 0; i < users.size(); i++) {
MiaoshaUser user = users.get(i);
pstmt.setInt(1, user.getLoginCount());
pstmt.setString(2, user.getNickname());
pstmt.setTimestamp(3, new Timestamp(user.getRegisterDate().getTime()));
pstmt.setString(4, user.getSalt());
pstmt.setString(5, user.getPassword());
pstmt.setLong(6, user.getId());
pstmt.addBatch();
}
pstmt.executeBatch();
pstmt.close();
conn.close();
Copy the code
IsAssignableFrom (), check whether Class1 and Class2 are the same, and check whether Class1 is a Class2 interface or its parent.
Class1.isAssignableFrom(Class2)
Copy the code
Cmazxiaoma instance of Object: isAssignableFrom(); isAssignableFrom()
JSR303 is a specification for data validation, using mobile phone number validation as an example
Define the @isMobile annotation, which will be validated by the IsMobileValidator class.
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {IsMobileValidator.class})
public @interface IsMobile {
boolean required() default true;
String message() default "Mobile phone number format error"; Class<? >[] groups() default {}; Class<? extends Payload>[] payload() default {}; }Copy the code
Define a mobile phone number validation class that throws a BindException if the validation fails
public class IsMobileValidator implements ConstraintValidator<IsMobile, String> {
private boolean required = false;
@Override
public void initialize(IsMobile isMobile) {
required = isMobile.required();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
if (required) {
return ValidatorUtil.isMobile(value);
} else {
if (StringUtils.isEmpty(value)) {
return true;
} else {
returnValidatorUtil.isMobile(value); }}}}Copy the code
Failing validation throws a BindException, which we catch in the global exception catcher.
@ControllerAdvice
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(value = Exception.class)
public Result<String> exceptionHandler(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception e) {
log.error(e.getMessage());
if (e instanceof GlobalException) {
GlobalException ex = (GlobalException) e;
return Result.error(ex.getCm());
} else if (e instanceof BindException) {
BindException ex = (BindException) e;
List<ObjectError> errors = ex.getAllErrors();
ObjectError error = errors.get(0);
String msg = error.getDefaultMessage();
return Result.error(CodeMsg.BIND_ERROR.fillArgs(msg));
} else {
returnResult.error(CodeMsg.SERVER_ERROR.fillArgs(e.getMessage())); }}}Copy the code
Stern said
Every time when browsing blog, see the place that do not understand, must take a small notebook to remember. And then I put it in simple books, and over time, quantitative change leads to qualitative change.