Guía de Azure Functions para desarrolladores de Java

Esta guía contiene información detallada para ayudarle a desarrollar correctamente Azure Functions con Java.

Como desarrollador Java, si no está familiarizado con Azure Functions, considere la posibilidad de leer primero uno de los siguientes artículos:

Introducción Conceptos

Fundamentos de las funciones de Java

Una función Java es un método public decorado con la anotación @FunctionName. Este método define la entrada de una función Java, y debe ser única en un paquete determinado. El paquete puede tener varias clases con varios métodos públicos anotados con @FunctionName. Se implementa un único paquete en una aplicación de funciones en Azure. Cuando se ejecuta en Azure, la aplicación de funciones proporciona el contexto de implementación, ejecución y administración de las funciones individuales de Java.

Modelo de programación

Los conceptos de desencadenadores y enlaces son fundamentales en Azure Functions. Los desencadenadores inician la ejecución del código. Los enlaces, por otro lado, proporcionan una manera de pasar y devolver datos de una función, sin tener que escribir un código de acceso a datos personalizados.

Creación de funciones de Java

Para facilitar la creación de funciones de Java, hay herramientas y arquetipos basados en Maven que usan plantillas de Java predefinidas para ayudarle a crear proyectos con un desencadenador de funciones específico.

Herramientas basadas en Maven

Los siguientes entornos del desarrollador tienen herramientas de Azure Functions que le permiten crear proyectos de aplicación de una función de Java:

Los vínculos de artículo anteriores muestran cómo crear sus primeras funciones mediante el IDE que prefiera.

Creación del scaffolding del proyecto

Si prefiere el desarrollo de la línea de comandos del terminal, la forma más sencilla de crear el scaffolding de los proyectos de aplicación de una función basados en Java es usar arquetipos Apache Maven. El arquetipo Maven de Java para Azure Functions está publicado en groupId:artifactId: com.microsoft.azure:azure-functions-archetype.

El siguiente comando genera un nuevo proyecto de función de Java con este arquetipo:

mvn archetype:generate \
    -DarchetypeGroupId=com.microsoft.azure \
    -DarchetypeArtifactId=azure-functions-archetype

Para empezar a usar este arquetipo, consulte el inicio rápido de Java.

Estructura de carpetas

Esta es la estructura de carpetas de un proyecto de Java de Azure Functions:

FunctionsProject
 | - src
 | | - main
 | | | - java
 | | | | - FunctionApp
 | | | | | - MyFirstFunction.java
 | | | | | - MySecondFunction.java
 | - target
 | | - azure-functions
 | | | - FunctionApp
 | | | | - FunctionApp.jar
 | | | | - host.json
 | | | | - MyFirstFunction
 | | | | | - function.json
 | | | | - MySecondFunction
 | | | | | - function.json
 | | | | - bin
 | | | | - lib
 | - pom.xml

Puede usar un archivo host.json compartido para configurar la aplicación de funciones. Cada función tiene su propio archivo de código (.java) y archivo de configuración de enlace (function.json).

Puede colocar más de una función en un proyecto. Evite colocar las funciones en archivos JAR independientes. FunctionApp en el directorio de destino es lo que se implementa en la aplicación de funciones en Azure.

Desencadenadores y anotaciones

Las funciones se invocan mediante un desencadenador, como una solicitud HTTP, un temporizador o una actualización de datos. La función debe procesar ese desencadenador y las demás entradas para generar una o más salidas.

Utilice las anotaciones de Java incluidas en el paquete com.microsoft.azure.functions.annotation.* para enlazar las entradas y salidas a los métodos. Para más información, vea los documentos de referencia de Java.

Importante

Debe configurar una cuenta de Azure Storage en local.settings.json para ejecutar de manera local los desencadenadores de Azure Blob Storage, Azure Queue Storage o Azure Table Storage.

Ejemplo:

public class Function {
    public String echo(@HttpTrigger(name = "req", 
      methods = {HttpMethod.POST},  authLevel = AuthorizationLevel.ANONYMOUS) 
        String req, ExecutionContext context) {
        return String.format(req);
    }
}

Aquí está el elemento function.json correspondiente que generó azure-functions-maven-plugin:

{
  "scriptFile": "azure-functions-example.jar",
  "entryPoint": "com.example.Function.echo",
  "bindings": [
    {
      "type": "httpTrigger",
      "name": "req",
      "direction": "in",
      "authLevel": "anonymous",
      "methods": [ "GET","POST" ]
    },
    {
      "type": "http",
      "name": "$return",
      "direction": "out"
    }
  ]
}

Versiones de Java

La versión de Java que se usa al crear la aplicación de funciones en la que se ejecutan las funciones en Azure se especifica en el archivo pom.xml. En la actualidad, el arquetipo de Maven genera un archivo pom.xml para Java 8, que se puede cambiar antes de publicar. La versión de Java en el archivo pom.xml debe coincidir con la versión en la que ha desarrollado y probado localmente la aplicación.

Versiones compatibles

En la tabla siguiente se muestran las versiones actuales de Java compatibles para cada versión principal del runtime de Functions, por sistema operativo:

Versión de Functions Versiones de Java (Windows) Versiones de Java (Linux)
4.x 11
8
11
8
3.x 11
8
11
8
2.x 8 N/D

A menos que especifique una versión de Java para la implementación, el arquetipo de Maven usa como valor predeterminado Java 8 durante la implementación en Azure.

Especificación de la versión de implementación

Puede controlar la versión de Java de destino del arquetipo de Maven mediante el parámetro -DjavaVersion. El valor de este parámetro puede ser 8 o 11.

El arquetipo de Maven genera un archivo pom.xml que tiene como destino la versión de Java especificada. Los siguientes elementos del archivo pom.xml indican la versión de Java que se va a usar:

Elemento Valor de Java 8 Valor de Java 11 Descripción
Java.version 1.8 11 Versión de Java que usa el complemento maven-compiler-plugin.
JavaVersion 8 11 Versión de Java hospedada por la aplicación de funciones en Azure.

En los siguientes ejemplos se muestra la configuración de Java 8 en las secciones correspondientes del archivo pom.xml:

Java.version

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
    <azure.functions.maven.plugin.version>1.6.0</azure.functions.maven.plugin.version>
    <azure.functions.java.library.version>1.3.1</azure.functions.java.library.version>
    <functionAppName>fabrikam-functions-20200718015742191</functionAppName>
    <stagingDirectory>${project.build.directory}/azure-functions/${functionAppName}</stagingDirectory>
</properties>

JavaVersion

<runtime>
    <!-- runtime os, could be windows, linux or docker-->
    <os>windows</os>
    <javaVersion>8</javaVersion>
    <!-- for docker function, please set the following parameters -->
    <!-- <image>[hub-user/]repo-name[:tag]</image> -->
    <!-- <serverId></serverId> -->
    <!-- <registryUrl></registryUrl>  -->
</runtime>

Importante

Debe tener la variable de entorno JAVA_HOME establecida correctamente en el directorio JDK que se usa durante la compilación de código mediante Maven. Asegúrese de que la versión del JDK sea al menos tan alta como la configuración de Java.version.

Especificación del sistema operativo de implementación

Maven también le permite especificar el sistema operativo en el que se ejecuta la aplicación de funciones en Azure. Use el elemento os para elegir el sistema operativo.

Elemento Windows Linux Docker
os windows linux docker

En el ejemplo siguiente se muestra la configuración del sistema operativo en la sección runtime del archivo pom.xml:

<runtime>
    <!-- runtime os, could be windows, linux or docker-->
    <os>windows</os>
    <javaVersion>8</javaVersion>
    <!-- for docker function, please set the following parameters -->
    <!-- <image>[hub-user/]repo-name[:tag]</image> -->
    <!-- <serverId></serverId> -->
    <!-- <registryUrl></registryUrl>  -->
</runtime>

Disponibilidad y soporte técnico del entorno de ejecución de JDK

Para el desarrollo local de aplicaciones de funciones Java, descargue y utilice los JDK adecuados de Java de Azul Zulu Enterprise for Azure de Azul Systems. Azure Functions usa un runtime del JDK de Java de Azul al implementar las aplicaciones de funciones en la nube.

El soporte técnico de Azure para problemas con los JDK y las aplicaciones de funciones está disponible con un plan de soporte técnico cualificado.

