Buscar
Social
Ofertas laborales ES
jueves
ago162001

Autentificacrión manejada por el contenedor -Tomca


Autentificación manejada por el contenedor en Tomcat.

Autentificación manejada por el contenedor en Tomcat.


Fecha de creación: 16.08.2001

Revisión 1.0.1 (15.10.2002)

Alberto Molpeceres
al AT javahispano DOT org

Copyright (c) 2002, Alberto Molpeceres. Este documento puede ser distribuido solo bajo los términos y condiciones de la licencia de Documentación de javaHispano v1.0 o posterior (la última versión se encuentra en /licencias/).



Palabras previas



El mayor trabajo de este artículo esta en la configuración más que en la programación, asi que hay poco que hacer, a parte de andar en unos cuantos ficheros XML para que Tomcat lleve a cabo la autentificación por nosotros como manda la especificación de Servlets 2.2 (es de suponer que en las posteriores también, aunque sea con algún pequeño cambio). Este artículo esta enfocado a la version 3.2.x de Tomcat (la que yo uso), pero es totalmente aplicable a la serie 4.x (y a todo servidor que siga la especificación 2.2 de Servlets), salvo en algún aspecto que remarcaré en su momento.


Lo que importa, como he dicho, es la especificación de los Servlets. Tomcat 3.2.* sigue la especificación 2.2, mientras que Tomcat 4.* se esta ajustando a la 2.3, aunque esta aún se esta escribiendo, el trabajo se hace en paralelo. Pero en cuestiones de seguridad la especificación parece que no cambiará demasiado, por eso, la mayor diferencia entre Tomcat 3.* y Tomcat 4.* esta más en el diseño y trabajo interno que en la configuración. Lo único que puede cambiar son los nombres de las clases que realizan la autentificación, pero poco más, al menos nada que nos impida ver como hacerlo en la versión 4 u otro servidor.



La especificación de Servlets 2.2.



La especificación 2.2 de Servlets pone las directrices que los contenedores de Servlets tienen que ofrecer para ser compatibles. Una de estas directrices es la referente a la autentificación de usuarios para proteger los recursos de nuestra aplicación web. Esta directriz hará que sea el servidor el que se ocupe de proteger nuestros recursos de forma automática sin que nosotros escribamos una línea de código, solo tendremos que configurar los descriptores XML, tanto de Tomcat (para decidir el método de autentificación), como de la aplicación (para decidir que recusos son los protegidos).


No os podeis imaginar lo útil que puede ser esta funcionalidad que trae Tomcat (y todo servidor que siga la especificación 2.2 o posterior). El desarrollador de la aplicación web no tiene que preocuparse de nada referente a la autentificación, simplemente pone los enlaces entre las páginas que necesite. Será el encargado de desplegar la aplicación el que por medio del descriptor XML establezca que recursos son los protegidos, y Tomcat, el solito, se encargará de que el usuario se autentifique antes de dejarle acceder a ese recurso.


La especificación de Servlets 2.2 ofrece cuatro tipos de autentificación :


  • BASIC: Es la más sencilla. Simplemente nos saltará una ventana para que introduzcamos el usuario y la contraseña. Si damos un usuario válido pasaremos al recurso protegido, y si no, nos aparecerá una pantalla de error. Podemos definir una pantalla de error para ese tipo de error, pero el proprio cuadro que aparece rompe la estética de la aplicación, por lo que no se usa en aplicaciones de cara al cliente.



  • FORM: Lo que nos aparecerá en esta ocuasión será un formulario que nosotros diseñamos y definimos. Como siempre, si introducimos una pareja usuario-contraseña válida pasaremos al recurso protegido, y si no, nos aparecerá otra página de login erroneo que también podemos definir nosotros. Al estar tan metido dentro de la aplicación (son paginas web, como el resto de la aplicacion), es el método más usado.



  • CLIENT-CERT: Este método requiere un certificado del lado del cliente. Como os podeis imaginar, seguro si que es muy seguro, pero caro también. Sin comentarios.



  • DIGEST: Es un poco más avanzada que la BASIC, pero sigue sin ser espectacular, por lo que apenas se usa.


Lista 1: Tipos de autentificación definidos.


La especificación también define tres modos para indicar la seguridad del transporte de los datos a través de la Web.


  • NONE: Ningún tipo de cifrado, HTTP puro y duro.



  • CONFIDENTIAL: Esto significa SSL. Es decir, que nadie pueda ver ni cambiar lo que enviamos.



  • INTEGRAL: Esto significa que, aunque alguién pueda ver lo que se envia, se garantice que no se cambie por el camino. Normalmente los servidores usan para esto también SSL, aunque no es imprescindible.


Lista 2: Modos de transporte.


A lo largo del artículo indicaré donde hacer esto, pero en este ordenador no tengo instalado el módulo SSL, asi que no podré comprobarlo. Si alguién se anima, que me diga si funciona bien.


La especificación de Servlets (y de J2EE) basa la autentificación en dos aspectos : usuarios y roles. Son dos terminos claros, lo único que puede quedar poco claro es el termino rol, pero es simplemetne otra forma de llamar a los grupos de usuarios. La seguridad de J2EE esta basada en estos roles porque esta claro que en un sistema habrá un número mucho más pequeño de grupos que de usuarios, por lo que todo es más fácil si centramos la seguridad en los grupos.


Eso es todo lo que necesitamos saber de la especificación, asi que ahora pasemos a la acción.



Dominios de seguridad en Tomcat (fichero TOMCAT_HOME\conf\server.xml).



Tomcat ofrece dos dominios de seguridad (aunque nada impide crear los nuestros propios, en realidad Tomcat 4 ofrece ya un tercero). El termino anglosajón es Security Realms, que también se podría traducir como reino, pero a mi me gusta más dominio, aunque quizás en algún sitio lo leais traducido como reino. Estos dominios son en realidad dos clases que se encargan de recibir los datos del usuario al autentificarse y los contrasta con los datos que tiene Tomcat, en una caso contra un fichero XML (TOMCAT_HOME\conf\tomcat-users.xml), y en el otro contra cualquier base de datos que tenga un driver JDBC (o también contra un servidor LDAP con Tomcat4). Si CUALQUIER BASE DE DATOS, interesante, ¿no?.


En el caso del fichero TOMCAT_HOME\conf\tomcat-users.xml el formato se explica por si mismo:

<tomcat-users>
<user name="tomcat" password="tomcat" roles="administrador, usuario_ />
<user name="javahispano" password="javahispano" roles="usuario" />
</tomcat-users>



Realizar la autentificación por medio de una base de datos JDBC requiere un poco más de trabajo. La base de datos en cuestion requiere tres tablas para que funcione correctamente. Los nombres de las tablas y las columnas se pueden personalizar, pero debido a su uso tampoco hace demasiada falta. Esta información la podeis sacar del fichero TOMCAT_HOME\doc\JDBCRealm.howto, pero como siempre, los chicos de Tomcat están muy ocupados para ofrecernos este gran servidor y se les olvida decirnos como configurarlo ;-).


