手写Spring-MVC之DispatcherServlet无组件版

Day47

手写Spring-MVC之DispatcherServlet

DispatcherServlet的思路:

前端传来URI,在TypeContainer容器类中通过uri得到对应的类描述类对象(注意:在监听器封装类描述类对象的时候,是针对于每一个URI进行封装的,也就是说每一个方法(uri)都有一个类描述类),通过类描述类对象获得类对象和方法描述类对象,再通过方法描述类对象获得方法对象和参数描述类对象。对参数描述类对象进行处理获得参数数组,然后通过method.invoke()执行方法,并返回值,最后处理返回值。

public class DispatherServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charSet=UTF-8");

        String uri = request.getRequestURI();
        HashMap<String, BeanDefinition> maps = TypeContainer.getMaps();
        BeanDefinition beanDefinition = maps.get(uri);
        if(beanDefinition==null){
            //System.out.println("没找到该uri下的类描述类");
            throw new FrameWorkException(ResponseCode.REQUEST_PATH_EXCEPTION.getCode(),ResponseCode.REQUEST_PATH_EXCEPTION.getMessage());
        }
        Object t = beanDefinition.getT();
        MethodDefinition methodDefinition = beanDefinition.getMethodDefinition();
        Method method = methodDefinition.getMethod();
        method.setAccessible(true);
        Model model = new Model();
        List<ParameterDefinition> parameterDefinitions = methodDefinition.getParameterDefinitions();
        Object[] args = handlerParameterArgs(parameterDefinitions, request, response,model);
        try{
            //调用Controller层里的某个方法
            Object returnVal = method.invoke(t, args);

            if(returnVal!=null){
                //处理返回值
                handlerReturnVal(methodDefinition,returnVal,request,response,model);
            }
        }catch (Exception e){
            System.out.println("出现异常了,处理全局异常");
        }


    }
    public void handlerReturnVal(MethodDefinition methodDefinition,Object returnVal,HttpServletRequest request,HttpServletResponse response,Model model) throws ServletException, IOException {

        if(methodDefinition.isResponseBodyHasOrNot()){
            //返回数据为JSON格式
            String jsonString = JSON.toJSONString(returnVal);
            sendResponse(response,jsonString);

        }
        else if(returnVal.getClass()==String.class){
            //如果返回值是字符串,先添加请求数据,再跳转(转发)

            handlerRequestVal(request,model.getMaps());
            jumpPage(returnVal,request,response);
        } else if (returnVal.getClass() == ModelAndView.class) {
            //如果返回值是ModelAndView,添加请求后跳转(转发)
            ModelAndView modelAndView = (ModelAndView) returnVal;
            handlerRequestVal(request,modelAndView.getMaps());
            jumpPage(modelAndView.getViewName(),request,response);

        }
    }
    public void sendResponse(HttpServletResponse response,String jsonString) throws IOException {
        response.getWriter().write(jsonString);
    }
    public void handlerRequestVal(HttpServletRequest request,Map<String,Object> maps){
        Set<Map.Entry<String, Object>> entries = maps.entrySet();
        for (Map.Entry<String, Object> entry : entries) {
            String key = entry.getKey();
            Object value = entry.getValue();
            request.setAttribute(key,value);


        }
    }

    public void jumpPage(Object returnVal,HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
        String str = (String) returnVal;
        request.getRequestDispatcher(str).forward(request,response);
    }
    public Object[] handlerParameterArgs(List<ParameterDefinition> parameterDefinitions,HttpServletRequest request,HttpServletResponse response,Model model){
        if(parameterDefinitions==null){
            return null;
        }
        Object[] args = new Object[parameterDefinitions.size()];
        for (ParameterDefinition parameterDefinition : parameterDefinitions) {

            Class<?> clazz = parameterDefinition.getClazz();//获取参数的class对象
            String name = parameterDefinition.getName();//获取参数的名字
            int index = parameterDefinition.getIndex();//获取参数的下标
            if(judgeTypeIsJavaOrNot(clazz)){//判断是否是Java常用数据类型
                args = handlerJavaType(request, name, clazz, args, index);
            } else if (clazz==HttpServletRequest.class) {//判断是否是请求类型
                args[index] = request;
            } else if (clazz==HttpServletResponse.class) {//判断是否是响应类型
                args[index] = response;
            } else if (clazz == String[].class) {//判断是否是数组类型
                String[] arr = request.getParameterValues(name);
                args[index] = arr;
            } else if (clazz == List.class) {//判断是否是List集合类型
                handlerListType(request,parameterDefinition,args,index);

            } else if (clazz== Model.class) {//判断是否是Model类型
                args[index] = model;
            }else{//判断是否是自定义类型
                handlerOtherType(parameterDefinition,request,args,index);
            }
        }
        return args;
    }

    public void handlerOtherType(ParameterDefinition parameterDefinition,HttpServletRequest request,Object[] args,int index){
        try {
            if(parameterDefinition.isRequestBodyHasOrNot()){
                //如果返回的是JSON格式数据
                BufferedReader br = request.getReader();
                StringBuffer sb = new StringBuffer();
                char[] cs = new char[1024];
                int len;
                while((len= br.read(cs))!=-1){
                    sb.append(cs,0,len);
                }
                String jsonStr = sb.toString();
                Object obj = JSON.parseObject(jsonStr,parameterDefinition.getClazz());
                args[index] = obj;


            }else{
                Object obj = parameterDefinition.getClazz().newInstance();
                Map<String, String[]> parameterMap = request.getParameterMap();
                BeanUtils.populate(obj,parameterMap);
                args[index] = obj;
            }



        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | IOException e) {
            throw new RuntimeException(e);
        }
    }
    public void handlerListType(HttpServletRequest request,ParameterDefinition parameterDefinition,Object[] args,int index){
        Map<String, String[]> parameterMap = request.getParameterMap();
        //key --- Value
        //user[0].username --- new String[]{"pengyuyan"};
        //user[0].password --- new String[]{"123123"};
        //user[1].username --- new String[]{"wuyanzu"};
        //user[1].password --- new String[]{"123456"};
        

        //获取泛型类型
        Type pt = parameterDefinition.getActualTypeArguments()[0];
        String className = pt.getTypeName();
        Class<?> aClass = null;
        try {
            aClass = Class.forName(className);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

        ArrayList<Object> list = new ArrayList<>();
        Set<Map.Entry<String, String[]>> entries = parameterMap.entrySet();
        for (Map.Entry<String, String[]> entry : entries) {
            String key = entry.getKey();//user[0].username
            String[] value = entry.getValue();//new String[]{"guodan"}
            int i = Integer.parseInt(key.substring(key.indexOf("[") + 1, key.indexOf("]")));//集合中的下标
            String fieldName = key.substring(key.indexOf(".") + 1);//集合中元素对象的属性名
            String fieldValue = value[0];//获取集合中元素对象里的属性值

            Object o =null;
            try{
                o = list.get(i);
            }catch (IndexOutOfBoundsException e){
                //该集合下标上没有元素
                try {
                    o = aClass.newInstance();//创建对象
                    list.add(o);//添加对象
                } catch (InstantiationException | IllegalAccessException ex) {
                    throw new RuntimeException(ex);
                }

            }
            try {
                    BeanUtils.setProperty(o,fieldName,fieldValue);//设置对象属性
                } catch (IllegalAccessException | InvocationTargetException ex) {
                    throw new RuntimeException(ex);
                }
                args[index] = list;


        }
    }
    public Object[] handlerJavaType(HttpServletRequest request,String name,Class<?> clazz,Object[] args,int index){
        String parameter = request.getParameter(name);
        if(clazz==byte.class || clazz==Byte.class){
            args[index] =  Byte.parseByte(parameter);
        }else if(clazz==short.class || clazz==Short.class){
            args[index] = Short.parseShort(parameter);
        }else if(clazz==int.class || clazz==Integer.class){
            args[index] = Integer.parseInt(parameter);
        }else if(clazz==long.class || clazz==Long.class){
            args[index] = Long.parseLong(parameter);
        }else if(clazz==float.class || clazz==Float.class){
            args[index] = Float.parseFloat(parameter);
        }else if(clazz==double.class || clazz==Double.class){
            args[index] = Double.parseDouble(parameter);
        }else if(clazz==char.class || clazz==Character.class){
            args[index] = parameter.toCharArray()[0];
        }else if(clazz==boolean.class || clazz==Boolean.class){
            args[index] = Boolean.parseBoolean(parameter);
        }if(clazz==String.class){
            args[index] = parameter;
        }
        return args;
    }
    public boolean judgeTypeIsJavaOrNot(Class<?> clazz){
        if(clazz==byte.class || clazz==Byte.class || clazz==short.class || clazz==Short.class || clazz==int.class || clazz== Integer.class || clazz==long.class || clazz== Long.class || clazz==float.class || clazz== Float.class || clazz==double.class || clazz==Double.class || clazz==char.class || clazz== Character.class || clazz==boolean.class || clazz==Boolean.class || clazz==String.class){
            return true;
        }
        return false;
    }


}

