Crystal_dan

Crystal_dan 查看完整档案

填写现居城市东南大学  |  计算机 编辑  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

Crystal_dan 发布了文章 · 8月15日

部署tomcat项目

mac上直接启动tomcat
切到目录apache-tomcat-8.5.32/bin
执行命令sh startup.sh
启动成功terminal上可以看到Tomcat started.

访问http://localhost:8080/ bingo

关闭tomcat的时候执行命令sh shutdown.sh

跑一个tomcat目录下的项目apache-tomcat-8.5.32/webapps

比如这个目录下有一个manager项目,里面提供了一个index.jsp
image.png

访问http://localhost:8080/manager/index.jsp

比如examples中配置的web.xml中有一条

 <servlet-mapping>
        <servlet-name>HelloWorldExample</servlet-name>
        <url-pattern>/servlets/servlet/HelloWorldExample</url-pattern>
    </servlet-mapping>

访问http://localhost:8080/examples/servlets/servlet/HelloWorldExample

idea 只含一个servlet的tomcat web
新建一个java web项目
new project -> java enterprise -> 选择tomcat作为application server -> 勾选create project from template

创建一个servlet,在web.xml 中配置相应的servlet配置

配置tomcat (配置过程省略),配置Deployment的时候Application context配置成和项目一样的名字

启动tomcat,访问http://localhost:8080/selfjavawebproj/myservlet
selfjavawebproj是web application的名字,myservet需要是在web.xml中配置的路径

tomcat的容器层级
tomcat的Servlet容器分为了四层,Engine->Host->Context->Wrapper
最内层的Wapper表示一个Servlet,Context表示一个应用程序,一个应用程序中会包含多个servlet,Host表示一个站点,一个站点下可以有多个应用程序,Engine表示引擎,用来管理多个站点。
在tomcat中的conf/server.xml中,可以配置多个service
以我的配置文件为例

<server>
  <Service name="Catalina">  //一个service下可以有多个connector,一个engine
 <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
 <Connector port="8009" protocol="AJP/1.3" redirectPort="8443"/>
 <Engine name="Catalina" defaultHost="localhost">//包含多个host
 <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
 </Engine>
 </service>
 <service>
 </service>
</server>

tomcat实现容器的时候定义了Container,里面定义了parent child这种角色,通过组合模式组装每一层级的容器

public interface Container extends Lifecycle {
    public void setName(String name);
    public Container getParent();
    public void setParent(Container container);
    public void addChild(Container child);
    public void removeChild(Container child);
    public Container findChild(String name);
}

http://localhost:8080/selfjavawebproj/myservlet
当发出这个请求时,
1 首先根据协议和端口号选择service和Engine,对应上面的配置,只有一个service且连接器支持HTTP1.1,端口8080,所以找到了name为Catalina的service和name为Catalina的Engine
2 根据访问的域名 localhost找到对应的host
3 根据url找到context,所以在前面配置的时候Application context配置的也是应用名称,在这里context就是selfjavawebproj
4 根据url找到Wrapper,即根据配置的路径在web.xml中找到对应的servlet实现类

但是并不是只有到了最后的Wrapper才真正开始处理请求,在中间的每一层都会对请求做这一层的处理,比如Engine先拿到请求,处理完Engine层的逻辑将请求给自己的child Host层,最后才到Wrapper ------ pipeline 责任链模式

具体在tomcat 这块是Pipeline-Valve 管道

Value表示一个处理点

public interface Valve {
  public Valve getNext();
  public void setNext(Valve valve);
  public void invoke(Request request, Response response)
}

Value中的invoke表示当前这个处理点的处理逻辑,getNext表示获取下一个处理点,getNext.invoke就触发了下一个处理点的处理逻辑

public interface Pipeline extends Contained {
  public void addValve(Valve valve);
  public Valve getBasic();
  public void setBasic(Valve valve);
  public Valve getFirst();
}

pipeline维护了这个value链,First是链的第一个,Basic是链的最后一个,Basic是关联下一级容器的第一个value
最后一级Wrapper的Basic指向Filter链,调用Filter链中doFilter方法,最后走到Servlet中的service方法

查看原文

赞 0 收藏 0 评论 0

Crystal_dan 发布了文章 · 8月10日

代理模式

一 代理模式

定义:在访问目标对象的时候,提供一种间接访问的方法,通过代理对象去访问目标对象,达到客户端的目的。

好处:保护目标对象;在目标对象的行为基础上,代理对象可以增加自己的处理,使得同一个目标对象可以满足不同的客户端的目的。

代理模式的模型:

image.png

涉及三个角色:

RealSubject:目标对象,也是真正要做事情的人。

Proxy:代理对象,持有对目标对象的引用,暴露给客户端,通过操作目标对象来完成客户端的目的。并且,代理对象可以在操作目标对象的前后做一些自定义的行为,灵活扩展了目标对象。

Subject:目标对象和代理对象的抽象,以便在任何使用目标对象的地方都可以使用代理对象。Subject可以是一个接口也可以是一个抽象类。

二 静态代理

以最简单的形式实现上述模型

public interface Subject {
    void doRequst();
}
public class RealSubject implements Subject {

    @Override
    public void doRequst() {
        System.out.println("it's the thing i really want to do");
    }
}
public class Proxy implements Subject {
    private Subject subject;

    public Proxy(Subject subject){
        this.subject = subject;
    }

    @Override
    public void doRequst() {
        System.out.println("before do the real thing");
        subject.doRequst();
        System.out.println("after do the real thing");
    }
}
public class Client {
    public static void main(String[] args) {
        Proxy proxy = new Proxy(new RealSubject());
        proxy.doRequst();
    }
}
/**
before do the real thing
it's the thing i really want to do
after do the real thing
**/

这种实现方式即常说的静态代理,有几个方面的约束:

1 代理对象和目标对象需要实现共同的接口,接口有变动,目标对象和代理对象都需要维护。

2 想要代理其他的目标对象,需要新增代理类。

三 动态代理

先看java实现动态代理的几个主要角色

1  Proxy :java.lang.reflect.Proxy,jdk api提供的代理类的主类,该类提供方法动态生成代理类,生成的时候需要指定一组接口(即代理类和目标对象要实现哪些接口)

public static Object newProxyInstance(ClassLoader var0, Class<?>[] var1, final InvocationHandler var2) throws IllegalArgumentException 
/**
提供了这个静态方法用于动态生成代理类
var0:   目标对象的类加载器
var1:  代理类要实现哪些接口,即目标对象实现的接口
var2:  处理器,代理类具体要做什么写在处理器中
**/

2 InvocationHandler :java.lang.reflect.InvocationHandler

/**
@param  proxy the proxy instance that the method was invoked on
代理对象,执行invoke的时候其实是在替这个proxy执行

@param  method the {@code Method} instance corresponding to
the interface method invoked on the proxy instance.  The declaring
class of the {@code Method} object will be the interface that
the method was declared in, which may be a superinterface of the
proxy interface that the proxy class inherits the method through.
目标对象要执行的方法

@param  args an array of objects containing the values of the
arguments passed in the method invocation on the proxy instance,
or {@code null} if interface method takes no arguments.
Arguments of primitive types are wrapped in instances of the
appropriate primitive wrapper class, such as
{@code java.lang.Integer} or {@code java.lang.Boolean}.
执行方法需要的参数
**/
 