Suponemos que tenemos una base de datos con drivers JDBC disponible, en nuestro caso MySQL, y que hemos copiado el driver JDBC al directorio TOMCAT_HOME\lib (o al equivalente de nuestra aplicación si es la única que lo usa) para que Tomcat pueda encontrarlo y usarlo automáticamente.


La estructura de las tablas es la siguiente (la doy en código SQL porque supongo que todo el que este metido en estas labores puede entenderlo, y si no, solo tiene que copiarlo y ejecutarlo en su SGBD):


  • Una tabla de usuarios para almacenar los usuarios, con su nombre y password

    create table usuarios
    (
    nombre_usuario varchar(15) not null primary key,
    password_usuario varchar(15) not null
    );




  • Una tabla de roles que contendrá el nombnre de los mismos. Tomcat no usa esta tabla para nada (no se si lo hacen otros servidores), pero en fin, esta en la especificación, asi que_

    create table roles
    (
    nombre_rol varchar(15) not null primary key
    );




  • Una tabla que relaciona los usuarios con los roles.

    create table usuarios_roles
    (
    nombre_usuario varchar(15) not null,
    nombre_rol varchar(15) not null,
    primary key( nombre_usuario, nombre_rol )
    );



Lista 3: Tablas necesarias en la base de datos


Por supuesto ttenemos que rellenar esta tablas con valores, aqui un poco de código SQL para introducir los mismos valores que tiene el fichero XML.

insert into usuarios values ('tomcat', 'tomcat');
insert into usuarios values ('javahispano', 'javahispano');

insert into roles values ('administrador');
insert into roles values ('usuario');

insert into usuarios_roles values ('tomcat', 'administrador');
insert into usuarios_roles values ('tomcat', 'usuario');
insert into usuarios_roles values ('javahispano', 'usuario');



El fichero donde debemos decir a Tomcat que tipo de dominio de seguridad usar es el fichero TOMCAT_HOME\conf\server.xml, en la sección de RequestInterceptor para Tomcat 3 y en la sección Realm para Tomcat 4. Nosotros solo tenemos que configurarlo con nuestros datos concretos.


Por defecto Tomcat 3 viene configurado para usar el fichero XML para autentificar los usuarios. La parte que lo indica es la siguiente:

<!- Comprobar permisos usando el fichero xml. -->
<RequestInterceptor
className="org.apache.tomcat.request.SimpleRealm"
debug="0" />



Y Tomcat 4 no trae ninguno configurado por defecto, pero ejemplos para todos los posibles (dónde estarían cuando escribí este artículo por primera vez?!):

<Realm className="org.apache.catalina.realm.MemoryRealm" />



Inmediatamente despúes de estas etiquetas viene la que plantilla de como usar la autentificación contra una base de datos. Una lástima que nadie nos lo hubiera dicho ;-). Esta comentada, y lo único que tendremos que hacer nosotros es, primero comentar la anterior, y segundo descomentar esta y configurarla (en Tomcat 3) o descomentar la que nos interese (en Tomcat 4).

       <!-
Comprobar permisos usando base de datos

Otras opciones para driverName:
driverName="oracle.jdbc.driver.OracleDriver"
connectionURL="jdbc:oracle:thin:@ntserver:1521:ORCL"
connectionName="javahispano"
connectionPassword="javahispano"

"connectionName" y "connectionPassword" son opcionales.
-->
<!--
En nuestro caso tenemos la siguiente configuración
(para MySQL):
Suponemos que a nuestra base de datos
-->

<RequestInterceptor
className="org.apache.tomcat.request.JDBCRealm"
<!--
El valor de debug indicara, como siempre, la
cantidad de informacion que saldra por la consola.
99 = toda, 0 = minima.
-->
debug="99"
driverName="org.gjt.mm.mysql.Driver"
connectionURL="jdbc:mysql://127.0.0.1/autentificacion?user=jh;password=jh"
userTable="usuarios"
userNameCol="nombre_usuario"
userCredCol="password_usuario"
userRoleTable="usuarios_roles"
roleNameCol="nombre_rol" />


      <Realm  className="org.apache.catalina.realm.JDBCRealm" debug="99"
driverName="org.gjt.mm.mysql.Driver"
connectionURL="jdbc:mysql://localhost/authority?user=test;password=test"
userTable="users" userNameCol="user_name" userCredCol="user_pass"
userRoleTable="user_roles" roleNameCol="role_name" />



En función de donde pongamos nuestra configuración (dentro de que elementos del fichero XML) estaremos configurando la seguridad para todo el servidor, para un servidor virtual, o para una única aplicación. Lo podeis ver en el propio archivo con los ejemplos, ahora no quiero mezclar más detallaes de ambos servidores.


Y asi, habiendo escogido uno y solo uno de los dominios de seguridad para que lo utilice Tomcat, podemos, por fin, pasar a usarlo en nuestra aplicación. En principio para hacer las pruebas podeis coger el que más rabia os de, funcionan los dos, y esto tiene que ver solo con la configuración de Tomcat, a nuestra aplicación le da bastante igual que sea una u otra, no tendremos que cambiar nada en ella.



Configurando nuestra aplicación.



Primero tenemos que crear nuestra aplicación de la forma que vimos en el artículo anterior [1]. Ahora no volvere a hacerlo, de modo que si no sabeis como hacerlo consultar como hacerlo en la sección articulos de nuestra web.


Tomcat (al menos la versión 3.2.*, desconozco la situación en la serie 4), solo tiene implementada la autentificación básica (BASIC) y la basada en formularios (FORM), asi que no os molesteis en probar las otras dos (CLIENT_CERT y DIGEST).


En este ejemplo, primero prepararemos nuestra aplicación para utilizar la autentificación básica, y después pasaremos a ver como hacerlo con formularios, que requiere un poco más de explicación, si bien casi todo el proceso es el mismo.


Autentificación BASIC.



Una vez creada la aplicación web pasamos a editar su descriptor de despliegue, que seguramente no será tan sencillo como este, pero para poner un ejemplo es suficiente, no usaremos ningún servlet, ni librerias de etiquetas JSP ni nada por el estilo.

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">

<web-app>

<display-name>Prueba autentificacion</display-name>
<description>
Estoy probando como funciona la
autentificación con Tomcat.
Alberto Molpeceres.
al@javahispano.com
</description>

<!--
Aqui empieza lo relativo a la autentificacion.
Son las llamadas restricciones de seguridad.
-->
<security-constraint>

<!--
Con tantos elementos web-resource-collection
como nos hagan falta vamos indicando las distintas
zonas.
-->
<web-resource-collection>
<web-resource-name>Nombre de la zona</web-resource-name>
<!--
Con url-pattern indicamos que ficheros o directorios
estan protegidos. Para nuestro ejemplo estarán TODOS
los ficheros de la aplicacion (se nos pedirá autentificarnos
nada mas intentar acceder a ella).
Podemos poner tantos elementos url-pattern como necesitemos.
-->
<url-pattern>/*</url-pattern>
<!--
Ahora indicamos los metodos por los que no se
puede acceder
-->
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>

<!--
Lo siguiente es indicar que roles tienen permitido
el acceso, en nuestro caso solo el administrador.
Tantos _role-name_ como necesitemos.
-->
<auth-constraint>
<role-name>administrador</role-name>
</auth-constraint>

<!--
Por fin indicamos el valor de seguridad en el
Transporte. Como dijimos, los posibles valores son :
NONE, CONFIDENTIAL, INTEGRAL.
Usare NONE porque ahora mismo no tengo instalado SSL.
-->
<user-data-constraint>
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>

<!--
Por ultimo indicamos el método por el que
queremos la autentificacion.
En este primer ejemplo, BASIC.
-->
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>Ejemplo de autentificacion basica</realm-name>
</login-config>

</web-app>



Listos. Ahora vamos al navegador e intentamos entrar a nuestra aplicación, veremos que pasa:

Figura 1: Cuadro de diálogo de autentificación básica.


(1)


Simplemente comprabar hasta que os aburrais que según nuestra configuración solo el usuario tomcat con contraseña tomcat puede entrar en la aplicación, ya que es el único usuario que tiene el rol administrador.


Pasemos ahora a algo más interesante: la autentificación por medio de un formulario.



Autentificación FORM.



Lo primero que tenemos que hacer es definir los formularios que utilizará nuestra aplicación, uno para el primer intento, y otro para el resto. Nada nos obliga a que sean dos distintos, pero siempre es mejor decirle al usuario que se ha equivocado al introducir sus datos en lugar de que piense que la aplicación no funciona. En realidad no es necesario que este segundo sea también un formulario HTML, puede ser una simple página que nos informe del error. Unos formularios de ejemplo, muy simples, podrían ser:


El fichero de login, login.html:

<html>
<head>
<title>
Login de aplicacion que requiere autentificacion
</title>
</head>
<body>
<h2>Login</h2>
<br>
<h3>www.javahispano.com</h3>
<br><br>
<form method="post" action="j_security_check">
Usuario: <input type="text" name="j_username"><br>
Password: <input type="text" name="j_password"><br>
</form>
</body>
</html>



Y el que se presentará cuando los datos son incorrectos, login_error.html:

<html>
<head>
<title>
Login de aplicacion que requiere autentificacion
</title>
</head>
<body>
<h2>Login</h2>
<br>
<h3>www.javahispano.com</h3>
<br><br>
<font color="#FF0000">
<h3>Datos incorrectos.<br></h3>
Por favor vuelva a intentarlo.
</font>
<br><br>
<form method="post" action="j_security_check">
Usuario: <input type="text" name="j_username"><br>
Password: <input type="text" name="j_password"><br>
<input type="submit" value="Login">
</form>
</body>
</html>



Tenemos que prestar atención en los distintos valores del formulario, ya que estos son estandar definidos en la especificación 2.2 para que las aplicaciones sean portables entre servidores.


El formulario tiene que enviarse por el método POST, y la acción a realizar será j_secutiry_check. Exactamente así escrita, todo en minúsculas (por si no lo sabeis, desde la version 3.2, Tomcat es sensible a mayúsculas incluso en Windows), y sin / antes. Es decir, /j_security_check y j_Security_Check son erróneos y no funcionarán. Ademas, los campos que tienen los datos del usuario deben llamarse j_username y j_password, no hay más vuelta de hoja, si quieres que Tomcat se encargue de la autentificación por ti, lo tienes que hacer así.


Ahora lo único que tenemos que cambiar respecto al ejemplo anterior es el elemento login-config del descriptor de nuestra aplicación, web.xml para que use la autentificación . Ahora será de la forma:

    <login-config>
<auth-method>FORM</auth-method>
<realm-name>
Ejemplo de autentificacion por medio de formulario
</realm-name>
<form-login-config>
<!--
Estamos suponiendo que hemos puesto los formularios
En el directorio raiz de nuestra aplicacion. Si no fuera
así abria que poner el path correcto.
-->
<form-login-page>/login.html</form-login-page>
<form-error-page>/login_error.html</form-error-page>
</form-login-config>
</login-config>



Tenemos que prestar atención al caso de querer proteger la aplicación entera y usar la autentificación con formulario (aunque no daría problemas con la básica), porque esto convertiría a nuestro formulario de entrada también en un recurso protegido, por lo cual Tomcat no podría mostrarlo y produciría un error. Si queremos hacer algo así, simplemente tendremos que meter nuestra aplicación en un directorio de acceso restringido y dejar los formularios fuera de él.




Apache y Tomcat tienen sus problemas.



Lamanteblemente os he mentido y no todo es así de sencillo, es posible que la primera vez que probeis la autentificación manejada por el servidor se os presenten algunos problemas. Pasaremos a intentar solucionarlos en esta sección, aunque lo dicho aquí no tiene valor alguno si se utiliza Tomcat en solitario (sin servidor Web).


Aunque Tomcat puede servir contenidos estáticos, normalmente lo usamos en conjunción con un servidor Web, muchas veces Apache, de forma que el servidor web se encarga de servir los contenidos estáticos, y Tomcat de los Servlets y de las páginas JSP. Esto se hace para liberar de trabajo a Tomcat, pero puede ser un problema a la hora de manejar la autentificación por medio del contenedor de Servlets.


El problema esta en que nosotros definimos las restricciones de seguridad en Tomcat, y algunas peticiones, las del contenido estático, no llegan a Tomcat, si no que las satisface el servidor Web directamente. Esto hace, que por ejemplo, la pagina secreta.html sea mostrada sin pedir la autentificación del usuario aunque este en un directorio declarado restringido por Tomcat.


Una solución sería transformar todas esas páginas a JSP, con solo cambiar la extensión de esas páginas sería suficiente. Otra solución sería decirle a Apache que le pasase TODAS las peticiones de nuestra aplicación a Tomcat (luego explico como se hace). Algunas personas dicen que Tomcat es lo suficientemente potente para soportar esto y más, pero tampoco es necesario, asi que veremos otra alternativa. Lo que nosotros vamos a hacer, es pasarle solo las peticiones de los recursos protegidos (por supuesto también todos los servlets y páginas JSP).


En el artículo anterior, sobre como instalar nuestras aplicaciones en Tomcat[1] vimos la forma de informar a Apache de que direcciones tenía que pasarle a Tomcat por tratarse con Servlets o JSP (viene al final del artículo). Ahora veremos como hacer para pasarle el resto de las cosas que necesitamos.


Lo que teníamos en el artículo anterior era algo asi como :

#
# Ahora informamos a Apache de que le mande las peticiones
# de Servlets y JSP a Tomcat.
# Solo le envia estas peticiones, ya que Apache sirve
# más rapidamente el contenido estatico.
# Ademas le decimos que protocolo (worker) usar, si tenemos el
# 13 mejor que 12 ;-).
# Ver el _como instalar Tomcat_ en nuestra web
# para masinformacion.
#
JkMount /mi_aplicacion/servlet/* ajp13
JkMount /mi_aplicacion/*.jsp ajp13



Lo que ahora tenemos que añadir aquí es el directorio o directorios que tenemos protegidos para que los administre Tomcat, sea contenido estático o dinámico. Simplemente añadimos una linea de la forma:

JkMount /mi_aplicacion/protegido/* ajp13



Para que Tomcat administrase toda la aplicación y sirviese tanto el contenido estático como el dinámico, deberiamos sustituir las líneas anteriores por una de la forma:

JkMount /mi_aplicacion/* ajp13



Y para terminar con este asunto, si Tomcat no se encarga de servir todas las peticiones a la aplicación, es decir, no utilizamos la línea anterior, tendremos que decirle a Apache que le pase a Tomcat otra dirección interesante, la que se encarga de autentificar a los usuarios, que como hemos visto anteriormente es j_security_check, asi que añadimos

JkMount /mi_aplicacion/j_security_check ajp13



Con esto esta todo, podemos configurar la relación Apache-Tomcat de la forma que más nos convenga, es problema del administrador decidir quién sirve qué.




Notas



(1) Como siempre mis textos estan en alemán, si alguién tiene gana de mandarlos en castellano, pues ya sabe, que me la envíe.

Recursos




[1] Instalación de aplicaciones en Tomcat.,
/articulos/ver_articulo.jsp?id=21


Acerca del autor

Alberto Molpeceres
Alberto es ahora mismo desarrollador de aplicaciones en ámbito cliente/servidor para la empresa T-Systems - debis Systemhaus en Munich (Alemania). Cuando no está trabajando o "metiendo caña" al resto de los integrantes de javaHispano intenta pasear con su novia, buscar la desaparecida lógica del idioma alemán o intentar olvidar la pesadilla que es buscar piso en Munich.

viernes
ago102001

Nuevas API de Sun

Sun ha hecho públicas en pocos días las nuevas implementaciones de tres conocidas APIs, o mejor dicho, las nuevas versiones de JMF (2.1.1) y de Java3D (1.2.1_02) y la nueva versión del JDK para Soalris (1.2.2_09).

La nueva versión de JMF se centra en la mejora de rendmiento y facilidad de eso de la multimedia en tiempo real, mientras que en Java3D se han ocupado de corregir numerosos bugs de la versión anterior, así como de incluir nuevos ejemplos para facilitar el aprendizaje.
viernes
ago102001

alphaBeans

Muchos de vosotros conocereis la pagina web de IBM dedicada al opensource (alphaworks), pero quizás no todos conozcais que también tienen una sección dedicada exclusivamente a útiles Java Beans preparaditas para usar. Si alguién no lo sabía, aqui lo suelto.
viernes
ago032001

Avalon 4 de Apache-Jakarta listo.

El subprojecto Avalon, de la fundación Apache, projecto Jakarta, es un intento de conseguir un framework de apoyo para el desarrollo de aplicaciones java. Por tanto, NO es una aplicacißon independiente.
Avalon esta dividido en subproyectos, que abarcan desde un conjunto de componentes comunes (por ejemplo parsing de ficheros o control de hilos) a un servidor de aplicaciones, de un framework para hacer pruebas a otro para registar las acciones del sistema.
En definitiva, Avalon es un producto muy completo que no se debe dejar de tener en cuenta.
miércoles
ago012001

Pool de Constantes



El pool de constantes Java







En un fichero de clases java el pool de constantes tiene un función similar a la tabla de símbolos en otros lenguajes. Esta constituido por una serie de referencias simbólicas a los tipos y constantes utilizados por una clase. Una referencia simbólica hacia un miembro de otra clase esta formada por el nombre completo (con paquetes) de la clase y el nombre del método o campo en dicha clase. Los miembros de la clase cuyo pool se examina también aparecen como referencias simbólicas.





En pool de constantes también existen entradas describiendo las constantes de cadena y de datos primitivos, y literales numéricos utilizados en expresiones. El contenido de dichas entradas es el valor constante.





Las referencias simbólicas son sustituidas por referencias directas (punteros o desplazamientos) hacia los miembros que apuntan durante el último paso del enlazamiento de una clase, denominado resolución. La estrategia mas común es no resolver el pool de una clase inmediatamente tras su carga, sino posponer la resolución de las entradas en el pool hasta su primer uso. Durante la resolución de una entrada que apunta hacia un miembro de otra clase se comprueba la existencia y accesibilidad de la otra clase y del campo o método declarado en ella. La accesibilidad viene dada por los modificadores private, public y protected.





El compilador genera un pool de constantes en cada fichero de clases. Cuando la maquina virtual carga una clase construye el pool de constantes runtime en la zona de datos del tipo asociada a dicha clase.





En la siguiente dirección podemos encontrar la especificación de la maquina virtual y en ella detalles sobre el pool de constantes: http://java.sun.com/docs/books/vmspec/2nd-edition/html/VMSpecTOC.doc.html





El pool de constantes en el fichero de clase es precedido de un número que proporciona la cuenta de entradas en el pool. Este número siempre excede en uno a la cantidad de entradas existentes en el pool.









Entradas en el pool







El formato de las tablas siguientes es:







NOMBRE_De_La_Entrada



nº de bytes

nombre del subcampo en la entrada

descripción







Entradas que describen valores literales







CONSTANT_Integer_Info



1 byte

tag

que identifica el tipo, en este caso tiene un

valor de 3



4 bytes

bytes

cuatro bytes sin signo con el valor de una

constante entera (int)





CONSTANT_Float_Info



1 byte

tag

que identifica el tipo, en este caso tiene

un valor de 4



4 bytes

bytes

cuatro bytes sin signo con el valor de una

constante float en orden big-endian





CONSTANT_Long_Info



1 byte

tag

5



8 bytes

bytes

ocho bytes sin signo con el valor de una

constante del tipo long en orden big-endian





CONSTANT_Double_Info



1 byte

tag

6



8 bytes

bytes

ocho bytes sin signo con el valor de una

constante del tipo double en orden big-endian







Las dos últimas entradas ocupan dos posiciones en pool. Por ejemplo si una entrada del tipo CONSTANT_Long_Info esta situada en la entrada numero 5 del pool, la siguiente entrada estaría en el numero 7. Y no existiría entrada con el número 6.







CONSTANT_String_Info



1 byte

tag

8



2 bytes

string_index

indice en el pool de una entrada del tipo

CONSTANT_Utf8_Info conteniendo el literal

de cadena correspondiente a esta entrada.







Mas adelante nos referiremos a estas cinco entradas como entradas de literales.







Entrada describiendo literales de cadena o nombres especiales







CONSTANT_Utf8_Info



1 byte

tag

1



2 bytes

length

longitud en bytes que a continuación describen

una cadena o un nombre de tipo o atributo



length bytes

bytes

caracteres en un formato Utf8 modificado







Esta entrada puede contener el valor de los literales de cadena declarados en el código fuente. Dichas cadenas se convierten a un objeto del tipo String como resultado de la resolución de la entrada. También se almacenan en este tipo de entrada los nombres completos de clases o interfaces, los nombres y descriptores de métodos y campos, y los nombres de los atributos del fichero de clase.





El formato Utf8 modificado es el siguiente:







Rango de caracteres

Representado por



'\u0001' a '\u007F'

0XXX XXXX (1 byte)



'\u0080' a '\u07FF' y '\u0000'

110X XXXX 10XX XXXX (2 bytes)



'\u0800' a '\uFFFF'

1110 XXXX 10XX XXXX 10XX XXXX (3 bytes)







Las diferencias con el formato estándar Utf8 son que no se reconocen los formatos de mas de 3 bytes y que el carácter null se codifica con dos bytes en vez de uno. Esta última diferencia evita que aparezca un byte a cero en una cadena en Java.







Entradas apuntando a tipos o miembros de tipos







CONSTANT_Class_Info



1 byte

tag

7



2 bytes

name_index

indice en el pool de una entrada del tipo

CONSTANT_Utf8_Info conteniendo el nombre

completo de una clase o interfaz, o bien

el descriptor de una matriz. Por ejemplo

“[[D” para “double[][]”.



CONSTANT_NameAndType_Info



1 byte

tag

12



2 bytes

name_index

indice en el pool de una entrada del tipo

CONSTANT_Utf8_Info conteniendo el nombre

simple de un campo o método. El nombre ha

de ser un identificador java valido o

“” para un constructor.



2 bytes

descriptor_index

Indice en el pool de una entrada del tipo

CONSTANT_Utf8_Info conteniendo el descriptor

de un campo o método.





CONSTANT_Fieldref_Info



1 byte

tag

9



2 bytes

class_index

indice en el pool de una entrada del tipo

CONSTANT_Class_Info conteniendo el nombre

completo de la clase o interfaz donde fue

declarado el campo apuntado por esta entrada.



2 bytes

name_and_

type_index

Indice en el pool de una entrada del tipo

CONSTANT_NameAndType conteniendo indices

en pool para dos entradas del tipo

CONSTANT_Utf8_info indicando el nombre

y descriptor del campo.





CONSTANT_Methodref_Info



1 byte

tag

10



2 bytes

class_index

indice en el pool de una entrada del tipo

CONSTANT_Class_Info conteniendo el nombre

completo de la clase (no interfaz) donde

fue declarado el método apuntado por esta

entrada.



2 bytes

name_and_

type_index

Indice en el pool de una entrada del tipo

CONSTANT_NameAndType conteniendo indices

en pool para dos entradas del tipo CONSTANT_Utf8_info

indicando el nombre y descriptor del método.





CONSTANT_InterfaceMethodref_Info



1 byte

tag

11



2 bytes

class_index

indice en el pool de una entrada del tipo

CONSTANT_Class_Info conteniendo el nombre

completo de el interfaz (no clase) donde fue declarado

el campo apuntado por esta entrada.



2 bytes

name_and_

type_index

Indice en el pool de una entrada del tipo

CONSTANT_NameAndType conteniendo indices en

pool para dos entradas del tipo CONSTANT_Utf8_info

indicando el nombre y descriptor del campo.









Ejemplo 1





class Strings {

//Los Literales de cadena sean constantes,estáticos, o instancias, generan

//una entrada CONSTANT_String_Info en el pool de la clase que los declara

String string1 = "Esto es un literal de cadena" ;

final String string2 = "Esto es un literal de cadena"

static final String string3 = "Esto es un literal de cadena"

}





Cada una de las tres lineas generaría una entrada CONSTANT_String_Info en el pool de de la clase Strings. Pero, al

ser el contenido de todas las literales de cadena idéntico únicamente se genera una:





--------------------------------------------------------

8 8 tag (2) CONSTANT_String

0 0 string_index Esto es un literal de cadena

19 11

--------------------------------------------------------





La cual apunta a una entrada CONSTANT_Utf8_Info conteniendo la cadena:





--------------------------------------------------------

1 1 tag (19) CONSTANT_Utf8

0 0 length 28

28 1c

69 45 E bytes Esto es un literal de cadena

115 73 s

116 74 t

111 6f o

32 20

101 65 e

115 73 s

32 20

117 75 u

110 6e n

32 20

108 6c l

105 69 i

116 74 t

101 65 e

114 72 r

97 61 a

108 6c l

32 20

100 64 d

101 65 e

32 20

99 63 c

97 61 a

100 64 d

101 65 e

110 6e n

97 61 a

--------------------------------------------------------





Puede examinar el fichero de clase entero compilando Strings.java y proporcionado el siguiente comando al analizador

de clases ParseClass descargable desde “usuarios.tripod.es/JoseBotella/” :





“ java -classpath c:\ josebotella.parseclass.ParseClass Strings.class “ , el cual produce String.class.txt







Ejemplo 2





class UsandoStrings {

//Las clases que usan literales de cadena declaradas en otras clases

//contienen en su pool entradas CONTANT_Fieldref_Info para las variables

//de instancia (string1) y CONSTANT_String_Info para los campos con el modificador

//“final”

public static void main(String args[]) {

System.out.println(Strings.string3);

System.out.println(new Strings().string2);

System.out.println(new Strings().string1);

}

}





UsandoStrings.class.txt contiene en su pool una entrada CONSTANT_Fieldref_info para la tercera linea, y una

CONSTANT_String_Info para las dos primeras. Puede comprobar que las lineas primera y segunda del método main producen una entrada del tipo String en vez de Fieldref si comenta la tercera linea, compila y utiliza el analizador de clases.





Como ejemplo de entrada CONSTANT_Fielref_Info podemos presentar la producida por la linea tercera en main :





-------------------------------------------------------

9 9 tag (7) CONSTANT_Fieldref

0 0 class_index Strings

5 5

0 0 name_and_type_index string1 Ljava/lang/String;

25 19

--------------------------------------------------------





Esta entrada apunta a las entradas número 5 y 25 del pool. Estas otras a su vez, proporcionan la clase donde se declaró el campo, su nombre y descriptor:





--------------------------------------------------------

7 7 tag (5) CONSTANT_Class

0 0 name_index Strings

24 18

--------------------------------------------------------



--------------------------------------------------------

12 c tag (25) CONSTANT_NameAndType

0 0 name_index string1

34 22 "

0 0 descriptor_ index Ljava/lang/String;

35 23 #

--------------------------------------------------------





Donde puede ver que estas entradas apuntan a otras de número 24, 34 y 35. En el fichero UsandoStrings.class.txt se observa que tales entradas, del tipo CONSTANT_Utf8_Info, contienen respectivamente las cadenas: Strings, string1 y Ljava/lang/String;







Ejemplo 3







class NumericosFinal {

//Todos los literales constantes de tipos primitivos generan entradas

//CONSTANT_Fieldref en el pool de la clase que los declara.

//Ademas existen las apropiadas entradas de literales independientemente

//del valor de la constante

final byte byteF = (byte) 127 ;

final short shortF = (short) 3;

final int intF = 32767;

final long longF = 1L;

final float floatF = 1.5F;

final double doubleF = 1.55;

final char charF = 'B';

final boolean booleanF = true;

}





En el fichero NumericosFinal.class.txt puede ver que se han generado entradas CONSTANT_Fieldref para cada uno de los campos. Y que el valor de cada constante se ha almacenado en una entrada apropiada tal como se relaciona a continuación:





boolean, byte, short, char, int............CONSTANT_Integer_Info double..............................................CONSTANT_Double_Info float..................................................CONSTANT_Float_Info long..................................................CONSTANT_Lonf_Info String................................................CONSTANT_String_Info





A titulo de ejemplo la entrada para el campo booleanF es:





--------------------------------------------------------

3 3 tag (38) CONSTANT_Integer

0 0 bytes 1

0 0

0 0

1 1

--------------------------------------------------------







Ejemplo 4







Ahora una clase que utiliza la anterior:





class UsandoNumericosFinal {

//Las clases que usan literales numéricos constantes declarados

//en otras clases no contendrán en su pool entradas del tipo

//CONSTANT_Fieldref, sino de los tipos literales. O bien, ninguna entrada

//dependiendo del valor de la constante

public static void main(String args[]) {

NumericosFinal nf = new NumericosFinal();

System.out.println(nf.booleanF);

System.out.println(nf.byteF);

System.out.println(nf.intF);

System.out.println(nf.longF);

System.out.println(nf.shortF);

System.out.println(nf.floatF);

System.out.println(nf.doubleF);

System.out.println(nf.charF);

}

}





En su correspondiente fichero UsandoNumericosFinal.class.txt se observa que que no existen entradas del tipo Fieldref. Únicamente contamos con :





--------------------------------------------------------

4 4 tag (8) CONSTANT_Float

63 3f ? bytes 1.5

192 c0 À

0 0

0 0

--------------------------------------------------------



--------------------------------------------------------

6 6 tag (10) CONSTANT_Double

63 3f ? bytes 1.55

248 f8 ø

204 cc Ì

204 cc Ì

204 cc Ì

204 cc Ì

204 cc Ì

205 cd Í

--------------------------------------------------------





Estas entradas se refieren a los campos floatF y doubleF. Luego, ¿como emplea nuestro programa el resto de los campos?

La respuesta nos la proporciona el desensamblador javap de la forma siguiente: “javap -c UsandoNumericosFinal”





El comando anterior produce la salida:





Method void main(java.lang.String[])

0 new #2 (Class NumericosFinal)

3 dup

4 invokespecial #3 (Method NumericosFinal())

7 astore_1

8 getstatic #4 (Field java.io.PrintStream out)

11 iconst_1 //Se ocupa del boolean

12 invokevirtual #5 (Method void println(boolean))

15 getstatic #4 (Field java.io.PrintStream out)

18 bipush 127 //Se ocupa del byte

20 invokevirtual #6 (Method void println(int))

23 getstatic #4 (Field java.io.PrintStream out)

26 sipush 32767 //Se ocupa del int

29 invokevirtual #6 (Method void println(int))

32 getstatic #4 (Field java.io.PrintStream out)

35 lconst_1 //Se ocupa del long

36 invokevirtual #7 (Method void println(long))

39 getstatic #4 (Field java.io.PrintStream out)

42 iconst_3 //Se ocupa del short

43 invokevirtual #6 (Method void println(int))

46 getstatic #4 (Field java.io.PrintStream out)

49 ldc #8 (Real 1.5) //Se ocupa del float

51 invokevirtual #9 (Method void println(float))

54 getstatic #4 (Field java.io.PrintStream out)

57 ldc2_w #10 (Double 1.55) //Se ocupa del double

60 invokevirtual #12 (Method void println(double))

63 getstatic #4 (Field java.io.PrintStream out)

66 bipush 66 //Se ocupa del char

68 invokevirtual #13 (Method void println(char))

71 return





Como puede verse en el método main existen una serie de bytecodes que evitan la creación de entradas en el pool para

todos los campos excepto doubleF y floatF. Ello depende del valor que toman los campos.

Por ejemplo, la instrucción iconst_1 coloca un 1 en el pool de operandos para representar el valor true, y bipush 127, coloca dicho valor para el byte. Sin embargo, si pretendiésemos asignar 32768 a intF, no podría emplearse sipush 32768 para tal fin puesto que el bytecode sipush coloca en la pila de operandos un número dentro del rango short; como resultado la asignación de 32768 a un int debería hacerse con una entrada en el pool. Esta es la forma empleada en las lineas 57 y 49. ¿Por que?, porque no existe ninguna instrucción que sea capaz de asignar los valores 1.5 y 1.55 sin ayuda de una entrada en el pool. De haber querido asignar 1.0, el compilador hubiese empleado los bytecodes fconst_1 o dconst_1.

En definitiva, cuando el compilador juzga que un determinado bytecode puede evitar la referencia a una entrada en el pool, buscando una reducción en el numero de bytes o una ejecución mas rápida, utiliza dicha instrucción. Los posibles bytecodes empleados por el compilador a tal efecto son: bipush, sipush, iconst_(0,1,2,3,4,5,m1), dconst_(0,1), fconst_(0,1,2) y lconst_(0,1) . Puede encontrar una descripción de los bytecodes en el libro Inside the Java 2 Virtual Machine de Bill Venners.





En este programa el compilador no emplea entradas de literales cuando puede evitarlo. Solo en los casos doubleF y floatF hace referencia a entradas del tipo CONSTANT_Double y CONSTANT_Float existentes en su propio pool. Pues bien, estas entradas son copias de las existentes en el pool de la clase NumericosFinal.





Una última observación. En el fichero javapNumericosFinal.txt , obtenido con javap, también puede observarse la exclusión, siempre que sea posible, de las referencias al pool de la clase NumericosFinal. En esta clase, el constructor, a pesar de tener a su disposición entradas en el pool para cada una de las constantes, simplemente las evita.







Ejemplo 5







class NumericosFinalStatic {

//Las clases que declaran constantes estáticas no cuentan en su pool con entradas

//del tipo Fieldref sino de los tipos literales apropiados para contener el valor

final static byte byteSF = (byte) 12 ;

final static short shortSF = (short) 2;

final static int intSF = 32768;

final static long longSF = 5L;

final static float floatSF = 1.0F;

final static double doubleSF = 1.0;

final static char charSF = 'a';

final static boolean booleanSF = false;

}





En el fichero NumericosFinalStatic.class.txt no existe ninguna entrada CONSTANT_Fielref, pero si las correspondientes entradas CONSTANT_Integer etc.

Como todas los campos estáticos son constantes el método de inicialización de clase (<clinit>) no tiene código para inicializar los campos estáticos. De hecho, ni siquiera existe tal método en javapNumericosFinalStatic.txt







Ejemplo 6





class UsandoNumericosFinalStatic {

//De nuevo no existen entradas del tipo Fieldref, sino del tipo literal apropiado

//para el valor, siempre y cuando el valor no pueda ser computado por uno de los

//bytecodes vistos anteriormente

public static void main(String args[]) {

NumericosFinalStatic nfs = new NumericosFinalStatic();

System.out.println(nfs.booleanSF);

System.out.println(nfs.byteSF);

System.out.println(nfs.intSF);

System.out.println(nfs.longSF);

System.out.println(nfs.shortSF);

System.out.println(nfs.floatSF);

System.out.println(nfs.doubleSF);

System.out.println(nfs.charSF);

}

}





En el fichero UsandoNumericosFinalStatic.class.txt puede observarse la inexistencia de entradas Fieldref. Si figuran, en cambio, las dos entradas:





--------------------------------------------------------



3 3 tag (7) CONSTANT_Integer

0 0 bytes 32768

0 0

128 80 ?

0 0

--------------------------------------------------------

5 5 tag (8) CONSTANT_Long

0 0 bytes 5

0 0

0 0

0 0

0 0

0 0

0 0

5 5

--------------------------------------------------------





Las cuales se corresponden con los valores que no pueden ser expresados de otra forma, tal como se pone de manifiesto en

el fichero javapUsandoNumericosFinalStatic.txt







Ejemplo 7







class Numéricos {

//Cuando no son constantes las entrada de los tipos literales se generan únicamente

//si su valor no permite utilizar un bytecode que suponga un ahorro en bytes o

//mayor velocidad. En cambio se generan entradas Fieldref para todos los campos.

//Este comportamiento no depende del carácter estático o instancia del campo.

byte bipush = 127;

short bipushUnShort = 127;

short sipushUnShort = 128;

char bipusAChar = 'a';

char CONSTANT_Integer_2 = '\uFFFF' ;

int CONSTANT_Integer = 32768;

int sipush = 32767;

int bipushUnInteger = 127;

long CONSTANT_Long = 2;

long lconst_1 = 1;

float fconstant_2 = 2;

float CONSTANT_Float = 3;

double dconstant_1 = 1;

double CONSTANT_Double = 2;

static int CONSTANT_INTEGER_AStaticInt = 32769;

static int bipushUnIntegerStatic = 126;

}





En el fichero Numericos.class.txt se observa que los campos que generan entradas literales en el pool son nombrados empezando por CONSTANT. El resto poseen un nombre que recuerda la instrucción de la maquina virtual usada en lugar de una entrada en el pool. Todos los campos producen entradas CONSTANT_Fieldref.

En el fichero javapNumericos.txt se proporcionan estos bytecodes.







Ejemplo 8







class UsandoNumericos {

//Si una clase utiliza variables de otra, ya sean estáticas o no, su pool no contiene

//entradas de los tipos literales sino Fieldref

public static void main(String args[]) {

Numéricos n = new Numéricos();

System.out.println(n.CONSTANT_Long);

System.out.println(n.lconst_1);

System.out.println(Numericos.CONSTANT_INTEGER_AStaticInt);

System.out.println(Numericos.bipushUnIntegerStatic);

}

}





En el fichero UsandoNumericos.class.txt vemos cuatro entradas CONSTANT_Fieldref. Para los curiosos también

proporciono javapUsandoNumericos.txt donde los bytecodes getfield o getstatic acceden a los campos correspondientes.







Conclusiones de los ejemplos 1 a 8







Estos ejemplos han pretendido descubrir la relación entre el tipo de entrada producida en el pool y el tipo de campo.

Estas son las conclusiones:





a) Variables estáticas y de instancia .





La clase que declara variables contiene en su pool entradas Fielref para ellas. También contiene entradas del tipo Integer,

Long, Double, Float o String (literales) para expresar los valores asignados a las variables, siempre y cuando dicho valor no

pueda expresarse con un bytecode. La clase que usa variables definidas en otras clases contiene en su pool entradas del tipo

Fieldref para hacer referencia a las variables independientemente de su valor.





b) Constantes estáticas.





Las clases que declaran constantes estáticas cuentan en su pool con entradas de los tipos literales independientemente del

valor de la constante. La clase que usa constantes estáticas definidas en otras clases contendrá en su pool entradas de los

tipos literales siempre y cuando no exista un bytecode que pueda expresar el valor de la constante.





c) Constantes de instancia.





La clase que declara constantes no estáticas contiene en su pool entradas Fielref para todas ellas. Por lo demás, siguen el

mismo patrón que las constantes estáticas al generar entradas en el pool.







Ejemplo 9





En esta clase tenemos cinco ocurrencias del literal 90 double. Todas ellas generan una sola entrada CONSTANT_Double en

el pool. El fichero ExpresionNumerica.class.txt expone dicha entrada.





class ExpresionNumerica {

int expresión = (int) (Math.sin(90) + Math.sin(90) + Math.sin(90) );

double d1 = 90.0;

double d2 = 90.0;

}







Ejemplo 10





class Métodos {

//Los métodos declarados en una clase no originan entradas

//CONSTANT_Methodref en el pool de la clase

static int i = 3;

static int MetodoEstatico(int inti) { return inti; }

void MetodoInstancia() { }

}





En el fichero Metodos.class.txt no existe ninguna entrada Methodref para los métodos declarados en la clase. Solo la

invocación de los mismos las produciría. La única invocación de método es la del constructor de la clase padre:





--------------------------------------------------------

10 a tag (1) CONSTANT_Methodref

0 0 class_index java/lang/Object

4 4

0 0 name_and_type_index (init) ()V

17 11

--------------------------------------------------------





La entrada número 4 es del tipo CONSTANT_Class e indica la clase donde reside el método que se invocara. La número

17 es del tipo CONSTANT_NameAndType y apunta a su vez a otras dos del tipo CONSTANT_Utf8. Por último dichas

entradas contienen las cadenas con el nombre del método “<init>” y su descriptor “( )V”.





La invocación del constructor padre se produce dentro del constructor de la propia clase Métodos como vemos en la salida

de “javap -c Metodos” :





class Métodos extends java.lang.Object {

static int i;

Métodos();

static int MetodoEstatico(int);

void MetodoInstancia();

static {};

}

Method Métodos()

0 aload_0

1 invokespecial #1 (Method java.lang.Object())

4 return

Method int MetodoEstatico(int)

0 iload_0

1 ireturn

Method void MetodoInstancia()

0 return

Method static {}

0 iconst_3

1 putstatic #2 (Field int i)

4 return





Vemos dos métodos creados automáticamente por el compilador. El constructor por defecto cuyo nombre es “<init>” y el

inicializador de clase “<clinit>”. Aunque javap elige mostrarnos otros nombres. La instrucción “invokespecial #1” invoca el

método al cual la entrada número uno del pool referencia, es decir el constructor padre.

El método <clinit>, (aquí static), inicializa las variables estáticas y nunca es llamado por el programador, sino por la propia

maquina virtual . Esto determina que nunca existira una entrada en el pool para él.







Ejemplo 11





class UsandoMetodos {

public static void main(String[] args) {

Metodos.MetodoEstatico(1);

Métodos me = new Métodos();

me.MetodoInstancia();

}

}





El fichero UsandoMetodos.class.txt muestra dos entradas en el pool. Invocando una al constructor padre de esta clase, y la

otra al constructor de la clase Métodos producida por la instrucción “new”. También aparecen la entradas siguientes

“apuntando simbólicamente a los métodos declarados en el ejemplo 10:





--------------------------------------------------------

10 a tag (2) CONSTANT_Methodref

0 0 class_index Métodos

3 3

0 0 name_and_type_index MetodoEstatico (I)I

17 11

--------------------------------------------------------

--------------------------------------------------------

10 a tag (5) CONSTANT_Methodref

0 0 class_index Métodos

3 3

0 0 name_and_type_index MetodoInstancia ()V

19 13

--------------------------------------------------------





En estos ejemplos la clase apuntada por class_index corresponde al tipo de la referencia sobre la cual se invoca el método.

En nuestro caso las referencias son “me” y “Metodos”.







Ejemplo 12





class SubMetodos extends Métodos implements Interfaz {

//El compilador hace que class_index de CONSTANT_Methodref o de

//CONSTANT_InterfaceMethodref apunte al tipo donde reside la declaración, del método

//que se esta invocando, mas cercana a la clase desde donde se invoca.

//El compilador busca el método en la clase o interfaz dada por la expresión sobre la

//que se produce la invocación del método.

//El compilador busca tanto métodos declarados en ese tipo como heredados de sus

//supertipos. class_index apunta al tipo padre si el método fue heredado y al hijo

//si el método fue sobrescrito.

public void MetodoInstancia() {

//sobrescribe el método padre

//que no haga nada no es relevante

}

public static void main(String[] args) {

new SubMetodos().test();

}

void test() {// class_index apuntara a...

super.MetodoInstancia(); //...Metodos 1

this.MetodoInstancia(); //...SubMetodos 5

this.MetodoEstatico(1); //...Metodos 9

MetodoInstancia(); //...SubMetodos 14

Métodos met = new SubMetodos();

met.MetodoInstancia(); //...Metodos 26

Interfaz inter = new SubMetodos();

inter.MetodoInstancia(); //...Interfaz 38

}

}



interface Interfaz {

void MetodoInstancia() ;

}





Deseamos comprobar el tipo apuntado por class_index en cada una de las invocaciones anteriores.

En el fichero SubMetodos.class.txt se pueden observar las siguientes entradas:





--------------------------------------------------------

10 a tag (5) CONSTANT_Methodref

0 0 class_index Métodos

9 9

0 0 name_and_type_index MetodoInstancia ()V

24 18

--------------------------------------------------------

10 a tag (6) CONSTANT_Methodref

0 0 class_index SubMetodos

2 2

0 0 name_and_type_index MetodoInstancia ()V

24 18

--------------------------------------------------------

10 a tag (7) CONSTANT_Methodref

0 0 class_index Métodos

9 9

0 0 name_and_type_index MetodoEstatico (I)I

25 19

--------------------------------------------------------

11 b tag (8) CONSTANT_InterfaceMethodref

0 0 class_index Interfaz

10 a

0 0 name_and_type_index MetodoInstancia ()V

24 18

--------------------------------------------------------





En la salida de “ javap -c Submetodos “ encontraremos las instrucciones de la maquina virtual que las utilizan:





Method void test()

0 aload_0

1 invokespecial #5 (Method void MetodoInstancia())

4 aload_0

5 invokevirtual #6 (Method void MetodoInstancia())

8 iconst_1

9 invokestatic #7 (Method int MetodoEstatico(int))

12 pop

13 aload_0

14 invokevirtual #6 (Method void MetodoInstancia())

17 new #2 (Class SubMetodos)

20 dup

21 invokespecial #3 (Method SubMetodos())

24 astore_1

25 aload_1

26 invokevirtual #5 (Method void MetodoInstancia())

29 new #2 (Class SubMetodos)

32 dup

33 invokespecial #3 (Method SubMetodos())

36 astore_2

37 aload_2

38 invokeinterface (args 1) #8 (InterfaceMethod void MetodoInstancia())

43 return





En el programa figura en cada invocación de método la linea en este listado donde se encuentra el bytecode que realiza la

llamada. Por ejemplo: “ super.MetodoInstancia() //Métodos 1 “. En el listado de arriba la linea comenzando por 1 muestra

el bytecode “invokeespecial #5” utilizado para realizar la llamada del método. El dígito 5 indica la entrada en el pool que

hace referencia al método llamado. Ademas en el comentario de la linea se indica que el class_index de la entrada ( #5 )

producida en el pool por dicha linea es del tipo “Metodos”.





En “ super.MetodoInstancia(); ” super tiene un tipo que es padre del que posee this: Métodos.

En “ this.MetodoInstancia(); ” this tiene el tipo SubMetodos.

En estos dos ejemplos, el tipo dado por la expresión sobre la cual se realiza la invocación del método declaró un método

cuyo descriptor coincide con el del método que se busca. Cumpliéndose ademas que dicho método es accesible según las

normas de los modificadores de acceso (public, protected, private), el tipo de la expresión es seleccionado por el compilador

para ser apuntado por class_index.





En “ this.MetodoEstatico() “ this, naturalmente, sigue teniendo el tipo “SubMetodos” pero el “MetodoEstatico” fue definido

an la clase padre y heredado, no sobrescrito. Luego class_index de la entrada en el pool número 7 apunta a la clase

Métodos. El compilador observó que la clase “SubMetodos” no definía el método que se esta invocando y buscó entre sus

supertipos. El carácter estático del método no varia el comportamiento descrito respecto de un método de instancia ni final.





En “ MetodoInstancia(); ” el tipo de la “expresión” es “SubMetodos”. Porque esta invocación es equivalente a usar “this”.

La clase “SubMetodos” si declaró ( sobrescribió ) el método invocado, luego esta es la clase apuntada por class_index.





En “ met.MetodoInstancia(); ” el tipo de “met” es la clase con la fue declarado. Esto es, “Metodos”.





En “ inter.MetodoInstancia(); ” el tipo de “inter” es “Interfaz”. En dicho interface fue declarado un método con el mismo

descriptor que aquel que se esta invocando, luego es dicho tipo el que aparece en class_index de la entrada

CONSTANT_InterfaceMethodref. Este tipo de entrada solo se genera cuando se invoca un método declarado en un

interface, pero únicamente si la invocación se realiza a través de una referencia cuyo tipo es un interface. En nuestro ejemplo

la referencia es “inter”.







Conclusiones de los ejemplos 10 a 12





Las entradas CONSTANT_InterfaceMethodref y CONSTANT_Methodref son generadas por la invocación de un método

en una clase. No los son por la declaración de métodos.

El tipo apuntado por class_index de una entrada en el pool del tipo CONSTANT_InterfaceMethodref o

CONSTANT_Methodref es el tipo de la “referencia o expresión” sobre la cual se produce la invocación del método,

siempre y cuando dicho tipo contenga una declaración del método invocado. En caso contrario el compilador buscara el

primer supertipo que lo declare.

Una entrada CONSTANT_InterfaceMethodref en el pool es creada únicamente por la invocación de un método declaradoen un interface, si esta invocación se produce sobre una referencia del tipo interface.
















José Botella.



Para cualquier duda o tirón de orejas, e-mail a:
Jose139090_ARROBA_netscape.net






Este artículo esta republicado de la página personal de José Botella. Consultalá para tener acceso a la revisión más actual del mismo.