Java development technology (3) E-commerce project optimization, rabbitmq, Git, OSI, VIM, Intellj IDEA, HTTP, JS, Java

Keywords: Java Nginx Redis RabbitMQ

Preface

Recently, the company let me maintain Spring+Servlet+Hibernate+Spring Security+Jsp old projects, just to exercise my business logic and project control ability. Although the project is very old, there are still many places worth learning.

E-commerce project optimization

1. Our main optimizations are the second kill interface: redis pre-empts inventory, reduces database access; memory tags less redis access; rabbitmq queue buffer, asynchronous ordering, and enhances user experience. Then the specific steps are as follows.

1. The Controller handling the secondkill service is ready to load in the Spring container cycle. That is to say, to implement InitializingBean, in the afterProperties Set () method to load the inventory of goods into redis, and set the flag in memory to set whether the goods end in seconds.

    /**
     * Memory Markup Initialization
     * @throws Exception
     */
    @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);
        });
    }

2. When the background receives a second kill request, it first looks at the memory flag tag, and then reduces the inventory of goods in redis. If the second kill ends, set the flag of the end of the second kill in memory. If the commodity second killing is still in progress, then go to the next step.

3. Buffer the message of the second-kill commodity into the queue and return it directly. This is not a successful return, but a return to the queue. At this point, the front desk can not directly prompt the success of the second kill, but start the timer, and then check whether it is successful after a period of time.

4. Message queue, modify the stock in db, create second kill order.

2. The solution of distributed Session is to generate a unique token, token identifies users, writes token into Cookie, and then writes token + user information into Redis. The failure time of token in redis is consistent with that of Cookie. Whenever a user logs in, the Session and Cookie validity periods are delayed.

3. From the caching point of view, we can optimize it by page caching + URL caching + object caching. We can manually render the Thymeleaf template and cache the product details page and the product list page into redis. Here we use the product 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);
        //Cache
        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;
    }

4. From the perspective of static resources, we carry out page static, front-end and back-end separation, static resource optimization, CDN node optimization. Here we use static resource optimization as an example.

1.JS/CSS compression, reduce traffic.
2. Multiple JS/CSS combinations to reduce the number of connections
3.CDN accesses nearby to reduce request time.
4. Cache some interfaces into the user's browser.

5. Safety optimization. The password is salt twice, and the first salt is fixed, written in Java code. The second salt addition is random and stored in a database. In the commodity second killing page, add the mathematical formula verification code to disperse the user's requests. A current-limiting and brush-proof mechanism is added to the interface. Here, an example is given to illustrate the current-limiting and brush-proof mechanism of the interface.

1. Define AccessLimit annotations to act on methods.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AccessLimit {

    int seconds();

    int maxCount();

    boolean needLogin() default true;
}

2. Define the Access Interceptor interceptor and obtain the parameters in the Access Limit annotation of the method. The reqeusturi of the request is used as the key in redis, and seconds as the failure time of the key. Each request adds 1. If the number of visits to the url exceeds the set maxCount within a specified time, then return "too frequent access".

    @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;
    }

6. Deployment optimization. LVS+Keepalived dual hot standby mode + Nginx+Tomcat.

Intelli J IDEA Use Skills

1. Global Search Ctrl + Shift + F
2. Global Replacement Ctrl+Shift+R

Vim Editor Use Skills

1. Search in vim editor.

1. Command mode input "/ string", such as "/ xiaoma"
2. If you continue to search for the next one, press n.

Redis Sets Password

1. Because spring.redis.password= is configured in application-dev.properties, if requirepass ${password} is not set in redis.conf, the console throws a connection rejection exception.

HTTP

Usage of Cache Control

no cache: Forces each request to be sent directly to the source server without having to be verified by the local cache version.
Max-age > 0: Extract directly from the browser cache.

RabbitMQ

1.AMQP(Advance Message Queuing Protocol) is an application-level standard advanced message queuing protocol that provides unified messaging services.

Exchange acts as a switch in RabbitMQ, which is also equivalent to routing. Of course, it can also be imagined as RabbitMQ filter. RabbitMQ has four modes.


rabbitmq Diagram - This diagram is from the Internet