public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable
 

java动态代理实例

public interface Subject {
    void doRequest();
}
public class RealSubject1 implements Subject {

    @Override
    public void doRequest() {
        System.out.println("RealSubject1 want to sing");
    }
}
public class RealSubject2 implements Subject {

    @Override
    public void doRequest() {
        System.out.println("RealSubject2 want to dance");
    }
}
public class RealInvocationHandler implements InvocationHandler {
    private Object subject;

    public RealInvocationHandler(Object subject){
        this.subject = subject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before process");
        Object result = method.invoke(subject,args);
        System.out.println("after process");
        return result;
    }
}
public class Client {
    public static void main(String[] args) {
        Subject realSubject1 = new RealSubject1();
        InvocationHandler handler1 = new RealInvocationHandler(realSubject1);
        Subject subject1 = (Subject) Proxy.newProxyInstance(realSubject1.getClass().getClassLoader(),realSubject1.getClass().getInterfaces(),handler1);
        subject1.doRequest();
        System.out.println("-----------------------");
        Subject realSubject2 = new RealSubject2();
        InvocationHandler handler2 = new RealInvocationHandler(realSubject2);
        Subject subject2 = (Subject)Proxy.newProxyInstance(realSubject2.getClass().getClassLoader(),realSubject2.getClass().getInterfaces(),handler2);
        subject2.doRequest();
    }
}
/**
before process
RealSubject1 want to sing
after process
-----------------------
before process
RealSubject2 want to dance
after process
**/

与静态代理做个比较:

不需要挨个写代理类,想代理其他的,动态new一个代理类

相关源码解析

Proxy.newProxyInstance生成的代理具备什么以及为什么可以转换成目标对象

protected InvocationHandler h;  
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    // 校验handler
    if (h == null) {
        throw new NullPointerException();
    }

    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /*
     * Proxy维护了一个proxyClassCache,如果想要的代理类已经有实现了指定接口的loader定义好了,直接返回cache的备份
     * 否则,通过ProxyClassFactory 生成一个代理类,实现指定的intfs
     */
    Class<?> cl = getProxyClass0(loader, intfs);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        //获取构造函数,将传进来的h赋给proxy内部持有的Invokerhanler对象
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
            // create proxy instance with doPrivilege as the proxy class may
            // implement non-public interfaces that requires a special permission
            return AccessController.doPrivileged(new PrivilegedAction<Object>() {
                public Object run() {
                    return newInstance(cons, ih);
                }
            });
        } else {
            return newInstance(cons, ih);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString());
    }
}

所以,Proxy生成的代理对象具备一个invokehandler对象的引用;并且实现了目标对象实现的接口,因为可以转换为目标对象的抽象

InvokeHandler为什么可以完成目标对象方法的执行

如上述例子中目标对象RealSubject1要执行doRequest方法,Proxy生成的代理类也会实现这个方法,大概如下:

 public final void doRequest()
 
    {
        //这个会调用invokehandler的invoke方法,这里的h即是父类Proxy的h,通过newProxyInstance传进来的
        try {
            this.h.invoke(this, m3, null);
            return;
        } catch (RuntimeException localRuntimeException) {
            throw localRuntimeException;
        } catch (Throwable localThrowable) {
            throw new UndeclaredThrowableException(localThrowable);
        }
 
    }

有了InvocationHandler 可以使Proxy从具体的代码逻辑抽离出来,更方便统一的生成代理类。

后续todo:

Proxy生成的代理对象的具体剖析

method.invoke具体做了什么

查看原文

赞 1 收藏 1 评论 0

Crystal_dan 发布了文章 · 8月10日

推技术

一 轮询:

polling:client按设置好的时间间隔主动访问server,有可能server返回有用数据,有可能server无有用数据返回,但都是一个建立连接-request-response-关闭连接的过程。

二 web的推:

1 comet

1)long polling

client访问server,若server有数据返回,则返回,关闭连接,client继续发请求;若server没有数据返回,连接保持,等待直到server有数据返回,关闭连接,client继续发请求。建立连接-request-(wait)-response-关闭连接。

image.png

2) Iframe

iframe 是很早就存在的一种 HTML 标记, 通过在 HTML 页面里嵌入一个隐蔵帧,然后将这个隐蔵帧的 SRC 属性设为对一个长连接的请求,服务器端就能源源不断地往客户端输入数据

2 websocket

websocket是一种协议,本质上和http,tcp一样。协议是用来说明数据是如何传输的。它的url前缀是ws:// 或者wss://,后者是加密的websocket。它的url诸如这样:ws://10.16.15.64:3201/

客户端和服务端进行websocket交互的方式也有人理解为“HTTP握手+TCP数据传输”的方式。

HTTP握手+TCP数据传输

握手和传输的整个流程是这样的:

浏览器(支持Websocket的浏览器)像HTTP一样,发起一个请求,然后等待服务端的响应

服务器返回握手响应,告诉浏览器请将后续的数据按照websocket制定的数据格式传过来

浏览器和服务器的socket连接不中断,此时这个连接和http不同的是它是双工的了

浏览器和服务器有任何需要传递的数据的时候使用这个长连接进行数据传递

这里说它是HTTP握手,是因为浏览器和服务器在建立长连接的握手过程是按照HTTP1.1的协议发送的,有Request,Request Header, Response, Response Header。但是不同的是Header里面的字段是有特定含义的。

说它是TCP传输,主要体现在建立长连接后,浏览器是可以给服务器发送数据,服务器也可以给浏览器发送请求的。当然它的数据格式并不是自己定义的,是在要传输的数据外层有ws协议规定的外层包的。

握手过程

image.png

这是一个握手过程的例子。

Upgrade头表示的意思是“客户端除了http之外也支持websocket协议,而且更倾向使用websocket,服务端如果支持的话,咱们就换websocket协议吧”

sec-websocket-version:是指出浏览器支持的websocket号。这里是支持hybi-13。这里是不会出现9-12的版本号的。websocket协议规定9-12是保留字段。

sec-websocket-key:算是一种验证返回回来的服务端是否是支持websocket的验证算法。与Response中的sec-websocket-accept是对应的。

sec-websocket-accept与sec-websocket-key的对应算法是:

sec-websocket-accept = base64(hsa1(sec-websocket-key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11))

如果返回的sec-websocket-accept不对,在chrome下会出现Sec-WebSocket-Accept dismatch的错误。

Response返回的HTTP Staus是101,代表服务端说“我们双方后面就按照websocket协议来进行数据传输吧”

小程序里对ws的支持好于h5。

ws的实现:

1)javax+tomcat(7.0.47+)

2)spring

查看原文

赞 2 收藏 2 评论 0

Crystal_dan 发布了文章 · 8月10日

websocket协议