其中的细节和难点:

1.处理参数

处理参数的思路

获取参数的class对象、参数的名字、参数的下标。然后根据参数名利用请求从前端获取对应的参数对象并添加到参数数组args[]中,位置为对应下标的位置。

其中需要根据不同的参数类型分别进行处理

如果是Java常用数据类型(8大基本数据类型+string),则进行装箱处理(String 直接添加),然后添加到参数数组args[]中;

public Object[] handlerJavaType(HttpServletRequest request,String name,Class<?> clazz,Object[] args,int index){
        String parameter = request.getParameter(name);
        if(clazz==byte.class || clazz==Byte.class){
            args[index] =  Byte.parseByte(parameter);
        }else if(clazz==short.class || clazz==Short.class){
            args[index] = Short.parseShort(parameter);
        }else if(clazz==int.class || clazz==Integer.class){
            args[index] = Integer.parseInt(parameter);
        }else if(clazz==long.class || clazz==Long.class){
            args[index] = Long.parseLong(parameter);
        }else if(clazz==float.class || clazz==Float.class){
            args[index] = Float.parseFloat(parameter);
        }else if(clazz==double.class || clazz==Double.class){
            args[index] = Double.parseDouble(parameter);
        }else if(clazz==char.class || clazz==Character.class){
            args[index] = parameter.toCharArray()[0];
        }else if(clazz==boolean.class || clazz==Boolean.class){
            args[index] = Boolean.parseBoolean(parameter);
        }if(clazz==String.class){
            args[index] = parameter;
        }
        return args;
    }
    public boolean judgeTypeIsJavaOrNot(Class<?> clazz){
        if(clazz==byte.class || clazz==Byte.class || clazz==short.class || clazz==Short.class || clazz==int.class || clazz== Integer.class || clazz==long.class || clazz== Long.class || clazz==float.class || clazz== Float.class || clazz==double.class || clazz==Double.class || clazz==char.class || clazz== Character.class || clazz==boolean.class || clazz==Boolean.class || clazz==String.class){
            return true;
        }
        return false;
    }

如果是请求/响应类型的数据则直接添加到数组中;

如果是数组,则获取的是数组对象,并添加到args[]中;

String[] arr = request.getParameterValues(name);
args[index] = arr;

如果是List集合类型(前端设计页面的时候会规范返回的格式),则要先通过获取泛型(这里需要回到参数描述类中添加泛型类型这一属性)来获取集合里面元素的类型,然后获得传入的数据map,遍历每行数据拼接出元素的名字、下标和属性值。然后通过元素的类型利用反射创建类对象,创建List集合并添加对象,设置对象属性值(注意:这里有一个小技巧,利用try-catch先获取空集合里面的元素,在出现没有元素的异常后在catch中创建对象,并添加到list中。这样在同一个元素下标中的list位置就有了对象,就不会进入到异常处理中创建对象,而是在后续进行属性设置,直到下标改变再重新创建对象。),最后将List集合添加到args[]中;

参数类型描述类添加泛型属性(相应的需要修改监听器封装部分的代码):

@NoArgsConstructor
@AllArgsConstructor
@Data
public class ParameterDefinition {
   private String name;//参数名
   private Class<?> clazz;//参数类型
   private int index;//参数下标
   private Type[] actualTypeArguments;//参数泛型的数组
   

}

监听器相关部分:

if(parameters.length!=0){
    for (int i = 0; i < parameters.length; i++) {
        //获取参数上泛型数组
        Type parameterizedType = parameters[i].getParameterizedType();
        Type[] actualTypeArguments = null;

        try{
            ParameterizedType pt = (ParameterizedType) parameterizedType;
            if(pt!=null){
                actualTypeArguments = pt.getActualTypeArguments();
            }
        }catch (Exception e){

        }

        String parameterName = parameters[i].getName();//获取参数名
        Class<?> parameterType = parameters[i].getType();//参数类型
        int index = i;//下标
        ParameterDefinition parameterDefinition = new ParameterDefinition(parameterName, parameterType, index,actualTypeArguments);//封装参数描述类对象
        parameterList.add(parameterDefinition);
    }
}