1.Direct: Divide it into the specified Queue according to Routing Key.
2.Topic: It's similar to Direct, but it can match multiple keywords.
3.Fanout: No Routing Key concept, equivalent to broadcast mode, distributes messages to all Queue s bound to Fanout Exchange.
4.Header: Unlike the three above, match by adding the attribute key-value.

3. Write RabbitMQ code

Four modes of configuring 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 Pattern
     * @return
     */
    @Bean
    public Queue queue() {
        return new Queue(QUEUE, true);
    }

    @Bean
    public Queue miaoshaoQue() {
        return new Queue(MQConfig.MIAOSHA_QUEUE, true);
    }

    /**
     * Topic Pattern
     * @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 Pattern
     * @return
     */
    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange(FANOUT_EXCHANGE);
    }

    @Bean
    public Binding fanoutBinding1() {
        return BindingBuilder.bind(topicQueue1())
                .to(fanoutExchange());
    }

    @Bean
    public Binding fanoutBinding2() {
        return BindingBuilder.bind(topicQueue2())
                .to(fanoutExchange());
    }

    /**
     * Header Pattern
     * @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");
        return BindingBuilder.bind(headerQueue1()).to(headersExchange())
                .whereAll(map).match();
    }
}

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);
    }

}

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;
        }
        //Judge if you've killed seconds
        MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(miaoshaUser.getId(), goodsId);

        if (!Objects.isNull(order)) {
            return;
        }
        //Reduce inventory and write orders in seconds
        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));
    }
}

Controller for testing RabbitMQ

/**
 * @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");
    }
}

Nginx

Nginx's commands were not written for a while, and they were always forgotten. Let's write it down in a brief book.
Start: / usr/local/nginx/sbin/nginx-C/usr/local/nginx/conf/nginx.conf
Close: /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 will hand over the next request to other Real Server s for processing. Fail_timeout is the waiting time for failure. When the request is found to be unsuccessful, wait for the time of failure_timeout to request again to determine whether it is successful or not.

Git's confusing knowledge points

Workspace: Includes files that have actually changed. Current changes to files that have not yet been add ed to the temporary storage area include file change information.
Temporary Storage Area: Temporary Storage File Change Information

git reset filename: Clear the changes to the filename file submitted to the temporary area by the add command.
git checkout --filename: Revokes modifications to the workspace.

Basic knowledge of JS

As we all know, Java has three main features: encapsulation, inheritance and polymorphism. We can inject these three features into Java objects with protoType of JS.

<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("encapsulation");
    },
    extend: function() {
        console.log("inherit");
    }
};
java = new Java();
java.oriented();
java.fengzhuang();
java.extend();
</script>

Spring MVC Cold Door Annotation

1.produces="text/html" means that the method will generate data in "text/html" format and respond to the ContentType of the bar. Before we write the message back to the response, we call addDefaultHeaders() to set the ContentType and ContentLength attributes in the response bar.

    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);
            }
        }
    }

2.@ResponseBody This annotation is used to write the return object of the method in Controller to the body data area of the response object (HttpOutputMessage) after converting the specified format through the appropriate HttpMessageConverter according to the content of Accept in the request header in HttpRequest. If consume is specified as "application/json" in the method, then the method only handles requests with the ContentType attribute value of "application/json" in the request header.


image.png

3. Determine whether a method has a specified annotation, whether a method has a specified annotation on its class, and whether a method has a specified annotation on its parameters.

parameter.hasParameterAnnotation(RequestBody.class)
AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class)
returnType.hasMethodAnnotation(ResponseBody.class)

4.@ModelAttribute

1. Applying the parameters of the method, the parameters passed by the client are injected into the specified object by name, and the object is automatically added to the model Map to facilitate the call of the view layer.

2. Using this method, it will be executed before each @RequestMapping annotated method. If there is a return value, it will be automatically added to the model Map. I'm usually used to encapsulate 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;
    }
}

5. Timing tasks, we annotate @EnableScheduling in the WebApplication class to start the timing tasks. The parameters of cron expression from left to right are seconds, minutes, hours, days, months, weeks and years, respectively. See this website for detailed usage of cron expressions 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"));
    }
}

6. To turn on the asynchronous task, 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 is time-consuming:" + (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 is time-consuming:" + (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 is time-consuming:" + (end - start));
        return new AsyncResult<>((true));
    }
}

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 = "Complete tasks, total time-consuming:" + (end - start) + "Millisecond";
        return Result.success(times);
    }
}

We can see that the total time consumed by 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 consumed should be more than 6000 Ms.

{"code":0,"data":"Complete tasks, total time-consuming:3005 Millisecond","msg":""}

7.SpringBoot is deployed to external Tomcat to configure the pom file. Setting the Tomcat scope to provided indicates that it is only used at compiler and test time because we deploy to external Tomcat, which is supported by external Tomcat during run time.

        <!--spring boot tomcat
        By default, you don't need to configure, but you need to configure when you need to deploy the current web application to an external servlet container.
        And configure scope as provided
        When the default jar boot is required, the provided is removed and provided indicates that it is only used at compile time and test time - >.
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>

Remember to change the packaging from jar to war

    <groupId>com.cmazxiaoma</groupId>
    <artifactId>seckillSystem</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

Rewrite the Spring Application startup class, and I recreate a class here called WebApplication

@SpringBootApplication
//Open Timing Tasks
@EnableScheduling
//Open Asynchronous Call Method
@EnableAsync
public class WebApplication extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(WebApplication.class);
    }
}

Then Build Artifacts.


image.png

OSI

OSI is open system interconnection, English is 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 (International Control Message Protocol), ARP (Address Resolution Protocol), RARP (Anti-Address Resolution Protocol)
Data Link Layer= PPP (Point-to-Point Protocol)

Exceptions thrown by HttpMessageConverter

When I request / login/to_login, I return to the login view, and the login interface loads the background image. At this point, we did not configure the resource mapping, resulting in background images requesting the back-end Controller. If the appropriate Controller is not found to process the request, the global exception trap is entered into exception handling. In the writeWithMessageConverters() method in RequestResponseBodyMethodProcessor, we call the getProducibleMediaTypes() method to get all the return message format types of the request.

        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);
        }

Since we do not explicitly set the produces attribute in the global exception trap Handler Mapping, we can only find the HttpMessageConverter that supports parsing Java objects by traversing all HttpMessageConverters, using canWrite() method, and add its supported mediaType to the set of mediaTypes.

    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 {
            return Collections.singletonList(MediaType.ALL);
        }
    }

We conclude that producibleMediaTypes are all about "application/json" format. We cycle twice to compare requestedMediaTypes with producibleMediaTypes and get compatible compatibleMediaTypes. If neither the request message format nor the return message format matches, an HttpMediaTypeNotAcceptable Exception exception is thrown.


image.png
        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;
        }
Solution

Configuring static resource automatic mapping in application-dev.properties file

spring.resources.add-mappings=true

Or manually configure resource mapping

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
        super.addResourceHandlers(registry);
    }

Basic knowledge of Java

PreparedStatement objects have addBatch(), executeBatch() methods for bulk insertion.

        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();

isAssignableFrom() is used to determine whether Class1 is the same as Class2, whether Class1 is the interface of Class2 or its parent class.

Class1.isAssignableFrom(Class2)

Instance of is easily confused with isAssignableFrom(), which uses cmazxiaoma instance of Object as an example to determine whether an object instance is an instance of a class or interface, or an instance of its parent or child interface.

JSR303 usage

JSR303 is a data validation specification. Here is an example of mobile phone number validation.

Define the @IsMobile annotation, which is 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 {};

}

Define the mobile phone number validation class. If the validation fails, the BindException will be thrown.

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 {
                return ValidatorUtil.isMobile(value);
            }
        }
    }
}

A BindException is thrown if the validation fails, and we catch this exception in the global exception trap.


@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 {
            return Result.error(CodeMsg.SERVER_ERROR.fillArgs(e.getMessage()));
        }
    }
}

Tail speech

Every time you visit a blog, when you see something you don't understand, you must remember it in a small book. Then it is arranged on the brief book, which accumulates over time, and quantitative changes lead to qualitative changes.

Posted by szalinski on Wed, 12 Dec 2018 03:57:06 -0800