使用http协议的问题

场景:客户端的展示随着服务端维护的状态的改变而实时改变。

可能会采取的方式:

1 轮询:client按设置好的时间间隔主动访问server,有可能server返回有用数据,有可能server无有用数据返回,但都是一个建立连接-request-response-关闭连接的过程。

2 长轮询:client访问server,若server有数据返回,则返回,关闭连接,client继续发请求;若server没有数据返回,连接保持,等待直到server有数据返回,关闭连接,client继续发请求。建立连接-request-(wait)-response-关闭连接。

3 推:client和server之间维护长连接,当server有返回的时候主动推给client。

问题:

http协议是一种无状态的,基于请求响应模式的协议。

半双工协议:可以在客户端和服务端2个方向上传输,但是不能同时传输。同一时刻,只能在一个方向上传输。

响应数据不实时,空轮询对资源的浪费。 HTTP消息冗长(轮询中每次http请求携带了大量无用的头信息)。

HTTP1.0 每个请求会打开一个新连接,一般打开和关闭连接花费的时间远大于数据传输的时间,对于HTTPS更是。

HTTP1.1 服务器不会在发送响应后立即关闭连接,可以在同一个socket上等待客户端的新请求

Websocket 协议

WebSocket是一种规范,是Html5规范的一部分。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并被RFC7936所补充规范。WebSocket API也被W3C定为标准。

单个 TCP 连接上进行全双工通讯的协议。

浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。websocket是全双工,没有严格的clientserver概念。

opening handshake

request:

GET /chat HTTP/1.1 
Host: server.example.com
Upgrade: websocket   //请求升级到WebSocket 协议
Connection: Upgrade //通道类型,keep-alive:通道长连,close:请求完毕后通道断开,Upgrade:升级协议
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==  //客户端随机生成的Key,校验服务器合法性,生成方式:随机16字节再被base64编码
Sec-WebSocket-Protocol: chat, superchat //子协议,特定于具体应用的消息格式或编排
Sec-WebSocket-Version: 13 
Origin: http://example.com

response:

HTTP/1.1 101 Switching Protocols  //非101仍然是http
Upgrade: websocket    //服务端协议已切换到WebSocket
Connection: Upgrade 
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
//indicates whether  the server is willing to accept the connection.
//用于校验WebSocket服务端是否合法,生成方式:客户端请求参数中的 Sec-WebSocket-Key+258EAFA5-E914-47DA-95CA-C5AB0DC85B11(GUID),
//SHA-1 hash  再进行base64
//GUID:which is unlikely to be used by network endpoints that do not understand the WebSocket protocol.
Sec-WebSocket-Protocol: chat
close handshake

image.png

fin:0,后续还有帧;1 本条消息的最后一帧

rsv1,rsv2,rsv3:不实用扩展协议,为0

Opcode:

image.png

websocket协议:https://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17https://tools.ietf.org/html/rfc6455#section-5

生命周期

image.png

4个生命周期事件:

打开事件 OnOpen;消息事件 OnMessage;错误事件 OnError;关闭事件 OnClose。

websocket简单实现

Java Websocket示例
//注解式
package echo;

import javax.websocket.OnMessage;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint(value =  "/echo")
public class EchoServer {
    @OnMessage
    public String echo(String message){
        return "I get this ("+message +") so I am sending it back";
    }
}
//编程式
package echo;

import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.Session;
import java.io.IOException;

/**
 * 编程式websocket
 */
public class ProgrammaticEchoServer extends Endpoint {

    @Override
    public void onOpen(Session session, EndpointConfig endpointConfig) {
        final Session mySession = session;
        mySession.addMessageHandler(
                new MessageHandler.Whole<String>() {

                    @Override
                    public void onMessage(String s) {
                        try {
                            mySession.getBasicRemote().sendText("I got this by prommmatic method ("+
                            s+") so I am sending it back ");
                        }catch (IOException io){
                            io.printStackTrace();
                        }
                    }
                });
    }
}
package echo;

import javax.websocket.Endpoint;
import javax.websocket.server.ServerApplicationConfig;
import javax.websocket.server.ServerEndpointConfig;
import java.util.HashSet;
import java.util.Set;

public class ProgrammaticEchoServerAppConfig implements ServerApplicationConfig {
    @Override
    public Set<ServerEndpointConfig> getEndpointConfigs(Set<Class<? extends Endpoint>> set) {
        Set configs = new HashSet<ServerEndpointConfig>();
        ServerEndpointConfig curSec = ServerEndpointConfig.Builder.create(ProgrammaticEchoServer.class,"/programmaticecho").build();
        configs.add(curSec);
        return configs;
    }

    /**
     * 获取所有通过注解注册的endpoint
     * @param set
     * @return
     */
    @Override
    public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> set) {
        return null;
    }
}

//客户端
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title> new document </title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Web Socket Echo CLient</title>
    <script language="javascript" type="text/javascript">
        var echo_websocket;
        function  init() {
            output = document.getElementById("output");
        }

        function  send_echo() {
            var wsUri = "ws://localhost:8080/programmaticecho";//注解式为ws://localhost:8080/echo
            writeToScreen("Connecting to "+wsUri);
            echo_websocket = new WebSocket(wsUri);
            echo_websocket.onopen = function (evt) {
                writeToScreen("Connected!");
                doSend(textID.value);
            };

            echo_websocket.onmessage = function (evt) {
                writeToScreen("Received message: "+evt.data);
                echo_websocket.close();
            };

            echo_websocket.onerror = function (evt) {
                writeToScreen('<span style= "color"red;">ERROR:<span> '+evt.data);
                echo_websocket.close();
            };

        }

        function doSend(message) {
            echo_websocket.send(message);
            writeToScreen("Sent message: "+message);

        }


        function writeToScreen(message) {
            var pre = document.createElement("p");
            pre.style.wordWrap = "break-word";
            pre.innerHTML = message;
            output.appendChild(pre);

        }

        window.addEventListener("load",init,false);


    </script>
</head>

<body>

<h1>Echo Server</h1>

    <div style="text-align: left;">
        <form action=""\>
            <input onclick="send_echo()" value="Press to send" type = "button"/>
            <input id="textID" name = "message" value="Hello Web Sockets" type = "text"/>
        </form>
    </div>
<br/>
<div id="output"></div>

</body>
</html>
netty对websocket的支持
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoop;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import java.net.InetSocketAddress;
public class WebsocketNettyServer {
    public static void main(String[] args) throws Exception{
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup wokerGroup = new NioEventLoopGroup();
        try{
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup,wokerGroup).channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer<SocketChannel>(){
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();

                            //websocket协议本身是基于http协议的,所以这边也要使用http解编码器
                            pipeline.addLast(new HttpServerCodec());
                            //以块的方式来写的处理器
                            pipeline.addLast(new ChunkedWriteHandler());
                            //netty是基于分段请求的,HttpObjectAggregator的作用是将请求分段再聚合,参数是聚合字节的最大长度
                            pipeline.addLast(new HttpObjectAggregator(8192));

                            //ws://server:port/context_path
                            //参数指的是contex_path
                            pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
                            //websocket定义了传递数据的6中frame类型
                            pipeline.addLast(new TextWebSocketFrameHandler());
                        }
                    });
            ChannelFuture channelFuture = serverBootstrap.bind(new InetSocketAddress(8899)).sync();
            channelFuture.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            wokerGroup.shutdownGracefully();
        }
    }
}



import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;

public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame>{

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {
        System.out.println("收到消息"+textWebSocketFrame.text());
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("有新的连接加入");
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("连接关闭");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("啊哦,出错了");
        ctx.fireExceptionCaught(cause);
    }
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket客户端</title>
</head>
<body>
<script type="text/javascript">
    var socket;
    //如果浏览器支持WebSocket
    if(window.WebSocket){
        //参数就是与服务器连接的地址
        socket = new WebSocket("ws://localhost:8899/ws");
        //客户端收到服务器消息的时候就会执行这个回调方法
        socket.onmessage = function (event) {
            var ta = document.getElementById("responseText");
            ta.value = ta.value + "\n"+event.data;
        }
        //连接建立的回调函数
        socket.onopen = function(event){
            var ta = document.getElementById("responseText");
            ta.value = "连接开启";
        }
        //连接断掉的回调函数
        socket.onclose = function (event) {
            var ta = document.getElementById("responseText");
            ta.value = ta.value +"\n"+"连接关闭";
        }
    }else{
        alert("浏览器不支持WebSocket!");
    }
    //发送数据
    function send(message){
        if(!window.WebSocket){
            return;
        }
        //当websocket状态打开
        if(socket.readyState == WebSocket.OPEN){
            socket.send(message);
        }else{
            alert("连接没有开启");
        }
    }
</script>
<form onsubmit="return false">
    <textarea name = "message" style="width: 400px;height: 200px"></textarea>
    <input type ="button" value="发送数据" onclick="send(this.form.message.value);">
    <h3>服务器输出:</h3>
    <textarea id ="responseText" style="width: 400px;height: 300px;"></textarea>
    <input type="button" onclick="javascript:document.getElementById('responseText').value=''" value="清空数据">
</form>
</body>
</html>

image.png

netty-socketio实现,即基于netty的java版socket io Server
查看原文

赞 1 收藏 1 评论 0

Crystal_dan 发布了文章 · 8月10日

struts的动态代理:ActionProxy和ActionInvocation

本文分析的源码涉及三个方面:

1  struts中使用到的动态代理:ActionProxy 和 ActionInvocation

2  struts中是如何处理拦截器和action请求的

3  struts中是如何处理结果的

每个请求都会通过Dispatcher中的serviceAction方法,这个方法中有很重要的一句代码

String namespace = mapping.getNamespace();
String name = mapping.getName();
String method = mapping.getMethod();

//步骤1 
ActionProxy proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
        namespace, name, method, extraContext, true, false);

request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());

// if the ActionMapping says to go straight to a result, do it!
//  后面再分析result
if (mapping.getResult() != null) {
    Result result = mapping.getResult();
    result.execute(proxy.getInvocation());
} else {
    // 步骤2 
    proxy.execute();
}

这里面比较重要的一句是获取proxy的这一句,从container中先获取一个ActionProxyFactory的实例,然后根据命名空间(struts.xml的package?),action的名字,方法名,上下文生成一个ActionProxy,实际是ActionProxy执行了action。

ActionProxyFactory如何创建ActionProxy?

ActionProxyFactory是一个接口,默认的工厂类是DefaultActionProxyFactory,其中的createActionProxy方法如下:

public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext) {
    //  创建了一个ActionInvocation实例
    ActionInvocation inv = createActionInvocation(extraContext, true);
    container.inject(inv);
    // 通过下面的createActionProxy可知ActionProxy中持有了对ActionInvocation的引用
    return createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
}
 
protected ActionInvocation createActionInvocation(Map<String, Object> extraContext, boolean pushAction) {
    return new DefaultActionInvocation(extraContext, pushAction);
}
 
public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) {
    //  传进去的inv会在prepare的时候用到
    DefaultActionProxy proxy = new DefaultActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
    container.inject(proxy);
    // 有一些初始化操作,比如methodname是null的时候就默认执行execute方法
    proxy.prepare();
    return proxy;
}

在上面的proxy.prepare 中,除了会对methodname有处理,还有重要的一步是invocation.init(this),这里的invocation就是new DefaultActionProxy的时候传入的inv,下面看看DefaultActionInvocation中的init会做些什么

public void init(ActionProxy proxy) {
    // 可见actionInvocation中也持有一个actionProxy的引用
    this.proxy = proxy;
    Map<String, Object> contextMap = createContextMap();
    // Setting this so that other classes, like object factories, can use the ActionProxy and other
    // contextual information to operate
    ActionContext actionContext = ActionContext.getContext();
    if (actionContext != null) {
        //  actionContext也有一个对actioninvocation的引用
        actionContext.setActionInvocation(this);
    }
    //  创建action实例
    createAction(contextMap);
    if (pushAction) {
        stack.push(action);
        contextMap.put("action", action);
    }
    invocationContext = new ActionContext(contextMap);
    invocationContext.setName(proxy.getActionName());
    //创建相关拦截器
    createInterceptors(proxy);
}
 
//具体看下actioninvocation是如何创建一个action的
protected void createAction(Map<String, Object> contextMap) {
    // load action
    String timerKey = "actionCreate: " + proxy.getActionName();
    try {
        UtilTimerStack.push(timerKey);
        // actioninvocation中有一个action的全局变量
        // 主要就是这一句,这里用到了objectfactory,这个工厂类持有对actionfactory的引用,实际是调用了actionfactory的buildaction
        // todo actionfactory的buildaction  后面再具体看actionfactory
        action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);
    } catch (InstantiationException e) {
        throw new XWorkException("Unable to intantiate Action!", e, proxy.getConfig());
    } catch (IllegalAccessException e) {
        throw new XWorkException("Illegal access to constructor, is it public?", e, proxy.getConfig());
    } catch (Exception e) {
        String gripe;
        if (proxy == null) {
            gripe = "Whoa!  No ActionProxy instance found in current ActionInvocation.  This is bad ... very bad";
        } else if (proxy.getConfig() == null) {
            gripe = "Sheesh.  Where'd that ActionProxy get to?  I can't find it in the current ActionInvocation!?";
        } else if (proxy.getConfig().getClassName() == null) {
            gripe = "No Action defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'";
        } else {
            gripe = "Unable to instantiate Action, " + proxy.getConfig().getClassName() + ",  defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'";
        }
        gripe += (((" -- " + e.getMessage()) != null) ? e.getMessage() : " [no message in exception]");
        throw new XWorkException(gripe, e, proxy.getConfig());
    } finally {
        UtilTimerStack.pop(timerKey);
    }
    if (actionEventListener != null) {
        action = actionEventListener.prepare(action, stack);
    }
}
 
//创建拦截器就比较简单了,根据配置文件把所有的拦截器放在一个list中
protected void createInterceptors(ActionProxy proxy) {
    // get a new List so we don't get problems with the iterator if someone changes the list
    List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors());
    interceptors = interceptorList.iterator();
}

理一下前面的:

dispatcher中先创建了actionproxy,同时会创建一个actioninvocation,然后对methodname对处理,创建action实例,创建拦截器list。

到目前为止,dispatcher中的步骤1 完成。现在开始步骤2,步骤2就是具体的要执行action的execute方法。

DefaultActionProxy的execute代码如下

public String execute() throws Exception {
    //context  相关后续分析
    ActionContext nestedContext = ActionContext.getContext();
    ActionContext.setContext(invocation.getInvocationContext());

    String retCode = null;

    String profileKey = "execute: ";
    try {
        UtilTimerStack.push(profileKey);
        //  proxy中使用invocation完成action的执行,和java的动态代理是一样的
        retCode = invocation.invoke();
    } finally {
        if (cleanupContext) {
            ActionContext.setContext(nestedContext);
        }
        UtilTimerStack.pop(profileKey);
    }

    return retCode;
}

接着看看DefaultActionInvocation的invoke做了什么:

public String invoke() throws Exception {
    String profileKey = "invoke: ";
    try {
        UtilTimerStack.push(profileKey);
        if (executed) {
            throw new IllegalStateException("Action has already executed");
        }
        //  这里的interceptors 类型是Iterator<InterceptorMapping>,就是上面createInterceptors的时候被赋值的
        //  遍历interceptors
        if (interceptors.hasNext()) {
            final InterceptorMapping interceptor = interceptors.next();
            String interceptorMsg = "interceptor: " + interceptor.getName();
            UtilTimerStack.push(interceptorMsg);
            try {
                  // 每拿到一个拦截器,就执行拦截器的intercept方法,在拦截器的intercept方法中一定会执行incovation.invoke,就会又调到本方法中
                  // 结合这里,就能够知道拦截器的执行顺序,每个拦截器执行到invocation.invoke之后都会递归的调到下一个拦截器上
                  // 假设有三个拦截器A B C,把每个拦截器在invocation.invoke前的代码成为invoke前,之后的代码成为invoke后
                  // 执行顺序就是:A的invoke前,B的invoke前,c的invoke前,action,c的invoke后,B的invoke后,A的invoke后
                  resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
            }
            finally {
                UtilTimerStack.pop(interceptorMsg);
            }
        } else {
            // 拦截器链中最后一个拦截器的invation.invoke就会执行action本身
            resultCode = invokeActionOnly();
        }

        // this is needed because the result will be executed, then control will return to the Interceptor, which will
        // return above and flow through again
        if (!executed) {
            if (preResultListeners != null) {
                LOG.trace("Executing PreResultListeners for result [#0]", result);

                for (Object preResultListener : preResultListeners) {
                    PreResultListener listener = (PreResultListener) preResultListener;

                    String _profileKey = "preResultListener: ";
                    try {
                        UtilTimerStack.push(_profileKey);
                        listener.beforeResult(this, resultCode);
                    }
                    finally {
                        UtilTimerStack.pop(_profileKey);
                    }
                }
            }

            // now execute the result, if we're supposed to
            if (proxy.getExecuteResult()) {
                executeResult();
            }

            executed = true;
        }

        return resultCode;
    }
    finally {
        UtilTimerStack.pop(profileKey);
    }
}
 
public String invokeActionOnly() throws Exception {
    //  这里的getaction方法就是invocation持有的action实例,即在前面创建action实例的时候创建的
    return invokeAction(getAction(), proxy.getConfig());
}

下面看一下invokeAction方法中是怎么执行action的:

protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception {
    // 获取要执行方法名,是否有必要使用默认的execute也早在proxy的prepare中处理过了
    String methodName = proxy.getMethod();
    if (LOG.isDebugEnabled()) {
        LOG.debug("Executing action method = #0", methodName);
    }

    String timerKey = "invokeAction: " + proxy.getActionName();
    try {
        UtilTimerStack.push(timerKey);
        Object methodResult;
        try {
            //使用onglUtil这个工具类执行action的方法,具体的就要了解onglutil做了啥了。。
            methodResult = ognlUtil.callMethod(methodName + "()", getStack().getContext(), action);
        } catch (MethodFailedException e) {
            // if reason is missing method, try find version with "do" prefix
            if (e.getReason() instanceof NoSuchMethodException) {
                try {
                    String altMethodName = "do" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1) + "()";
                    methodResult = ognlUtil.callMethod(altMethodName, getStack().getContext(), action);
                } catch (MethodFailedException e1) {
                    // if still method doesn't exist, try checking UnknownHandlers
                    if (e1.getReason() instanceof NoSuchMethodException) {
                        if (unknownHandlerManager.hasUnknownHandlers()) {
                            try {
                                methodResult = unknownHandlerManager.handleUnknownMethod(action, methodName);
                            } catch (NoSuchMethodException e2) {
                                // throw the original one
                                throw e;
                            }
                        } else {
                            // throw the original one
                            throw e;
                        }
                        // throw the original exception as UnknownHandlers weren't able to handle invocation as well
                        if (methodResult == null) {
                            throw e;
                        }
                    } else {
                        // exception isn't related to missing action method, throw it
                        throw e1;
                    }
                }
            } else {
                // exception isn't related to missing action method, throw it
                throw e;
            }
        }
        // 处理执行完方法后拿到的methodResult,然后返回处理后的结果
        return saveResult(actionConfig, methodResult);
    } catch (NoSuchPropertyException e) {
        throw new IllegalArgumentException("The " + methodName + "() is not defined in action " + getAction().getClass() + "");
    } catch (MethodFailedException e) {
        // We try to return the source exception.
        Throwable t = e.getCause();

        if (actionEventListener != null) {
            String result = actionEventListener.handleException(t, getStack());
            if (result != null) {
                return result;
            }
        }
        if (t instanceof Exception) {
            throw (Exception) t;
        } else {
            throw e;
        }
    } finally {
        UtilTimerStack.pop(timerKey);
    }
}

saveReult:

// 如果methodresult是Result类型就保存到explicitResult(explicitResult是actioninvocation中一个Rsult类型的全局变量)上,返回null,否则直接返回string的methodResult
protected String saveResult(ActionConfig actionConfig, Object methodResult) {
    if (methodResult instanceof Result) {
        this.explicitResult = (Result) methodResult;

        // Wire the result automatically
        container.inject(explicitResult);
        return null;
    } else {
        return (String) methodResult;
    }
}

再回到invoke中,执行完action之后有一段代码

// now execute the result, if we're supposed to
if (proxy.getExecuteResult()) {
    executeResult();
}
 
// 具体的executeResult如下
private void executeResult() throws Exception {
    // create a Result
    result = createResult();

    String timerKey = "executeResult: " + getResultCode();
    try {
        UtilTimerStack.push(timerKey);
        if (result != null) {
            // 执行Result的execute
            result.execute(this);
        } else if (resultCode != null && !Action.NONE.equals(resultCode)) {
            throw new ConfigurationException("No result defined for action " + getAction().getClass().getName()
                    + " and result " + getResultCode(), proxy.getConfig());
        } else {
            if (LOG.isDebugEnabled()) {
                LOG.debug("No result returned for action " + getAction().getClass().getName() + " at " + proxy.getConfig().getLocation());
            }
        }
    } finally {
        UtilTimerStack.pop(timerKey);
    }
}
 
//具体的createResult如下
public Result createResult() throws Exception {
    LOG.trace("Creating result related to resultCode [#0]", resultCode);
    if (explicitResult != null) {
        把之前在saveResult中保存到explicitResult中的值赋值到ret中,同时explicitResult清空
        Result ret = explicitResult;
        explicitResult = null;
        // 如果explicitResult不为空,即之前获得的结果直接就是一个result类型,那么直接返回
        // 否则之前获得结果是一个普通的字符串,需要找到字符串对应的Result
        return ret;
    }
    
    ActionConfig config = proxy.getConfig();
    // 配置文件中配置的result节点信息
    Map<String, ResultConfig> results = config.getResults();

    ResultConfig resultConfig = null;

    try {
        //  尝试获取resultCode对应的resultconfig
        resultConfig = results.get(resultCode);
    } catch (NullPointerException e) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Got NPE trying to read result configuration for resultCode [#0]", resultCode);
        }
    }
    if (resultConfig == null) {
        // If no result is found for the given resultCode, try to get a wildcard '*' match.
        resultConfig = results.get("*");
    }
    if (resultConfig != null) {
        try {
            // 根据resultconfig 构造一个Result返回
            return objectFactory.buildResult(resultConfig, invocationContext.getContextMap());
        } catch (Exception e) {
            if (LOG.isErrorEnabled()) {
                LOG.error("There was an exception while instantiating the result of type #0", e, resultConfig.getClassName());
            }
            throw new XWorkException(e, resultConfig);
        }
    } else if (resultCode != null && !Action.NONE.equals(resultCode) && unknownHandlerManager.hasUnknownHandlers()) {
        return unknownHandlerManager.handleUnknownResult(invocationContext, proxy.getActionName(), proxy.getConfig(), resultCode);
    }
    return null;
}

那么,Result到底是什么?

// Result接口很简单,只提供一个execute方法
public interface Result extends Serializable {

    /**
     * Represents a generic interface for all action execution results.
     * Whether that be displaying a webpage, generating an email, sending a JMS message, etc.
     *
     * @param invocation  the invocation context.
     * @throws Exception can be thrown.
     */
    public void execute(ActionInvocation invocation) throws Exception;

}

struts在配置action的时候,会相应的配置result节点,其中包括result的type,每一个type其实对应一个具体的Result实现类,上面的objectFactory.buildResult实则调的是resultFactory的buildResult。

默认的ResultFactory是StrutsResultFactory

public Result buildResult(ResultConfig resultConfig, Map<String, Object> extraContext) throws Exception {
    // config中有配置type,找到对应的具体result实现类
    String resultClassName = resultConfig.getClassName();
    Result result = null;

    if (resultClassName != null) {
        //  生成具体result实现类的实例
        // 回到上面的result.execute,就可知道这个时候获取了result实例,不同type的result的execute有不同的处理
        result = (Result) objectFactory.buildBean(resultClassName, extraContext);
        Map<String, String> params = resultConfig.getParams();
        if (params != null) {
            setParameters(extraContext, result, params);
        }
    }
    return result;
}

至此,大概知道了struts的动态代理的使用,如何用动态代理模式处理action请求和拦截器,如何处理result。

todo:

1  上面只是大概流程,可找几点具体分析

2  context相关

3 struts的工厂类 ObjectFactory及具体的各种工厂类

4 msite现在使用的json result是如何处理的。

查看原文

赞 1 收藏 1 评论 0

Crystal_dan 发布了文章 · 8月10日

MBG

mybatis-generator-config_1_0.dtd mybatis生成代码的配置文件的文档类型定义

配置示例https://www.jianshu.com/p/e09d2370b796
image

生成代码流程,以生成java代码 Dao层为例

image

查看原文

赞 0 收藏 0 评论 0

Crystal_dan 发布了文章 · 8月10日

ParallelScavenge + Parallel Old

ParalletScavenge:复制算法,多线程,关注吞吐率

Copying GC(GC复制算法):

最早是Robert R.Fenichel和Jerome C.Yochelson提出,简单说将空间分为From和To,当From空间完全占满时,gc将活动对象全部复制到To空间,复制完成后,From和To的使命互换。

为了保证From中所有活着的对象都能复制到To里,要求From和To空间大小一致。

coping(){
  free = to_start;
  for(r : roots){
    *r = copy(*r)
  }
  swap(from_start,to_start)
}
/**
*free设在To空间的开头
*第四行copy函数,复制能从根引用到的对象及其子对象,返回*r所在的新空间。
*然后交换From和To,对From空间进行gc
*/
 
copy(obj){
  if(obj.tag != COPIED){
    copy_data(free,obj,obj.size);
    obj.tag = COPIED;
    obj.forwarding = free;
    free = obj.size
 
    for(child : children(obj.forwarding){
      *child = copy(*child)
    }
  }
  return obj.forwarding
}
/**
*obj.tag 并不是单独的一个域,只是占用了obj的field1,一旦obj被复制完毕,其在From空间的域值更改没有关系
*obj.forwarding也不是一个单独的域,占用了obj.field2,用来标识新空间的位置
* 由更上可知,需要每个对象至少有两个域
* 深度遍历
*/

image.png

//分配
new_obj(size){
  if(free + size > from_start + HEAP_SIZE /2){
    copying(); //空间不足,gc
    if(free + size > from_start + HEAP /2){
       allocation_fail();//还是不够,分配失败
    }
  }
  obj = free;
  obj.size = size;
  free += size;
  return obj;
}

copying算法的总结:

1 copying算法的时间复杂度只跟活跃对象的个数有关,可以在较短时间内完成gc,吞吐量较高

2 分配速度快,移动指针,不涉及空闲链表

3 不会产生碎片,复制的行为本身包含了压缩

4 深度优先,具有引用关系的对象靠在一起。利于缓存的使用(局部性原理)。

5 堆的利用率低

6  递归的进栈出栈消耗比迭代的形式大,但用迭代的形式是广度优先,jdk以前是广度,现在都是深度;有进阶算法可以近似接近深度优先而不需要递归

 Parallel Old:标记-整理,多线程

Mark Compact GC 标记-压缩算法:

首先看Donald E.Knuth研究出来的Lisp2算法:

compaction_phase(){
  set_forwarding_ptr();
  adjust_ptr();
  move_obj();
}
 
set_forwarding_ptr(){
  scan = new_address = heap_start;
  while(scan < heap_end){  //第一次搜索堆
    if(scan.mark == TRUE){  //被标记为活对象
      scan.forwarding = new_address;//new_address指向移动后的地址
      new_address += scan.size;
    }
    scan += scan.size; //扫描下一个活对象
  }
}
 
adjust_ptr(){
  for(r : roots){
    *r = (*r).forwarding;
  }
  
  scan = heap_start;
  while(scan < heap_end){  //第二次搜索堆
    if(scan.mark = TRUE){
      for(child : children(scan))
        *child = (*child).forwarding
    }
    scan += scan.size;
  }
}
 
move_obj(){
  scan = free = heap_start;
  while(scan < heap_end){//第三次搜索堆
     if(scan.mark = TRUE){
        new_address = scan.forwarding;
        copy_data(new_address,scan,scan.size);
        new_address.forwarding = null;
        new_address.mark = FALSE:
        free += new_address.size;
        scan += scan.size;
     }
  }
}

image.png

总结:

1 堆利用率高

2 压缩空间,没有碎片

3 时间消耗大:需要搜索三次堆,且时间与堆大小成正比

还有一个Two-Finger算法,可以做到只搜索两次,但要求所有对象整理成大小一致,这个约束比较苛刻,jvm用的应该是Lisp2。

分代垃圾回收:

可行性来源于经验:“大部分的对象在生成后很快就变成了垃圾,很少有对象能活得很久”

有些GC算法的时间是和活着的对象的个数成正比,比如标记-清除MarkSweep,复制Copying;

minorGC:新生代GC,minor指小规模的;

majorGC:老年代GC

promotion:新生代对象经过若干次gc仍活着的晋升为老年代。

Ungar的分代垃圾回收:David Ungar研究出来的将copying算法与分代思想结合

 1  堆划分为4个空间:生成空间new_start,2个大小相等的幸存空间survivor1_start,survivor2_start,老年代空间old_start

    2个幸存空间类似于copying gc中的From和To,每次利用生成空间+1个幸存空间,gc后活着的对象复制到另一个幸存空间

2  考虑:新生代gc:需要考虑老年代空间对新生代空间中对象的引用

Remembered Set & Card Table

image.png
Remembered Set:实现部分垃圾收集时(partial gc),用于记录从非收集部分指向收集部分的指针的集合的 抽象数据结构了。

在分代垃圾回收里,Remembered Set 用来记录老年代对象 对  新生代对象 的跨代引用;在regional collector中,Remembered Set 用来记录跨region的指针。

粒度;数据结构;写屏障

3 对象的晋升

 MaxTenuringThreshold 定义了晋升老年代的对象的最大年龄,大于这个年龄,一定会晋升,小于这个年龄,也有可能晋升,取决于TargetSurvivorRatio。

当年龄为age的对象的大小之和超过了targetSurvivorratio * survivor,则实际用来判断是否晋升的年龄是这个动态的age,不是maxtenuringthreshold

查看原文

赞 0 收藏 0 评论 0

Crystal_dan 发布了文章 · 8月10日

反射优化引起的问题

记录一次压测中遇到的线程阻塞问题
java可以使用反射来执行方法调用,反射根据一个类名得到Class对象,再由对象名和给定的参数集拿到Method对象,就可以通过Method.invoke来执行

@CallerSensitive
    public Object invoke(Object var1, Object... var2) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        if(!this.override && !Reflection.quickCheckMemberAccess(this.clazz, this.modifiers)) {
            Class var3 = Reflection.getCallerClass();
            this.checkAccess(var3, this.clazz, var1, this.modifiers);
        }
        MethodAccessor var4 = this.methodAccessor;
        if(var4 == null) {
            var4 = this.acquireMethodAccessor();
        }
        return var4.invoke(var1, var2);
    }

由上面的代码可以看出来invoke是由MethodAccessor来执行的,MethodAccessor又是acquireMethodAccessor方法获取到的

private MethodAccessor acquireMethodAccessor() {
        MethodAccessor var1 = null;
        if(this.root != null) {
            var1 = this.root.getMethodAccessor();
        }
        if(var1 != null) {
            this.methodAccessor = var1;
        } else {
            var1 = reflectionFactory.newMethodAccessor(this); 
            this.setMethodAccessor(var1);
        }
        return var1;
    }

MethodAccessor是通过ReflectionFactory的newMethodAccessor获取的,代码如下:

 public MethodAccessor newMethodAccessor(Method var1) {
        checkInitted();
        if(noInflation && !ReflectUtil.isVMAnonymousClass(var1.getDeclaringClass())) {
            return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers());
        } else {
            NativeMethodAccessorImpl var2 = new NativeMethodAccessorImpl(var1);
            DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2);
            var2.setParent(var3);
            return var3;
        }
    }

如果noInflation为true(不膨胀,当Java虚拟机从JNI存取器改为字节码存取器的行为被称为膨胀(Inflation)),创建MethodAccessorGenerator,否则NativeMethodAccessor。NativeMethodAccessorImpl中的invoke代码如下

public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
        if(++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
            MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
            this.parent.setDelegate(var3);//当这一步执行完之后,DelegatingMethodAccessorImpl中的delegate就是MethodAccessorImpl而不是NativeMethodAccessorImpl了
        }
        return invoke0(this.method, var1, var2);
    }

ReflectionFactory.inflationThreshold() 就是jvm的启动参数的-Dsun.reflect.inflationThreshold,默认值是15.

调用次数没有超过这个阈值的时候其实使用的还是NativeMethodAccessor.invoke),即没有if里面那些处理。超出阈值后执行if中的逻辑,native的就被搞成了MethodAccessorImpl。同时setDelegate

这个setDelegate 要回看上面DelegatingMethodAccessorImpl,有点像一个中间层,在native和java版之间转换

class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
    private MethodAccessorImpl delegate;
    DelegatingMethodAccessorImpl(MethodAccessorImpl var1) {
        this.setDelegate(var1);
    }
    public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
        return this.delegate.invoke(var1, var2);
    }
    void setDelegate(MethodAccessorImpl var1) {
        this.delegate = var1;
    }
}

据说java版的启动慢,但是执行快(编译器可以优化);native版的启动快,但是执行慢。所以hotspot的jdk做了个优化,调用次数少时用native版的,当发现调用次数多时,则调用MethodAccessorGenerator.generateMethod()来生成Java版的MethodAccessor的实现类,并且改变DelegatingMethodAccessorImpl所引用的MethodAccessor为Java版

sun.reflect.GeneratedMethodAccessor<N>是怎么出现的呢?

默认这个优化是开启的且阈值是15,在前面少数调用时,调用的其实是native版的invoke0,超出阈值后,就开始使用MethodAccessorGenerator.generateMethod,这里面最终会调到一个genarateName方法

 private static synchronized String generateName(boolean var0, boolean var1) {
        int var2;
        if(var0) {
            if(var1) {
                var2 = ++serializationConstructorSymnum;
                return "sun/reflect/GeneratedSerializationConstructorAccessor" + var2;
            } else {
                var2 = ++constructorSymnum;
                return "sun/reflect/GeneratedConstructorAccessor" + var2;
            }
        } else {
            var2 = ++methodSymnum;
            return "sun/reflect/GeneratedMethodAccessor" + var2;
        }
    }
查看原文

赞 1 收藏 1 评论 0

Crystal_dan 发布了文章 · 8月10日

对象的创建

基于HotSpot

基本类型对象的创建

1 当虚拟机遇到一条new指令时,尝试在常量池中定位到相关类的符号引用

2 检查这个符号引用代表的类是否已经加载,解析,初始化,如果没有先执行类加载

3 完成2后可确定对象所需的内存大小

4 从堆中划分出相应大小的内存。

5 初始化对象:对象的哈希码,gc分代年龄,对应类的信息。

6 根据代码设置对象初始值。

问题一:

步骤3是如何确定对象所需内存的大小的?

首先看一下对象在内存中的存储布局:

image.png

hotspot规定对象的起始地址必须是8字节的整倍数,即对象的大小必须是8字节的整数倍。对象头一般格式较固定,可以保证这一点,当实例数据不满足的时候,需要对齐填充部分来一起保证。

问题二:

步骤4中是如何从堆中划分内存的?

堆中划分内存有两种方法:指针 和  空闲列表。使用哪种方式取决于堆是否是规整的(堆是否能规整取决于使用的垃圾回收算法)。

规整的意思是说堆中使用过的内存和空闲内存有明显的划分界线,而不是混在一起的。

如果是规整的,那么就可以使用指针法,在使用过的内存和空闲内存的分界点用一个指针表示,分配内存时只要将指针向空闲内存方向移动一段与对象内存大小相等的距离。

如果不是规整的,那只能使用空闲列表,空闲列表是指虚拟机维护一个列表,记录哪些内存块是使用过的哪些是空闲的,分配内存时从列表中选择一块满足对象大小的内存块。这里如何选择内存块,估计就是学生时代学的操作系统讲的那些内容了,如何保证内存碎片较少之类的(后续整理)

hotspot使用的应该是指针

分配内存的时候还涉及到并发的问题,两种解决方式:

1 对分配内存这种操作进行同步处理,CAS保证操作的原子性

2 本地线程分配缓冲TLAB,预先为每个线程分配一小块内存TLAB,需要内存时各线程先在自己的TLAB上分配,当TLAB不够用的时候才会需要同步。

CAS:

简单描述下,大体采用的是乐观锁的思想,不使用锁,多个线程去改同一个变量时,都可以尝试去改,只会有一个线程成功,失败的线程也会立刻得到反馈,不会阻塞。

CAS中有三个变量,内存中的值V,预期原值A,新值B,每个线程尝试去改变量的时候,先判断A和V是否相等,如果相同,则将B更新到内存,否则更新失败。

查看原文

赞 1 收藏 1 评论 0

Crystal_dan 发布了文章 · 8月10日

ThreadLocal

同一个threadlocal变量所包含的对象,在不同的线程中是不同的副本。

既然是不同的线程拥有不同的副本且不允许其他线程访问,所以不存在共享变量的问题。

解决的是变量在线程间隔离在方法或类之间共享的问题。

解决这个问题可能有的两种方案

第一种:一个threadlocal对应一个map,map中以thread为key
image

这种方法多个线程针对同一个threadlocal1是同一个map,新增线程或减少线程都需要改动map,这个map就变成了多个线程之间的共享变量,需要额外机制比如锁保证map的线程安全。

线程结束时,需要保证它所访问的所有 ThreadLocal 中对应的映射均删除,否则可能会引起内存泄漏。-----为什么会导致内存泄漏呢?见下文

第二种:一个thread对应一个map,map中以threadlocal为key区分

image

Thread中有一个变量

ThreadLocal.ThreadLocalMap threadLocals = null;//每个线程维护一个map

ThreadLocalMap

 static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
        
 private Entry[] table;

ThreadLocalMap维护了一个table数组,存储Entry类型对象,Entry类型对象以ThreadLocal为key,任意对象为值的健值对

ThreadLocal的get set

 public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//返回当前线程维护的threadlocals变量
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
    
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    

Entry key的弱引用以及内存泄漏
image

为什么说threadlocal会存在内存泄漏:

每个thread维护的threadlocalmap key是指向threadlocal的弱引用,当没有任何其他强引用指向threadlocal的时候,gc会把key回收。

但value是是thread指向的强引用,thread不结束,value不会被回收。

所以当threadlocal不可用但thread还在的这段时间内,会存在所说的内存泄漏。尤其当使用线程池的时候,线程被复用。

jdk有没有相应的处理:

再回到threadlocalmap的get set方法

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t); //获取当前线程的map
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this); //找到当前的threadlocal
            if (e != null)
                return (T)e.value;//取值
        }
        return setInitialValue();// map为null或者map找不到指定key时,初始化基本值,不展开
    }
    
    //getEntry函数
      private Entry getEntry(ThreadLocal key) {
            int i = key.threadLocalHashCode & (table.length - 1);//根据key计算索引
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);//table中该索引位置对象e为null 或者 索引位置key不符进入getEntryAfterMiss
        }
        
   //getEntryAfterMiss函数
     private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;
            //遍历table一直到找到了k=key的位置,返回相应对象e
            //遍历过程中如果遇到了k为null,即调用expungeStaleEntry清理该entry,即前面所说的内存泄漏,这里是处理的一个时机
            while (e != null) {
                ThreadLocal k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len); //循环遍历table,ThreadLocal采用的是开放地址法,即有冲突后,把要插入的元素放在要插入的位置后面为null的地方
                e = tab[i];
            }
            return null;//如果是e为null  返回null
        }
   //expungeStaleEntry 函数
  private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            // expunge entry at staleSlot:key为null的索引位置的对象.value置为null,对象也置为null
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;  //遍历是从staleSlot之后到遇到的第一个e为null
                 i = nextIndex(i, len)) {
                ThreadLocal k = e.get();
                if (k == null) {//遍历的过程中遇到key为null做和上面同样的处理
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else { //key不为null的重新hash
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }
 
        //再看set
     public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
    //重点在map.set函数
      private void set(ThreadLocal key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);//计算索引

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) { //如果根据索引找到的entry不是空的
                ThreadLocal k = e.get();
                if (k == key) {  //key相同,value直接覆盖
                    e.value = value;
                    return;
                }

                if (k == null) {  //遍历过程中key为null,清除
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
                        //上面没有处理掉,找到第一个为null的能用的坑位,new一个entry放入
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

能大致看到,上面的代码中,threadlocalmap的get set都会做对key为null的清除工作,从而解决了上面说的内存泄漏问题,只是这种处理依赖对set get的调用

threadlocalmap的remove方法:

 private void remove(ThreadLocal key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

综上所述,很多地方会看到有这样的两条建议:

1 .使用者需要手动调用remove函数,删除不再使用的ThreadLocal.

2 .还有尽量将ThreadLocal设置成private static的,这样ThreadLocal会尽量和线程本身一起消亡。

查看原文

赞 1 收藏 1 评论 0

认证与成就

  • 获得 11 次点赞
  • 获得 0 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 0 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 7月23日
个人主页被 96 人浏览