Personalización de JVM

Functions permite personalizar la Máquina virtual Java (JVM) utilizada para ejecutar funciones Java. Las siguientes opciones de JVM se usan de forma predeterminada:

  • -XX:+TieredCompilation
  • -XX:TieredStopAtLevel=1
  • -noverify
  • -Djava.net.preferIPv4Stack=true
  • -jar

Puede proporcionar argumentos adicionales en una configuración de la aplicación denominada JAVA_OPTS. Puede agregar la configuración de la aplicación a la aplicación de funciones implementada en Azure en Azure Portal o la CLI de Azure.

Importante

En el plan de consumo, también debe agregar el valor WEBSITE_USE_PLACEHOLDER con un valor de 0 para que funcione la personalización. Esta configuración aumenta los tiempos de arranque en frío de las funciones Java.

Azure portal

En Azure Portal, utilice la pestaña Configuración de la aplicación para agregar la configuración JAVA_OPTS.

Azure CLI

Puede usar el comando az functionapp config appsettings set para establecer JAVA_OPTS, como en el siguiente ejemplo:

az functionapp config appsettings set \
    --settings "JAVA_OPTS=-Djava.awt.headless=true" \
    "WEBSITE_USE_PLACEHOLDER=0" \
    --name <APP_NAME> --resource-group <RESOURCE_GROUP>

En este ejemplo se habilita el modo "desatendido". Reemplace <APP_NAME> por el nombre de su aplicación de funciones y <RESOURCE_GROUP>, por el grupo de recursos.

Bibliotecas de terceros

Azure Functions admite el uso de bibliotecas de terceros. De forma predeterminada, todas las dependencias especificadas en el archivo pom.xml del proyecto se incluyen automáticamente durante el objetivo de mvn package. Para las bibliotecas no especificadas como dependencias en el archivo pom.xml, colóquelas en un directorio lib del directorio raíz de la función. Las dependencias colocadas en el directorio lib se agregan al cargador de clases del sistema en tiempo de ejecución.

La dependencia com.microsoft.azure.functions:azure-functions-java-library se proporciona en la ruta de acceso de la clase de forma predeterminada y no debe incluirse en el directorio lib. Asimismo, azure-functions-java-worker agrega a la ruta de clase las dependencias que figuran aquí.

Compatibilidad con tipos de datos

Puede usar objetos de Java POJO (Plain Old Java Object), tipos definidos en azure-functions-java-library o tipos de datos primitivos como String e Integer para enlazar con enlaces de entrada o salida.

POJO

Para convertir los datos de entrada a POJO, azure-functions-java-worker usa la biblioteca gson. Los tipos de POJO que se usan como entradas para las funciones deben ser public.

Datos binarios

Enlace entradas o salidas binarias a byte[]; para ello, establezca el campo dataType en function.json como binary:

   @FunctionName("BlobTrigger")
    @StorageAccount("AzureWebJobsStorage")
     public void blobTrigger(
        @BlobTrigger(name = "content", path = "myblob/{fileName}", dataType = "binary") byte[] content,
        @BindingName("fileName") String fileName,
        final ExecutionContext context
    ) {
        context.getLogger().info("Java Blob trigger function processed a blob.\n Name: " + fileName + "\n Size: " + content.length + " Bytes");
    }

Si espera valores null, use Optional<T>.

Enlaces

Los enlaces de entrada y de salida permiten conectarse de manera declarativa a los datos desde el código. Cada función puede tener varios enlaces de entrada y de salida.

Ejemplo de enlace de entrada

package com.example;

import com.microsoft.azure.functions.annotation.*;

public class Function {
    @FunctionName("echo")
    public static String echo(
        @HttpTrigger(name = "req", methods = { HttpMethod.PUT }, authLevel = AuthorizationLevel.ANONYMOUS, route = "items/{id}") String inputReq,
        @TableInput(name = "item", tableName = "items", partitionKey = "Example", rowKey = "{id}", connection = "AzureWebJobsStorage") TestInputData inputData,
        @TableOutput(name = "myOutputTable", tableName = "Person", connection = "AzureWebJobsStorage") OutputBinding<Person> testOutputData
    ) {
        testOutputData.setValue(new Person(httpbody + "Partition", httpbody + "Row", httpbody + "Name"));
        return "Hello, " + inputReq + " and " + inputData.getKey() + ".";
    }

