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 de Java, si no conoce bien Azure Functions, considere la posibilidad de leer primero uno de los artículos siguientes:

Introducción Conceptos Escenarios y ejemplos

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. 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:

En estos artículos se muestra 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 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 17
11
8
21 (versión preliminar)
17
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, 11, 17 o 21.

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 Valor de Java 17 Valor de Java 21 (versión preliminar, Linux) Descripción
Java.version 1.8 11 17 21 Versión de Java que usa el complemento maven-compiler-plugin.
JavaVersion 8 11 17 21 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

Microsoft y las compilaciones Adoptium de OpenJDK se proporcionan y admiten en Functions para Java 8 (Adoptium), Java 11, 17 y 21(MSFT). Estos archivos binarios se proporcionan como una distribución sin costo, multiplataforma y lista para producción de OpenJDK para Azure. Contienen todos los componentes para la creación y ejecución de aplicaciones de Java SE.

Para desarrollo o pruebas locales, puede descargar la compilación de Microsoft de OpenJDK o los archivos binarios de Adoptium Teium de forma gratuita. 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.

Si quiere seguir usando los archivos binarios de Zulu para Azure en la aplicación de funciones, configure la aplicación en consecuencia. Puede seguir usando los archivos binarios de Azul para su sitio. Pero las mejoras o actualizaciones de seguridad solo están disponibles en las versiones nuevas de OpenJDK. Por este motivo, debería acabar quitando configuración para que las aplicaciones usen la versión más reciente disponible de Java.

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 otros argumentos a la JVM mediante una de las configuraciones de la aplicación siguientes, en función del tipo de plan:

Tipo de plan Nombre del valor Comentario
Plan de consumo languageWorkers__java__arguments Esta configuración aumenta los tiempos de inicio en frío para las funciones de Java que se ejecutan en un plan de Consumo.
Plan Premium
Plan dedicado
JAVA_OPTS

En las secciones siguientes se muestra cómo agregar esta configuración. Para más información sobre cómo usar la configuración de la aplicación, vea la sección Uso de la configuración de la aplicación.

Portal de Azure

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

Azure CLI

Puede usar el comando az functionapp config appsettings set a fin de agregar esta configuración, tal como se muestra en el ejemplo siguiente para la opción -Djava.awt.headless=true:

az functionapp config appsettings set \
    --settings "languageWorkers__java__arguments=-Djava.awt.headless=true" \
    --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;
    }

Invoque esta función en un objeto HttpRequest. Escribe varios valores en Queue Storage.

HttpRequestMessage y HttpResponseMessage

Se definen en azure-functions-java-library. Son tipos de asistente 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 aplicaciones.

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

Uso de la inserción de dependencias en Java Functions

Azure Functions Java admite el modelo de diseño de software de inserción de dependencias (DI), que es una técnica para lograr la inversión de control (IoC) entre las clases y sus dependencias. Java Azure Functions proporciona un enlace para integrarse con marcos populares de inserción de dependencias en las aplicaciones de Functions. SPI de Azure Functions Java contiene una interfaz FunctionInstanceInjector. Al implementar esta interfaz, puede devolver una instancia de la clase de función y las funciones se invocarán en esta instancia. Esto proporciona a marcos como Spring, Quarkus, Google Guice, Dagger, etc. la capacidad de crear la instancia de función y registrarla en su contenedor de IOC. Esto significa que puede usar esos marcos de inserción de dependencias para administrar las funciones de forma natural.

Nota

Tipos de SPI de Microsoft Azure Functions Java (azure-function-java-spi) es un paquete que contiene todas las interfaces SPI para que terceros puedan interactuar con el tiempo de ejecución de las funciones de Microsoft Azure.

Inserción de instancias de funciones para la inserción de dependencias

azure-function-java-spi contiene una interfaz FunctionInstanceInjector

package com.microsoft.azure.functions.spi.inject; 

/** 

 * The instance factory used by DI framework to initialize function instance. 

 * 

 * @since 1.0.0 

 */ 

public interface FunctionInstanceInjector { 

    /** 

     * This method is used by DI framework to initialize the function instance. This method takes in the customer class and returns 

     * an instance create by the DI framework, later customer functions will be invoked on this instance. 

     * @param functionClass the class that contains customer functions 

     * @param <T> customer functions class type 

     * @return the instance that will be invoked on by azure functions java worker 

     * @throws Exception any exception that is thrown by the DI framework during instance creation 

     */ 

    <T> T getInstance(Class<T> functionClass) throws Exception; 

} 

Para obtener más ejemplos que usan FunctionInstanceInjector para integrarse con marcos de inserción de dependencias, consulte este repositorio.

Pasos siguientes

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