Neste artigo veremos como realizar transferência de arquivos pela rede usando a classe Socket em Java. Aprenderemos também um pouco sobre propriedades transientes e serialização de objeto para transferência.

Nosso intuito neste artigo é demonstrar uma solução para determinado problema, que é exatamente a transferência de arquivos entre dois hosts, então serão o mais práticos possíveis deixando um pouco a teoria de lado.

A ideia do projeto

Antes de começarmos a desenvolver precisamos entender o que iremos fazer. A ideia aqui é ter dois hosts: um servidor e um cliente.

  • O Servidor será iniciado e ficará aguardando o envio de algum arquivo vindo do cliente, quando este arquivo chegar ele irá salvá-lo no diretório que o cliente solicitou. A nossa classe Servidora não terá nenhuma interface gráfica, apenas será uma ferramenta de “escuta de envio de arquivos”.
  • O Cliente, por outro lado, contará com uma interface gráfica simples onde o mesmo poderá escolher o arquivo que deseja enviar, o diretório de destino e as informações do servidor de destino (IP e Porta), dessa forma o cliente poderá enviar o arquivo para qualquer servidor que esteja executando a classe de escuta de arquivos.

A classe Arquivo.java

Criaremos uma classe Arquivo, como mostra a Listagem 1, que irá “carregar” o arquivo que foi escolhido no cliente e mais algumas informações importantes, como por exemplo: tamanho do arquivo, nome do arquivo e etc. Entenda que o arquivo é um aglomerado de bytes, independente do seu tipo (pdf, vídeo, texto, música, etc.), sendo assim para garantir a integridade do arquivo nós gravaremos seu conteúdo como byte[].

package br.com.transientfield.bean;

import java.io.Serializable;
import java.util.Date;

public class Arquivo implements Serializable {

   /**
    *
    */
   private static final long serialVersionUID = 1L;
             
   private String nome;
   private byte[] conteudo;
   private transient long tamanhoKB;
   private transient Date dataHoraUpload;
   private transient String ipDestino;
   private transient String portaDestino;
   private String diretorioDestino;
   public String getNome() {
             return nome;
   }
   public void setNome(String nome) {
             this.nome = nome;
   }
   public byte[] getConteudo() {
             return conteudo;
   }
   public void setConteudo(byte[] conteudo) {
             this.conteudo = conteudo;
   }
   public long getTamanhoKB() {
             return tamanhoKB;
   }
   public void setTamanhoKB(long tamanhoKB) {
             this.tamanhoKB = tamanhoKB;
   }
   public Date getDataHoraUpload() {
             return dataHoraUpload;
   }
   public void setDataHoraUpload(Date dataHoraUpload) {
             this.dataHoraUpload = dataHoraUpload;
   }
   public String getIpDestino() {
             return ipDestino;
   }
   public void setIpDestino(String ipDestino) {
             this.ipDestino = ipDestino;
   }
   public String getPortaDestino() {
             return portaDestino;
   }
   public void setPortaDestino(String portaDestino) {
             this.portaDestino = portaDestino;
   }
   public String getDiretorioDestino() {
             return diretorioDestino;
   }
   public void setDiretorioDestino(String diretorioDestino) {
             this.diretorioDestino = diretorioDestino;
   }
}
Listagem 1. Arquivo.java

Você pode perguntar-se, porque optamos por byte[] ? Com o byte[] nós podemos gravar também o conteúdo do arquivo facilmente no banco de dados, e retorná-lo depois sem perder as informações. Lembre-se: Nós não sabemos que tipo de arquivo será enviado.

Vamos ver alguns pontos importantes sobre a classe da Listagem 1:

  1. Ela é Serializable: É necessário que nossa classe implemente a interface Serializable para que possamos converter toda ela em um aglomerado de bytes (byte[]) e transferi-la via Socket.
  2. Os campos 'transient': Alguns dos nossos campos estão identificados com a palavra reservada 'transient' como, por exemplo, o campo “ipDestino”. Um campo transiente não é serializado, consequentemente aquele aglomerado de bytes definido no item acima não irá conter o campo “ipDestino”, perceba que este é útil apenas no lado do cliente, pois é o cliente que precisa saber para quem ele irá enviar o arquivo, não faz sentido o servidor possuir o campo “ipDestino” na classe Arquivo, enviar este campo seria desperdício de banda e processamento.