DispatcherServlet:

 public void handlerListType(HttpServletRequest request,ParameterDefinition parameterDefinition,Object[] args,int index){
        Map<String, String[]> parameterMap = request.getParameterMap();
        //key --- Value
        //user[0].username --- new String[]{"pengyuyan"};
        //user[0].password --- new String[]{"123123"};
        //user[1].username --- new String[]{"wuyanzu"};
        //user[1].password --- new String[]{"123456"};
        

        //获取泛型类型
        Type pt = parameterDefinition.getActualTypeArguments()[0];
        String className = pt.getTypeName();
        Class<?> aClass = null;
        try {
            aClass = Class.forName(className);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

        ArrayList<Object> list = new ArrayList<>();
        Set<Map.Entry<String, String[]>> entries = parameterMap.entrySet();
        for (Map.Entry<String, String[]> entry : entries) {
            String key = entry.getKey();//user[0].username
            String[] value = entry.getValue();//new String[]{"guodan"}
            int i = Integer.parseInt(key.substring(key.indexOf("[") + 1, key.indexOf("]")));//集合中的下标
            String fieldName = key.substring(key.indexOf(".") + 1);//集合中元素对象的属性名
            String fieldValue = value[0];//获取集合中元素对象里的属性值

            Object o =null;
            try{
                o = list.get(i);
            }catch (IndexOutOfBoundsException e){
                //该集合下标上没有元素
                try {
                    o = aClass.newInstance();//创建对象
                    list.add(o);//添加对象
                } catch (InstantiationException | IllegalAccessException ex) {
                    throw new RuntimeException(ex);
                }
                try {
                    BeanUtils.setProperty(o,fieldName,fieldValue);//设置对象属性
                } catch (IllegalAccessException | InvocationTargetException ex) {
                    throw new RuntimeException(ex);
                }
                args[index] = list;
            }


        }
    }

如果是Model类型(model需要创建,属性只有一个map,用来存放属性的名和值,同时有一个setAttribute()方法,即添加键值。model是用来跳转页面的,将前端的数据封装到自己的容器属性中,然后通过请求传递给前端。同时创建一个ModelAndView类,除了容器属性外还有一个字符串类型的路径,功能和model类似,但适用于需要动态指定跳转页面的复杂业务),则直接添加到args[]中;

public class Model {
    private Map<String,Object> maps= new ConcurrentHashMap<>();
    public void setAttributes(String key,Object value){maps.put(key,value);}

    public Map<String, Object> getMaps() {
        return maps;
    }
}

public class ModelAndView {
    private Map<String,Object> maps = new HashMap<>();
    private String viewName;
    public void setAttributes(String key,Object value){
        maps.put(key,value);
    }

    public Map<String, Object> getMaps() {
        return maps;
    }

    public void setMaps(Map<String, Object> maps) {
        this.maps = maps;
    }

    public String getViewName() {
        return viewName;
    }

    public void setViewName(String viewName) {
        this.viewName = viewName;
    }
}

如果是自定义类型,则用反射创建对象,将数据利用BeanUtils工具类的populate方法将参数整合到类对象中,并添加到args[]中。

public void handlerOtherType(Class<?> clazz,HttpServletRequest request,Object[] args,int index){
        try {
            Object obj = clazz.newInstance();
            Map<String, String[]> parameterMap = request.getParameterMap();
            BeanUtils.populate(obj,parameterMap);
            args[index] = obj;

        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }
2.处理返回值

思路

如果返回值不为空,即该方法有返回值,则要对返回值的类型进行判断,然后添加请求数据,再进行转发。

不同类型返回值的处理:

这里返回值有两种情况,本质是跳转逻辑的不同。一种是字符串类型的路径,controller层会将前端的数据封装到Model对象中,servlet就获取model的map属性,遍历里面的数据利用请求设置到页面,而返回的字符串路径直接用来跳转,一种是ModelAndView类型的对象,同样遍历map属性,将属性通过请求设置到页面,并利用类里面的路径属性进行跳转。

public void handlerReturnVal(Object returnVal,HttpServletRequest request,HttpServletResponse response,Model model) throws ServletException, IOException {
        if(returnVal.getClass()==String.class){
            //如果返回值是字符串,先添加请求数据,再跳转(转发)

            handlerRequestVal(request,model.getMaps());
            jumpPage(returnVal,request,response);
        } else if (returnVal.getClass() == ModelAndView.class) {
            //如果返回值是ModelAndView,添加请求后跳转(转发)
            ModelAndView modelAndView = (ModelAndView) returnVal;
            handlerRequestVal(request,modelAndView.getMaps());
            jumpPage(modelAndView.getViewName(),request,response);

        }
    }
	//遍历Model/ModelAndView类对象的容器属性,把属性数据添加到请求
    public void handlerRequestVal(HttpServletRequest request,Map<String,Object> maps){
        Set<Map.Entry<String, Object>> entries = maps.entrySet();
        for (Map.Entry<String, Object> entry : entries) {
            String key = entry.getKey();
            Object value = entry.getValue();
            request.setAttribute(key,value);


        }
    }
	//页面跳转
    public void jumpPage(Object returnVal,HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
        String str = (String) returnVal;
        request.getRequestDispatcher(str).forward(request,response);
    }
3.处理JSON数据

如果接收的是JSON格式数据,那么如何识别数据以及怎样传递数据呢?

**思路:**参数注解用来识别判断客户端传递过来的数据是否是JSON数据,需要将JSON格式的字符串解析成参数类型的数据;方法注解表示服务器传输过去的数据为JSON数据,需要将返回值解析成JSON格式的字符串。

具体实现:添加RequestBody注解用在参数上,和ResponseBody注解用在方法上。

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestBody {
}


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ResponseBody {
}

DispatcherServlet中在创建参数数组的时候判断参数上是否有注解,所以要在参数描述类中添加用来判断是否参数有注解的布尔值。在方法描述类中添加布尔值判断方法上是否有注解。(相应的要修改监听器中封装类的那部分代码)

参数描述类和方法描述类修改:

@NoArgsConstructor
@AllArgsConstructor
@Data
public class ParameterDefinition {
    private String name;//参数名
    private Class<?> clazz;//参数类型
    private int index;//参数下标
    private Type[] actualTypeArguments;//参数泛型的数组
    private boolean requestBodyHasOrNot;//参数上是否有@RequestBody注解

}


@NoArgsConstructor
@AllArgsConstructor
@Data
public class MethodDefinition {

    private String requestMappingPath;//子级URi
    private String name;//方法名
    private Method method;//方法对象

    private Class<?> returnClazz;//返回值类型
    private List<ParameterDefinition> parameterDefinitions;//参数描述类对象的集合
    private boolean responseBodyHasOrNot;//方法上是否有@ResponseBody注解
}

监听器相关部分:

if(parameters.length!=0){
    for (int i = 0; i < parameters.length; i++) {
        //获取参数上泛型数组
        Type parameterizedType = parameters[i].getParameterizedType();
        Type[] actualTypeArguments = null;

        try{
            ParameterizedType pt = (ParameterizedType) parameterizedType;
            if(pt!=null){
                actualTypeArguments = pt.getActualTypeArguments();
            }
        }catch (Exception e){
        }

        String parameterName = parameters[i].getName();//获取参数名
        Class<?> parameterType = parameters[i].getType();//参数类型
        int index = i;//下标

        //获取参数上是否有@RequestBody注解
        boolean requestBodyHasOrNot = false;
        RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
        if(requestBody!=null){
            requestBodyHasOrNot = true;
        }
        ParameterDefinition parameterDefinition = new ParameterDefinition(parameterName, parameterType, index,actualTypeArguments,requestBodyHasOrNot);//封装参数描述类对象
        parameterList.add(parameterDefinition);
    }
}

//获取方法上是否有@ResponseBody注解
boolean responseBodyHasOrNot = false;
ResponseBody responseBody = method.getAnnotation(ResponseBody.class);
if(responseBody!=null){
    responseBodyHasOrNot = true;
}
MethodDefinition methodDefinition = new MethodDefinition(sonUri, methodName, method, returnType, parameterList,responseBodyHasOrNot);//封装方法描述类对象

servlet中在自定义类型中要判断是否有对应的注解,如果有就解析json数据封装成对象然后添加到数组。

public void handlerOtherType(ParameterDefinition parameterDefinition,HttpServletRequest request,Object[] args,int index){
    try {
        if(parameterDefinition.isRequestBodyHasOrNot()){
            //如果返回的是JSON格式数据
            BufferedReader br = request.getReader();
            StringBuffer sb = new StringBuffer();
            char[] cs = new char[1024];
            int len;
            while((len= br.read(cs))!=-1){
                sb.append(cs,0,len);
            }
            String jsonStr = sb.toString();
            Object obj = JSON.parseObject(jsonStr,parameterDefinition.getClazz());
            args[index] = obj;


        }else{
            Object obj = parameterDefinition.getClazz().newInstance();
            Map<String, String[]> parameterMap = request.getParameterMap();
            BeanUtils.populate(obj,parameterMap);
            args[index] = obj;
        }



    } catch (InstantiationException | IllegalAccessException | InvocationTargetException | IOException e) {
        throw new RuntimeException(e);
    }
}

在方法处理中判断是否有对应的方法注解,如果有就将返回的JSON转换为字符串写回前端。

public void handlerReturnVal(MethodDefinition methodDefinition,Object returnVal,HttpServletRequest request,HttpServletResponse response,Model model) throws ServletException, IOException {

        if(methodDefinition.isResponseBodyHasOrNot()){
            //返回数据为JSON格式
            String jsonString = JSON.toJSONString(returnVal);
            sendResponse(response,jsonString);

        }
        else if(returnVal.getClass()==String.class){
            //如果返回值是字符串,先添加请求数据,再跳转(转发)

            handlerRequestVal(request,model.getMaps());
            jumpPage(returnVal,request,response);
        } else if (returnVal.getClass() == ModelAndView.class) {
            //如果返回值是ModelAndView,添加请求后跳转(转发)
            ModelAndView modelAndView = (ModelAndView) returnVal;
            handlerRequestVal(request,modelAndView.getMaps());
            jumpPage(modelAndView.getViewName(),request,response);

        }
    }
    public void sendResponse(HttpServletResponse response,String jsonString) throws IOException {
        response.getWriter().write(jsonString);
    }
注意:

1.@WebServlet不要加,在web项目中配置。因为是在项目中使用到,而不是全局。

postman

代替浏览器发送请求的工具

请求里面输入url,body选择raw->JSON,发送一个JSON格式的字符串

测试能否成功。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/771220.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【postgresql】版本学习

PostgreSQL 17 Beta 2 发布于2024-06-27。 PostgreSQL 17 Beta 2功能和变更功能的完整列表&#xff1a;PostgreSQL: Documentation: 17: E.1. Release 17 ​ 支持的版本&#xff1a; 16 ( 当前版本) / 15 / 14 / 13 / 12 ​ 不支持的版本&#xff1a; 11 / 10 / 9.6 / 9.5 /…

「前端」快速排序算法演示

快速排序算法演示。 布局描述 一个简单的HTML页面,用户可以在其中输入一系列用逗号分隔的数字。 一个CSS样式表,提供了一个美观大方的布局和样式。 一个JavaScript脚本,实现了快速排序算法,并在用户点击按钮时对输入的数字进行排序,并显示结果。 效果演示 核心代码 <…

Django创建项目

虚拟环境创建成功 使用命令行创建项目 创建一个文件夹&#xff0c;用pycharm打开&#xff0c;将之前创建好的虚拟环境选中&#xff08;这一步不在仔细赘述了&#xff0c;比较简单&#xff09; cd进入虚拟环境所在文件目录&#xff0c;打开虚拟环境pipenv shell 创建django项…

身边有填报志愿需求别错过!张雪峰透露今年志愿填报技巧:报专业,别报行业!(文末附稳定高薪专业推荐)

高考填报志愿是每个考生和家长都要面对的重大抉择。在当前就业形势日趋严峻、部分行业发展前景不明朗的大背景下,考生在填报志愿时更需要全面了解各个专业的就业前景,理性权衡自身兴趣特长与社会需求&#xff0c;而不是盲目跟风报考所谓的"热门专业"。 今天跟大家分…

对谈 MoonBit:AI 时代的编程语言应该是什么样子的?丨编码人声

「编码人声」是由「RTE开发者社区」策划的一档播客节目&#xff0c;关注行业发展变革、开发者职涯发展、技术突破以及创业创新&#xff0c;由开发者来分享开发者眼中的工作与生活。 本期节目&#xff0c;我们请到了 MoonBit 的创始人宏波和资深的开发者狼叔作为我们的嘉宾&…

人工智能在日常生活中的十大应用:从医疗到智能家居

人工智能已成为当今人类日常生活的重要组成部分&#xff0c;无论您是否意识到&#xff0c;它几乎在所有场景中都能提供帮助。每次您进行网络搜索、在线预订旅行、接收来自京东等购物平台的产品推荐又或是打开您的新浪、抖音时&#xff0c;都能看到影子&#xff0c;这些只是一些…

Python番外篇之代码编译与字节码

引言 关于字节码&#xff0c;不太想讲&#xff0c;不影响实际使用&#xff0c;对新手不友好…… 但是&#xff0c;涉及到新手经常碰到的问题的解惑&#xff0c;似乎又不得不讲。 最终&#xff0c;还是打算以番外篇的形式&#xff0c;稍微提一下。 不过&#xff0c;关于字节码的…

RabbitMQ入门教程(精细版二带图)

目录 六 RabbitMQ工作模式 6.1Hello World简单模式 6.1.1 什么是简单模式 6.1.2 RabbitMQ管理界面操作 6.1.3 生产者代码 6.1.4 消费者代码 6.2 Work queues工作队列模式 6.2.1 什么是工作队列模式 6.2.2 RabbitMQ管理界面操作 6.2.3 生产者代码 6.2.4 消费者代码 …

RAID详解

一、RAID存储是什么&#xff1f; RAID 存储&#xff08;Redundant Arrays of Independent Disks&#xff0c;独立磁盘冗余阵列&#xff09;是一种通过将多个独立的物理磁盘组合在一起&#xff0c;以实现更高的存储性能、数据可靠性和容错能力的技术。其主要目的是解决单个磁盘…

tapd 与国内外主流的8大项目管理软件大对比

对比Tapd与8大项目管理工具&#xff1a;PingCode、Worktile、Redmine、Teambition、广联达、Jira、禅道、飞书。 Tapd 是腾讯推出的一款敏捷开发管理工具&#xff0c;特别适合那些需要高效协作和快速迭代的敏捷开发团队。它支持多种敏捷方法论&#xff0c;包括Scrum和Kanban&am…

liunx文件系统,日志分析

文章目录 1.inode与block1.1 inode与block概述1.2 inode的内容1.3 文件存储1.4 inode的大小1.5 inode的特殊作用 2.硬链接与软链接2.1链接文件分类 3.恢复误删除的文件3.1 案例:恢复EXT类型的文件3.2 案例:恢复XFS类型的文件3.2.1 xfsdump使用限制 4.分析日志文件4.1日志文件4.…

Android10以上实现获取设备序列号功能

Android10以上实现获取设备唯一标识&#xff0c;目前只支持华为和荣耀设备。实现原理&#xff1a;通过无障碍服务读取序列号界面。 public class DeviceHelper implements Application.ActivityLifecycleCallbacks {static final String TAG "WADQ_DeviceHelper";s…

无人机智能追踪反制系统技术详解

随着无人机技术的飞速发展&#xff0c;无人机在各个领域的应用越来越广泛。然而&#xff0c;无人机的无序飞行和非法使用也带来了一系列安全隐患和威胁。因此&#xff0c;无人机智能追踪反制系统应运而生&#xff0c;成为维护公共安全和防止无人机滥用的重要工具。本文将详细介…

SPI四种模式--极性与相位

SPI的四种模式&#xff1a;相位和极性 极性 定义时钟空闲状态&#xff1a; CPOL0&#xff1a;时钟线在空闲状态为低电平 CPOL1&#xff1a;时钟线在空闲状态为高电平 这个设置决定了设备不进行通信时时钟线的状态。 兼容性&#xff1a; 不同的SPI设备可能需要不同的时钟极性…

Spring Boot 的机场投诉管理平台-计算机毕业设计源码22030

摘要 随着航空运输业的迅速发展&#xff0c;机场的客流量不断增加&#xff0c;旅客对机场服务的质量和效率也提出了更高的要求。为了提高机场的服务质量&#xff0c;及时处理旅客的投诉&#xff0c;建立一个高效、便捷的机场投诉管理平台显得尤为重要。 本项目旨在设计与实现一…

飞利浦的台灯值得入手吗?书客、松下多维度横评大分享!

随着生活品质的持续提升&#xff0c;人们对于健康的追求日益趋向精致与高端化。在这一潮流的推动下&#xff0c;护眼台灯以其卓越的护眼功效与便捷的操作体验&#xff0c;迅速在家电领域崭露头角&#xff0c;更成为了众多家庭书房中不可或缺的视力守护者。这些台灯以其精心设计…

AIGC对设计师积极性的影响

随着科技的迅猛发展&#xff0c;生成式人工智能&#xff08;AIGC&#xff09;工具正逐渐深入设计的每个角落&#xff0c;对设计师的工作方式和思维模式产生了深远的影响。AIGC不仅极大提升了设计师的工作效率&#xff0c;更激发了他们的创新思维&#xff0c;为设计行业带来了翻…

java 基础之 反射技术_java 程序src阶段 class对象阶段 run阶段3个阶段

System.out.println(in); } publicClass[] aa1(String name, int[] password){ returnnew Class[]{String.class} ; } privatestatic void aa1(int num){ System.out.println(num“静态方法”); } public static void main(String[] args){ System.out.println(“main”…

MySQL单表千万级数据查询优化大家怎么说(评论有亮点)

题图来自APOD 上次写了一篇MySQL优化实战的文章“MySQL千万级数据从190秒优化到1秒全过程”。 这篇文章主要还是在实战MySQL优化&#xff0c;所以从造数据到查询SQL优化SQL都没有业务或者其它依赖&#xff0c;优化的技巧也不涉及软件架构就是纯SQL优化。 由于笔者经验有限和…

mysql:部署MySQL 8.0 环境

mysql网址&#xff1a;MySQL 点击 MySQL Community Server 选择合适的版本 选择8.0版本 下载完成&#xff0c;点击mysql-installer-community-8.0.26.0.msi文件&#xff0c;打开安装向导。 选择自定义安装类型 打开“Select Products” 窗口&#xff0c;可以定制需要安装的产…