    public static class TestInputData {
        public String getKey() { return this.RowKey; }
        private String RowKey;
    }
    public static class Person {
        public String PartitionKey;
        public String RowKey;
        public String Name;

        public Person(String p, String r, String n) {
            this.PartitionKey = p;
            this.RowKey = r;
            this.Name = n;
        }
    }
}

Esta función se invoca con una solicitud HTTP.

  • La carga útil de la solicitud HTTP se pasa como String para el argumento inputReq.
  • Una entrada se recupera de Table Storage y se pasa como TestInputData al argumento inputData.

Para recibir un lote de entradas, puede enlazarse a String[], POJO[], List<String> o List<POJO>.

@FunctionName("ProcessIotMessages")
    public void processIotMessages(
        @EventHubTrigger(name = "message", eventHubName = "%AzureWebJobsEventHubPath%", connection = "AzureWebJobsEventHubSender", cardinality = Cardinality.MANY) List<TestEventData> messages,
        final ExecutionContext context)
    {
        context.getLogger().info("Java Event Hub trigger received messages. Batch size: " + messages.size());
    }
    
    public class TestEventData {
    public String id;
}

Esta función se desencadena cuando hay nuevos datos en el centro de eventos configurado. Como cardinality se establece en MANY, la función recibe un lote de mensajes desde el centro de eventos. El elemento EventData del centro de eventos se convierte a TestEventData para la ejecución de la función.

Ejemplo de enlace de salida

Puede enlazar un enlace de salida al valor de retorno con $return.

package com.example;

import com.microsoft.azure.functions.annotation.*;

public class Function {
    @FunctionName("copy")
    @StorageAccount("AzureWebJobsStorage")
    @BlobOutput(name = "$return", path = "samples-output-java/{name}")
    public static String copy(@BlobTrigger(name = "blob", path = "samples-input-java/{name}") String content) {
        return content;
    }
}

Si hay varios enlaces de salida, use el valor devuelto para solo uno de ellos.

Para enviar varios valores de salida, use el tipo OutputBinding<T> que se define en el paquete azure-functions-java-library.