A classe Arquivo.java no servidor

Não precisamos de alguns campos no servidor, por isso eles foram anotados como transientes para evitar desperdício de banda e processamento. Então no lado do servidor nós iremos criar a mesma classe Arquivo.java, como mostra a Listagem 2, no mesmo pacote “br.com.transientfield.bean”, porém sem os campos transientes já que eles nunca serão requisitados.

package br.com.transientfield.bean;
import java.io.Serializable;
import java.util.Date;


public class Arquivo implements Serializable {

   /**
    *
    */
   private static final long serialVersionUID = 1L;
   
   private String nome;
   private byte[] conteudo;
   private String diretorioDestino;
   private Date dataHoraUpload;        
   
   public Date getDataHoraUpload() {
             return dataHoraUpload;
   }
   public void setDataHoraUpload(Date dataHoraUpload) {
             this.dataHoraUpload = dataHoraUpload;
   }
   public String getNome() {
             return nome;
   }
   public void setNome(String nome) {
             this.nome = nome;
   }
   public byte[] getConteudo() {
             return conteudo;
   }
   public void setConteudo(byte[] conteudo) {
             this.conteudo = conteudo;
   }
   public String getDiretorioDestino() {
             return diretorioDestino;
   }
   public void setDiretorioDestino(String diretorioDestino) {
             this.diretorioDestino = diretorioDestino;
   }
}
Listagem 2. Classe Arquivo.java no servidor

Não iremos reexplicar esta classe, pois segue o mesmo princípio da classe Arquivo no cliente, porém sem alguns atributos que não são necessários.

Implementando o Servidor

Em linhas gerais a nossa classe servidor fica aguardando o envio de um arquivo pelo cliente, quando este arquivo é recebido então ele é desserilizado e salvo no diretório desejado. Dividimos a Listagem 3 em blocos de comentários para que a explicação fique de mais fácil entendimento.

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.ServerSocket;
import java.net.Socket;

import br.com.transientfield.bean.Arquivo;

public class Server {

   public static void main(String args[]) {
     try {
        //1
        ServerSocket srvSocket = new ServerSocket(5566);
        System.out.println("Aguardando envio de arquivo ...");
        Socket socket = srvSocket.accept();
        
        //2
        byte[] objectAsByte = new byte[socket.getReceiveBufferSize()];
        BufferedInputStream bf = new BufferedInputStream(
           socket.getInputStream());
        bf.read(objectAsByte);
        
        //3
        Arquivo arquivo = (Arquivo) getObjectFromByte(objectAsByte);
        
        //4
        String dir = arquivo.getDiretorioDestino().endsWith("/") ? arquivo
           .getDiretorioDestino() + arquivo.getNome() : arquivo
           .getDiretorioDestino() + "/" + arquivo.getNome();
        System.out.println("Escrevendo arquivo " + dir);

        //5
        FileOutputStream fos = new FileOutputStream(dir);
        fos.write(arquivo.getConteudo());
        fos.close();

     } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
     }

   }

   private static Object getObjectFromByte(byte[] objectAsByte) {
     Object obj = null;
     ByteArrayInputStream bis = null;
     ObjectInputStream ois = null;
     try {
        bis = new ByteArrayInputStream(objectAsByte);
        ois = new ObjectInputStream(bis);
        obj = ois.readObject();

        bis.close();
        ois.close();

     } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
     } catch (ClassNotFoundException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();           
     }                 
     
     return obj;
             
   }

}
Listagem 3. Classe Server.java

Vamos as explicações divididas por bloco de comentários:

  1. Logo no início nós criamos inicializamos o ServerSocket usando a porta 5566 e aguardando a conexão do cliente. Ao executar o método accept() o servidor ficará aguardando que alguém conecte-se a ele, até que tal conexão seja realizada ele ficará em “sleep” neste ponto.
  2. Neste ponto uma conexão já foi realizada e usamos o método getReceiveBufferSize() para saber o tamanho do objeto que foi enviado, em bytes. O BufferedInputStream irá realizar a tarefa de capturar o que foi lido do inputStream do socket e gravar na variável “objectAsByte” através do método read(), ou seja, o que está no canal de comunicação é gravado no objectAsByte.
  3. Já temos em mãos o que foi transferido, porém estes dados estão em bytes e não serão muito úteis se continuarem nesta forma. Usando um método para desserialização de dados, o getObjectFromByte(), nós transformamos os bytes em um objeto Arquivo. Obviamente que nós sabemos que o que será transferido é sempre um objeto Arquivo, pois se outro tipo de objeto fosse transferido iríamos ter um erro de cast.
  4. Com o objeto Arquivo podemos começar a manipular os dados para salvar o arquivo no diretório desejado. Formatamos este diretório certificando-se de colocar todas as informações necessárias.
  5. Por fim usamos o FileOutputStream passando o nome do diretório onde queremos gravar nosso arquivo. Através do método write() passamos um aglomerado de bytes que está no atributo conteudo do bean Arquivo, assim o FileOutputStream saberá o que gravar e onde gravar.

Você já pode iniciar o cliente e deixar o mesmo monitorando o envio de arquivos, vamos agora construir nosso cliente.

Implementando o Cliente

Primeiro vamos ver a interface que idealizamos em nosso projeto, como mostra a Figura 1.

Interface Cliente
Figura 1. Interface Cliente

O formulário da Figura 1 possui os seguintes campos:

  1. Arquivo Carregado: Mostra o nome do arquivo após selecionado no botão “Selecionar Arquivo”.
  2. IP: Número do IP do Servidor. Ex: 192.168.2.1.
  3. Porta: Número da Porta do Servidor. Ex: 5566 (porta usada no nosso exemplo).
  4. Diretório Dest.: Diretório onde o arquivo será salvo no servidor. Independente se for Linux ou Windows.
  5. Selecionar Arquivo: Abre a janela para seleção do arquivo que será enviado e depois preenche o label “tamanho” com o tamanho do arquivo em KB.
  6. Enviar: Inicia o processo de envio do arquivo do Cliente para o Servidor. Neste ponto o servidor é notificado que uma conexão está sendo estabelecida.

Por fim, temos a classe ClienteSocket.java, como mostra a Listagem 4, que representa o formulário demonstrado na Figura 1. Mostraremos a mesma dividida em blocos para facilitar a compreensão.

package br.com.transientfield.gui;

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Date;

import javax.swing.JFileChooser;
import javax.swing.JOptionPane;

import br.com.transientfield.bean.Arquivo;

public class ClienteSocket extends javax.swing.JFrame {
 
 private long tamanhoPermitidoKB = 5120; //Igual a 5MB
 private Arquivo arquivo;

 
 private static final long serialVersionUID = 1L;

 public ClienteSocket() {
           initComponents();
 }

 
private void initComponents() {
jLabel1 = new javax.swing.JLabel();
jTextFieldNome = new javax.swing.JTextField();
jButtonArquivo = new javax.swing.JButton();
jLabelTamanho = new javax.swing.JLabel();
jButtonEnviar = new javax.swing.JButton();
jLabel2 = new javax.swing.JLabel();
jTextFieldIP = new javax.swing.JTextField();
jLabel3 = new javax.swing.JLabel();
jTextFieldDiretorio = new javax.swing.JTextField();
jLabel4 = new javax.swing.JLabel();
jTextFieldPorta = new javax.swing.JTextField();

setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
jLabel1.setText("Arquivo Carregado");

jTextFieldNome.setEnabled(false);

jButtonArquivo.setText("Selecionar Arquivo");
jButtonArquivo.addActionListener(new java.awt.event.ActionListener() {
    public void actionPerformed(java.awt.event.ActionEvent evt) {
        jButtonArquivoActionPerformed(evt);
    }
});

jLabelTamanho.setFont(new java.awt.Font("Dialog", 0, 12));
jLabelTamanho.setText("Tamanho:");

jButtonEnviar.setText("Enviar");
jButtonEnviar.addActionListener(new java.awt.event.ActionListener() {
    public void actionPerformed(java.awt.event.ActionEvent evt) {
        jButtonEnviarActionPerformed(evt);
    }
});

jLabel2.setText("IP");

jLabel3.setText("Diret\u00f3rio Dest.");

jLabel4.setText("Porta");

javax.swing.GroupLayout layout = new javax.swing.GroupLayout
(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
    layout.createParallelGroup(javax.swing.GroupLayout
    .Alignment.LEADING)
    .addGroup(layout.createSequentialGroup()
        .addContainerGap()
        .addGroup(layout.createParallelGroup(javax.swing.
        GroupLayout.Alignment.LEADING)
            .addComponent(jTextFieldNome, javax.swing.
            GroupLayout.DEFAULT_SIZE, 279, Short.MAX_VALUE)
            .addComponent(jLabel1)
            .addComponent(jButtonEnviar)
            .addComponent(jButtonArquivo)
            .addComponent(jLabelTamanho)
            .addGroup(layout.createSequentialGroup()
                .addComponent(jLabel2)
                .addPreferredGap(javax.swing.LayoutStyle.
                ComponentPlacement.RELATED, 104, Short.MAX_VALUE)
                .addComponent(jTextFieldIP, javax.swing.
                GroupLayout.PREFERRED_SIZE, 162, javax.swing.
                GroupLayout.PREFERRED_SIZE))
            .addGroup(layout.createSequentialGroup()
                .addGroup(layout.createParallelGroup(javax.swing.
                GroupLayout.Alignment.TRAILING, false)
                    .addGroup(javax.swing.GroupLayout.Alignment.
                    LEADING, layout.createSequentialGroup()
                        .addComponent(jLabel4)
                        .addPreferredGap(javax.swing.LayoutStyle.
                        ComponentPlacement.RELATED, javax.swing.
                        GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .addComponent(jTextFieldPorta, javax.swing.
                        GroupLayout.PREFERRED_SIZE, 162, javax.swing.
                        GroupLayout.PREFERRED_SIZE))
                    .addGroup(javax.swing.GroupLayout.Alignment.
                    EADING, layout.createSequentialGroup()
                        .addComponent(jLabel3)
                        .addPreferredGap(javax.swing.LayoutStyle.
                        ComponentPlacement.RELATED)
                        .addComponent(jTextFieldDiretorio, javax.
                        swing.GroupLayout.PREFERRED_SIZE, 160, 
                        javax.swing.GroupLayout.PREFERRED_SIZE)))
                .addPreferredGap(javax.swing.LayoutStyle.
                ComponentPlacement.RELATED, 2, javax.swing.
                GroupLayout.PREFERRED_SIZE)))
        .addGap(37, 37, 37))
);
layout.setVerticalGroup(
    layout.createParallelGroup(javax.swing.GroupLayout.
    Alignment.LEADING)
    .addGroup(layout.createSequentialGroup()
        .addContainerGap()
        .addComponent(jLabel1)
        .addPreferredGap(javax.swing.LayoutStyle.
        ComponentPlacement.RELATED)
        .addComponent(jTextFieldNome, javax.swing.GroupLayout.
        PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, 
        javax.swing.GroupLayout.PREFERRED_SIZE)
        .addPreferredGap(javax.swing.LayoutStyle.
        ComponentPlacement.RELATED)
        .addGroup(layout.createParallelGroup(javax.swing.
        GroupLayout.Alignment.BASELINE)
            .addComponent(jLabel2)
            .addComponent(jTextFieldIP, javax.swing.GroupLayout.
            PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, 
            javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.
        RELATED)
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.
        Alignment.BASELINE)
            .addComponent(jLabel4)
            .addComponent(jTextFieldPorta, javax.swing.GroupLayout.
            PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, 
            javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.
        RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.
        Alignment.BASELINE)
            .addComponent(jLabel3)
            .addComponent(jTextFieldDiretorio, javax.swing.GroupLayout.
            PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, 
            javax.swing.GroupLayout.PREFERRED_SIZE))
        .addGap(16, 16, 16)
        .addComponent(jButtonArquivo)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(jLabelTamanho)
        .addGap(25, 25, 25)
        .addComponent(jButtonEnviar)
        .addGap(139, 139, 139))
);
pack();
}// </editor-fold>//GEN-END:initComponents
Listagem 4. Formulário ClienteSocket.java parte 1

Na Listagem 4 temos:

  1. Os imports necessários para funcionamento do nosso ClienteSocket;
  2. Dois atributos: tamanhoPermitidoKB para armazenar o máximo em KB que um arquivo poderá ter para ser enviado, e 'arquivo' que irá guardar a instância do objeto Arquivo que foi configurado durante a seleção do arquivo;
  3. O construtor da classe ClienteSocket() chama um outro método: initComponents().
  4. O initComponents() é responsável por configurar todos os componentes do nosso formulários: botões, labels, caixas de texto e etc.
private void jButtonEnviarActionPerformed(java.awt.event.ActionEvent evt) {
   enviarArquivoServidor();
}      

 private void jButtonArquivoActionPerformed(java.awt.event.ActionEvent evt) {
   FileInputStream fis;
   try {
            
      JFileChooser chooser = new JFileChooser();
      chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
      chooser.setDialogTitle("Escolha o arquivo");
      
      if (chooser.showOpenDialog(this) == JFileChooser.OPEN_DIALOG) {
          File fileSelected = chooser.getSelectedFile();
          
          byte[] bFile = new byte[(int) fileSelected.length()];
          fis = new FileInputStream(fileSelected);
          fis.read(bFile);
          fis.close();
          
          long kbSize = fileSelected.length() / 1024;
          jTextFieldNome.setText(fileSelected.getName());
          jLabelTamanho.setText(kbSize + " KB");
          
          arquivo = new Arquivo();
          arquivo.setConteudo(bFile);
          arquivo.setDataHoraUpload(new Date());
          arquivo.setNome(fileSelected.getName());
          arquivo.setTamanhoKB(kbSize);
          arquivo.setIpDestino(jTextFieldIP.getText());
          arquivo.setPortaDestino(jTextFieldPorta.getText());
         arquivo.setDiretorioDestino(jTextFieldDiretorio.getText().trim());
      }
            
   } catch (Exception e) {
            e.printStackTrace();
   }
 }
Listagem 5. Formulário ClienteSocket parte 2

Na Listagem 5 temos o uso de dois métodos que complementam a Listagem 4, ambos são disparados no clique do botão, vejamos:

  1. O método jButtonEnviarActionPerformed() é disparado no clique do botão 'Enviar' e a sua ação é chamar o método enviarArquivoServidor() que será mostrado mais a frente.
  2. O método jButtonArquivoActionPerformed() é disparado no clique do botão 'Selecionar Arquivo' e o mesmo possui mais detalhes:
    • Usamos a classe JFileChooser para permitir ao usuário a escolha do arquivo que será enviado.
    • Criamos uma variável byte[] bFile através do tamanho do arquivo já selecionado, esta variável irá armazenar o conteúdo do arquivo. Conseguimos extrair o seu conteúdo através do método read() da classe FileInputStream que recebe o objeto File selecionado.
    • Calculamos o tamanho do arquivo em KB e configuramos o nome do arquivo no jTextFieldNome.
      2.4 – Instanciamos a variável arquivo setando todos os seus atributos.
private void enviarArquivoServidor(){
   if (validaArquivo()){
    try {
        Socket socket = new Socket(jTextFieldIP.getText().trim(),
          Integer.parseInt(jTextFieldPorta.getText().trim()));

        BufferedOutputStream bf = new BufferedOutputStream
        (socket.getOutputStream());

        byte[] bytea = serializarArquivo();
        bf.write(bytea);
        bf.flush();
        bf.close();
        socket.close();
    } catch (UnknownHostException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
   }
}

private byte[] serializarArquivo(){
   try {
      ByteArrayOutputStream bao = new ByteArrayOutputStream();
      ObjectOutputStream ous;
      ous = new ObjectOutputStream(bao);
      ous.writeObject(arquivo);
      return bao.toByteArray();
   } catch (IOException e) {
      e.printStackTrace();
   }

   return null;
}

private boolean validaArquivo(){
   if (arquivo.getTamanhoKB() > tamanhoPermitidoKB){
      JOptionPane.showMessageDialog(this,
       "Tamanho máximo permitido atingido ("+(tamanhoPermitidoKB/1024)+")");
      return false;
   }else{
      return true;
   }
}


public static void main(String args[]) {
    java.awt.EventQueue.invokeLater(new Runnable() {
      public void run() {
        new ClienteSocket().setVisible(true);
      }
    });
}

  private javax.swing.JButton jButtonArquivo;
  private javax.swing.JButton jButtonEnviar;
  private javax.swing.JLabel jLabel1;
  private javax.swing.JLabel jLabel2;
  private javax.swing.JLabel jLabel3;
  private javax.swing.JLabel jLabel4;
  private javax.swing.JLabel jLabelTamanho;
  private javax.swing.JTextField jTextFieldDiretorio;
  private javax.swing.JTextField jTextFieldIP;
  private javax.swing.JTextField jTextFieldNome;
  private javax.swing.JTextField jTextFieldPorta;
}
Listagem 6. Formulário ClienteSocket parte 3

Na Listagem 6 temos alguns pontos importantes a serem notados:

  1. O método enviarArquivoServidor():
    • Faz a validação do arquivo, checando se o mesmo possui o tamanho máximo permitido para envio;
    • Realiza a conexão com o servidor através do IP e Porta digitados;
    • Prepara o objeto BufferedOutputStream para escrita dos dados e envio ao servidor;
    • Serializa o Arquivo que foi instanciado no passo 4, transformando o mesmo em um aglomerado de bytes, ou seja, byte[]. Com o método write() nós escrevemos os bytes para serem transferidos ao servidor e com o flush() nós forçamos essa transferência imediatamente.
  2. O método serializarArquivo() usa o ByteArrayOutputStream em conjunto com o ObjectOutputStream para escrever o objeto e converter para byte[].
  3. O método validarArquivo() apenas checa se o tamanho do arquivo é menor ou igual ao máximo permitido.

Para você leitor há ainda alguns desafios a serem enfrentados que deixamos para que você estude-os e resolva-os. Você perceberá que só conseguirá enviar arquivos com até 8129 bytes, e que sempre no envio de um arquivo ao término do processo o servidor será encerrado.

Então o seu desafio é fazer com que arquivos maiores possam ser enviados e que o servidor possibilite o envio de mais de um arquivo sem ser encerrado.

Focamos neste artigo a implementação de um projeto de transferência de arquivos via Socket, dando maior ênfase à parte prática. Os desafios irão lhe ajudar a assimilar melhor o conteúdo indo atrás de soluções para os problemas encontrados.


Links Úteis

  • Curso de Yarn - Gerenciando de dependências com Yarn: Neste curso aprenderemos o que é o Yarn: um gerenciador de dependências concorrente do NPM que promete ser mais rápido e eficiente que este.
  • Web services RESTful com Spring framework e JPA: Neste curso você vai aprender a criar sua primeira API REST baseada nos recursos do Spring Framework. Veremos como declarar corretamente os verbos HTTP em cada recurso consumido e também como definir, de forma apropriada, o status de cada resposta fornecida pela API.
  • Gestão de projetos com MS Project: Este artigo apresenta uma das ferramentas mais usadas pelo mercado, o MS Project, demonstrando como preparar a ferramenta para seu uso adequado, configurar calendários e principalmente compartilhar essas configurações entre projetos.

Saiba mais sobre Java ;)

  • Curso de Java EE: Construa uma aplicação completa Java EE: Neste curso de Java usaremos a plataforma WEB. Você irá encontrar o mais rico material sobre construções de aplicação utilizando as tecnologias e recursos do Java Enterprise Edition 7.
  • Java Enterprise Edition - Java EE: Neste Guia de Referência você encontrará todo o conteúdo que precisa para conhecer o Java EE, Java Enterprise Edition, a plataforma Java voltada para o desenvolvimento de aplicações web/corporativas.
  • Curso de Android: Neste curso iremos aprender a fazer um aplicativo de signos no Android Studio. Criamos um exemplo de uma calculadora de signos utilizando orientação a objetos na linguagem Java contida na IDE e arquivos XML que representam a parte visual de nosso aplicativo.