@FunctionName("QueueOutputPOJOList")
    public HttpResponseMessage QueueOutputPOJOList(@HttpTrigger(name = "req", methods = { HttpMethod.GET,
            HttpMethod.POST }, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
            @QueueOutput(name = "itemsOut", queueName = "test-output-java-pojo", connection = "AzureWebJobsStorage") OutputBinding<List<TestData>> itemsOut, 
            final ExecutionContext context) {
        context.getLogger().info("Java HTTP trigger processed a request.");
       
        String query = request.getQueryParameters().get("queueMessageId");
        String queueMessageId = request.getBody().orElse(query);
        itemsOut.setValue(new ArrayList<TestData>());
        if (queueMessageId != null) {
            TestData testData1 = new TestData();
            testData1.id = "msg1"+queueMessageId;
            TestData testData2 = new TestData();
            testData2.id = "msg2"+queueMessageId;

            itemsOut.getValue().add(testData1);
            itemsOut.getValue().add(testData2);

            return request.createResponseBuilder(HttpStatus.OK).body("Hello, " + queueMessageId).build();
        } else {
            return request.createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body("Did not find expected items in CosmosDB input list").build();
        }
    }

     public static class TestData {
        public String id;
    }

Esta función se invoca en una solicitud HttpRequest. Escribe varios valores en Queue Storage.

HttpRequestMessage y HttpResponseMessage

Se definen en azure-functions-java-library. Son tipos de aplicación auxiliar que funcionan con funciones HttpTrigger.

Tipo especializado Destino Uso típico
HttpRequestMessage<T> Desencadenador HTTP Obtiene el método, encabezados o consultas.
HttpResponseMessage Enlace de salida HTTP Devuelve un estado distinto de 200.

Metadatos

Algunos desencadenadores envían metadatos de desencadenador junto con datos de entrada. Puede usar la anotación @BindingName para enlazarla a metadatos de desencadenador.

package com.example;

import java.util.Optional;
import com.microsoft.azure.functions.annotation.*;


public class Function {
    @FunctionName("metadata")
    public static String metadata(
        @HttpTrigger(name = "req", methods = { HttpMethod.GET, HttpMethod.POST }, authLevel = AuthorizationLevel.ANONYMOUS) Optional<String> body,
        @BindingName("name") String queryValue
    ) {
        return body.orElse(queryValue);
    }
}

En el ejemplo anterior, el elemento queryValue está enlazado al parámetro de cadena de consulta name de la dirección URL de solicitud HTTP http://{example.host}/api/metadata?name=test. Este es otro ejemplo que muestra cómo enlazar a Id desde los metadatos de desencadenador de la cola.

 @FunctionName("QueueTriggerMetadata")
    public void QueueTriggerMetadata(
        @QueueTrigger(name = "message", queueName = "test-input-java-metadata", connection = "AzureWebJobsStorage") String message,@BindingName("Id") String metadataId,
        @QueueOutput(name = "output", queueName = "test-output-java-metadata", connection = "AzureWebJobsStorage") OutputBinding<TestData> output,
        final ExecutionContext context
    ) {
        context.getLogger().info("Java Queue trigger function processed a message: " + message + " with metadaId:" + metadataId );
        TestData testData = new TestData();
        testData.id = metadataId;
        output.setValue(testData);
    }

Nota

El nombre que se proporciona en la anotación debe coincidir con la propiedad de metadatos.

Contexto de ejecución

El elemento ExecutionContext definido en azure-functions-java-library contiene métodos de ayuda que le permitirán comunicarse con las funciones en tiempo de ejecución. Para obtener más información, consulte el artículo de referencia sobre ExecutionContext.

Registrador

Use el elemento getLogger definido en ExecutionContext para escribir registros desde el código de función.

Ejemplo:


import com.microsoft.azure.functions.*;
import com.microsoft.azure.functions.annotation.*;

public class Function {
    public String echo(@HttpTrigger(name = "req", methods = {HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) String req, ExecutionContext context) {
        if (req.isEmpty()) {
            context.getLogger().warning("Empty request body received by function " + context.getFunctionName() + " with invocation " + context.getInvocationId());
        }
        return String.format(req);
    }
}

Ver registros y seguimiento

Puede usar la CLI de Azure para hacer streaming del registro stdout y stderr de Java, así como de otros registros de la aplicación.

Aquí se explica cómo configurar la aplicación de funciones para escribir el registro de aplicación con la CLI de Azure:

az webapp log config --name functionname --resource-group myResourceGroup --application-logging true

Para transmitir en secuencias la salida para la aplicación de funciones con la CLI de Azure, abra una nueva sesión del símbolo del sistema, Bash o Terminal y escriba el siguiente comando:

az webapp log tail --name webappname --resource-group myResourceGroup

El comando az webapp log tail tiene opciones para filtrar la salida usando la opción --provider.

Para descargar los archivos de registro como un solo archivo ZIP mediante la CLI de Azure, abra una sesión nueva del símbolo del sistema, Bash o Terminal y escriba el siguiente comando:

az webapp log download --resource-group resourcegroupname --name functionappname

Debe haber habilitado el registro del sistema de archivos en Azure Portal o la CLI de Azure antes de ejecutar este comando.

Variables de entorno

En Functions, la configuración de la aplicación, como las cadenas de conexión del servicio, se exponen como variables de entorno durante la ejecución. Puede obtener acceso a estas opciones mediante System.getenv("AzureWebJobsStorage").

En el siguiente ejemplo se obtiene la configuración de la aplicación, con la clave denominada myAppSetting:


public class Function {
    public String echo(@HttpTrigger(name = "req", methods = {HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) String req, ExecutionContext context) {
        context.getLogger().info("My app setting value: "+ System.getenv("myAppSetting"));
        return String.format(req);
    }
}

Nota

El valor de AppSetting FUNCTIONS_EXTENSION_VERSION debería ser ~2 o ~3 para una experiencia de arranque en frío optimizada.

Pasos siguientes

Para más información sobre el desarrollo de Java de Azure Functions, vea los siguientes recursos: