The Avengers – O filme mais incrível de todos os tempos
Posted: April 27, 2012 Filed under: quadrinhos | Tags: avengers, cinema, quadrinhos, review Leave a comment »Chego agora em casa e verifico pela segunda vez se não ejaculei nas minhas calças, acabei de testemunhar um marco na história da integração dos quadrinhos com outras mídias, uma experiência cinematográfica tão intensa e hiperbólica que faz o fã e o leigo se ajeitarem na poltrona enquanto as pálpebras permanecem inertes.
Todos meus preconceitos foram atingidos por flechas, imobilizados por dardos atordoantes, cortados por um escudo, eletrocutados por um raio, explodidos por raios repulsores e esmagados por gigantescos punhos verdes. O que muitos especularam que seria praticamente um Homem de Ferro 3, acabou realmente como uma equipe de super-heróis, nenhum destaque foi mal dosado ou forçado, todos tiveram seu momento de acordo com suas habilidades e seu prelúdio, muito bem medido, parabéns Joss Whedon por este incrível roteiro que traz uma trama ousada, convertendo um membro da equipe logo no início e mostrando as jogadas sutis de Loki, guiando o filme de uma maneira em que todos os personagens se enfrentam sem incoerência, proporcionando o prazer para bilhões de curiosos que sempre imaginaram estes embates fantásticos, muitos elementos de comédia, muitíssimo bem colocados, diversão garantida para um nerd que conseguir arrastar uma paquera para ir ao cinema.
A construção dos personagens está incrível, Nick Fury não está lá apenas para cumprir tabela, é estrategista e ardiloso como nos quadrinhos. A Viúva Negra também está muito fiel a personagem, conseguindo arrancar informações até mesmo de um Deus das Travessuras. O Dr. Bruce Banner (Mark Ruffalo) não decepcionou, transmitiu bem a essência do cientista e até ganhou frase de efeito, não foi um “efeito Heath Ledger” mas o cara não fez feio. Clint Barton mostra, junto com a apetitosa Scarlett Johansson, que aqueles que não são super-humanos e não usam armadura podem fazer um belo estrago, esses dois tem cenas de ação mirabolantes! Tony Stark está muito hilário, como sempre Robert Downey Jr. em piloto automático, cenas de tirar o fôlego onde finalmente todo o arsenal do Homem-de-Ferro é explorado de forma sublime e, para alegria geral do público, muito destaque para o pseudo-anti-herói que tem sua índole debochada/alcoólatra quebrada quando testemunha a morte e é inspirado por Steve Rogers. Chris Evans está de parabéns, teve apenas uma piadinha durante todo o filme, o Capitão América realmente ficou no centro da equipe, ao contrário de tantos pôsteres e trailers que vêm sendo exibidos a tanto tempo sempre enaltecendo o latinha, fiquei muito preocupado com o uniforme azulão e as tais asinhas pintadas na lateral de sua máscara, franzi a testa ao ver a cara de desespero dele nos videos que circularam na web, “onde está o bandeiroso que deveria passar confiança, o símbolo que vai liderar a equipe e dar um soco nos primeiros sinais de adversidade?” ele está lá, em cenas extraordinárias (estou ficando sem vocabulário para descrever o filme) onde ele enfrenta os Chitauri e em situações em que ele se destaca mesmo no meio de mentes brilhantes como as de Stark e Banner.
Loki está muito bem interpretado, muitos elogiaram a atuação de Tom Hiddleston no filme do Deus do Trovão, mas eu não consegui captar muita coisa por quê eu não gostei muito de Thor, porém, em “The Avengers”, o vilão realmente ganha o foco que merece, arrogante, insano e estrategista, o asgardiano se apresenta como uma verdadeira ameaça que sozinho consegue desestabilizar os heróis durante toda história, culminando para a batalha final, cenas ininterruptas onde a porradaria entre vingadores e alienígenas eclode. As cenas da batalha final são indescritíveis, é exatamente o que eu li em um dos tweets publicado por um dos críticos que compareceram a premiere, são orgasmos nerds, simplesmente a coisa mais incrível que eu já vi no cinema. São cenas que fazem você parar para planejar sua próxima visita ao cinema, cenas que fazem você se perguntar quantas vezes você terá que ver isso novamente até que possa ter uma ínfima certeza de que os detalhes foram apreciados em sua totalidade. Homem de Ferro desviando de lasers, Hulk socando leviatãs no ar, Capitão América defletindo raios de plasma, Viúva Negra montada em alienígenas voando em uma nave descontrolada, flechas voando para tudo que é lado, seguidas por trovões e marretadas que fazem o cinema tremer.
Então fica a dica, um ótimo filme, acabou de derrubar o Dark Knight da primeira posição da minha lista de melhores filmes de super-heróis, só espero que os comic-book-hipsters definhem com sua idiotice e que esta obra incentive todo mundo a curtir cada vez mais histórias em quadrinhos. Então é isso, AVENGERS ASSEMBLE!!!
Este post também foi publicado no Dinamo Studio, Veja outras resenhas por lá!
http://www.cursodequadrinhos.com.br/coluna/os-vingadores-resenha/
Criando uma aplicação legada (Spring MVC, JQuery, Sitemesh e JDBC Template)
Posted: July 25, 2011 Filed under: Java Leave a comment »A idéia aqui é criar uma aplicação simples em Java onde o usuário pode cadastrar pessoas, mas para exemplificar a utilização de várias tecnologias interessantes, transformei aquilo que você faria com 1 JSP, 1 Servlet e 1 ou 2 classes em um monstro robusto/escalável/manutenível/confiável/extensível e tantas outras palavras clichês que você usa pra vender seu software mas não sabe o real significado.
Hoje nós temos na mesa:
- o Spring! (blá blá blá Padrões blá blá blá J2EE blá blá blá Rod Johnson blá blá blá Injeção de dependência)
- o JQuery! (blá blá blá Javascript blá blá blá Ctrl+C/Ctrl+V blá blá blá hype blá blá blá moda)
- o Sitemesh (blá blá blá Melhor que o Tiles/Facelets blá blá blá OpenSymphony blá blá blá Decorator)
- o JDBC Template (blá blá blá Pior que Hibernate blá blá blá liberdade para queries blá blá blá queria usar algo diferente do Hibernate)
Ok, vamos começar a criar nossa aplicação mesozóica. Vamos precisar de algumas bibliotecas de tags, para não ter que declarar as mesmas em toda página, vamos criar uma jsp para guardar isso:
WEB-INF/jsp/declareTagLibs.jsp
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="dec" uri="http://www.opensymphony.com/sitemesh/decorator" %>
<c:set var="contexto" value="${pageContext.request.contextPath}"/>
*Vamos colocar páginas na pasta WEB-INF/jsp para não permitir o acesso as mesmas através da url diretamente.
Agora vamos criar nossa página inicial que vai apenas redirecionar o usuário para as páginas mapeadas pelo Spring:
index.jsp
<%@ include file="/WEB-INF/jsp/declareTagLibs.jsp" %> <%-- Redirected because we can't set the welcome page to a virtual URL. --%> <c:redirect url="index"/>
Vamos compreender como este nome lógico (index) é mapeado ao configurar o componente ‘ViewResolver’. Segue abaixo as configurações necessárias:
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="TudoEhLegado" version="2.5"><!-- Se você não entende esta configuração, você deveria estar assando hambúrgueres--><display-name>TudoEhLegado</display-name> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list><!-- Magia. Não toque --> <servlet> <servlet-name>mvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet><!-- Ah não! Você está mapeando tudo(/), e os arquivos estáticos (.jpg, .png, .js, etc.)?Calma porra... --> <servlet-mapping> <servlet-name>mvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- Gambiarra do Spring que permite comandos HTTP além do GET e POST passando os mesmos por um campo hidden criado dinamicamente no formulário, desta forma podemos fazer uma aplicação RESTfull (GET, POST, PUT, DELETE) --><filter> <filter-name>hiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>hiddenHttpMethodFilter</filter-name> <servlet-name>mvc</servlet-name> </filter-mapping><!-- Vamos configurar o filtro do Sitemesh para que ele monte nosso layout com header, footer, menus, etc. --><filter> <filter-name>sitemesh</filter-name> <filter-class>com.opensymphony.sitemesh.webapp.SiteMeshFilter</filter-class> </filter> <filter-mapping> <filter-name>sitemesh</filter-name> <url-pattern>*</url-pattern> </filter-mapping></web-app>
Ainda no WEB-INF, temos que configurar o arquivo xml que vai coordenar os componentes do Spring, o nome deste arquivo deve ser o nome que atribuímos ao ‘DispatcherServlet’ (front-controller) seguido de ‘-servlet.xml’. Infelizmente, ainda temos mais arquivos xml para configurar depois deste.
mvc-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<!-- Solução para tratamento de requisições de arquivos estáticos -->
<!-- Handles GET requests for /resources/** by efficiently serving static
content in the ${webappRoot}/resources dir -->
<mvc:resources mapping="/resources/**" location="/assets/" />
<!-- Auto-scan for controllers -->
<context:component-scan base-package="br.com.temposmodernos.web" />
<!-- auto-scanned daos and services -->
<context:component-scan base-package="br.com.temposmodernos.service.impl" />
<context:component-scan base-package="br.com.temposmodernos.dao.impl" />
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="resources/messages" />
</bean>
<!-- Define o organizador OXM (marshaller) que é usado para converter objetos de/para
XML. -->
<bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller" />
<bean id="xmlConverter"
class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
<constructor-arg ref="xstreamMarshaller" />
<property name="supportedMediaTypes" value="application/xml" />
</bean>
<!-- View resolvers -->
<bean
class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="mediaTypes">
<map>
<entry key="html" value="text/html" />
<entry key="xml" value="application/xml"/>
<entry key="json" value="application/json" />
</map>
</property>
<property name="viewResolvers">
<list>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:viewClass="org.springframework.web.servlet.view.JstlView"
p:prefix="/WEB-INF/jsp/"
p:suffix=".jsp" />
</list>
</property>
<property name="defaultViews">
<list>
<bean
class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
</list>
</property>
</bean>
<context:property-placeholder
location="classpath:/resources/application.properties" />
<!-- banco de dados -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!-- este cara deve ser declarado por último -->
<mvc:annotation-driven />
</beans>
Segue o arquivo com os dados de acesso a banco configurado pelo <context:property-placeholder>:
/resources/application.properties
################### MySQL Configuration ########################## jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/tudoEhLegado jdbc.username=root jdbc.password=root
Agora vamos entender como o Sitemesh funciona, este framework de construção de layouts funciona basicamente através de um servlet filter que mapeia as páginas que devem ser decoradas e insere os trechos de html nos trechos configurados:
sitemesh.xml
<?xml version="1.0" encoding="UTF-8"?>
<sitemesh>
<!-- Local do arquivo de configuração de páginas decoradoras -->
<property name="decorators-file" value="/WEB-INF/decorators.xml"/>
<excludes file="${decorators-file}"/>
<page-parsers>
<parser content-type="text/html"
class="com.opensymphony.module.sitemesh.parser.FastPageParser" />
</page-parsers>
<decorator-mappers>
<!-- Ao configurar esses parâmetros, é possível definir um decorator via 'meta' tag no header da página. exemplo: <meta name="decorator" content="mydecorator"> -->
<mapper
class="com.opensymphony.module.sitemesh.mapper.PageDecoratorMapper">
<param name="property.1" value="meta.decorator" />
<param name="property.2" value="decorator" />
</mapper>
<!-- Mapeamento para internacionalização, concatena '-en' ao nome do arquivo -->
<mapper
class="com.opensymphony.module.sitemesh.mapper.LanguageDecoratorMapper">
<param name="match.en" value="en" />
<param name="match.zh" value="zh" />
</mapper>
<!-- Mapeamento para compatibilidade de browser, concatena '-ie' ao nome do arquivo -->
<mapper
class="com.opensymphony.module.sitemesh.mapper.AgentDecoratorMapper">
<param name="match.MSIE" value="ie" />
<param name="match.Mozilla/" value="ns" />
</mapper>
<!-- Define um decorator chamado 'printable' passando parâmetros, para páginas
que devem ter uma versão preparada para impressão -->
<mapper
class="com.opensymphony.module.sitemesh.mapper.PrintableDecoratorMapper">
<param name="decorator" value="printable" />
<param name="parameter.name" value="printable" />
<param name="parameter.value" value="true" />
</mapper>
<!-- prepara um parâmetro de url que pode ser usado para
especificar o decorator que será usado em alguma URL,
e.g.: 'myurl.jsp?foobar=mydecorator' vai mapear o decorator chamado "mydecorator"-->
<mapper
class="com.opensymphony.module.sitemesh.mapper.ParameterDecoratorMapper">
<param name="decorator.parameter" value="decorator" />
<param name="parameter.name" value="confirm" />
<param name="parameter.value" value="true" />
</mapper>
<mapper
class="com.opensymphony.module.sitemesh.mapper.ConfigDecoratorMapper">
<param name="config" value="${decorators-file}" />
</mapper>
</decorator-mappers>
</sitemesh>
Finalmente, o último arquivo de configuração:
decorators.xml
<?xml version="1.0" encoding="UTF-8"?> <decorators> <!-- urls excluídas nunca serão decoradas pelo Sitemesh --> <excludes> <pattern>**nodecorator**</pattern> <pattern>**nodecorator=true**</pattern> </excludes> <decorator name="simple" page="/WEB-INF/decorators/base_layout.jsp"> <pattern>*</pattern> </decorator> </decorators>
Segue nossa página decorator que vai montar o layout da aplicação, veja que as tags dec:title, dec:head e dec:body definem o local onde o conteúdo de escopo de página deve surgir:
/WEB-INF/decorators/base_layout.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ include file="/WEB-INF/jsp/declareTagLibs.jsp"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title><dec:title default="Web Page" /></title> <link rel="stylesheet" type="text/css" href="<c:url value="/resources/css/style.css"/>" /> <script type="text/javascript" src="<c:url value="/resources/js/jquery-1.4.2.min.js"/>"></script> <script type="text/javascript" src="<c:url value="/resources/js/jquery-ui-1.8.14.custom.min.js"/>"></script> <dec:head /></head> <body> <!--body code--> <div id="bodyWrapper"> <div id="header">Spring MVC with Annotations</div> <div id="navigation"> <a href="#">HOME</a> <a href="#">ABOUT</a> </div> <div id="pageBody"> <div id="sidebar"> <a href="#">Link 1</a><br /> <a href="#">Link 2</a><br /> <a href="#">Link 3</a><br /> </div> <div id="content"> <dec:body /> </div> </div> <div id="footer">Copyright 2011 marcelorjava - All Rights Reserved</div> </div> </body> </html>
Segue o css usado pela página decoradora:
/assets/css/style.css
#bodyWrapper { width:800px; height: 400px; }
#header { width: 800px; height: 45px; background-color: 99CCFF; font-size: 34px; text-align: center; font-family: "Times New Roman",Georgia,Serif; }
#navigation { width: 800px; height: 28px; background-color: CCFFFF; }
#navigation a { font-size: 22px; text-decoration: none; font-family: "Tahoma",Serif; }
#pageBody { width: 800px; height: 335px; font-size: 16px; }
#sidebar { width: 180px; height: 335px; background-color: 99FFFF; float: left; }
#content { width: 620px; background-color: white; float: right; }
#footer { width: 800px; height: 20px; background-color: CCFFFF; font-size: 18px; text-align: center; }
Para que o jQuery UI fique legal, é interessante inserir estes 2 arquivos css:
http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/themes/base/jquery-ui.css http://static.jquery.com/ui/css/demo-docs-theme/ui.theme.css
Voltando ao fluxo da aplicação, para mapear o nome lógico para a página WEB-INF/jsp/index.jsp, além do ViewResolver, temos uma classe controladora que pode implementar algo na camada de modelo e retornar o resultado para a view ‘index’ (neste exemplo esta classe não faz nada, apenas retorna o nome lógico da página index.jsp). As classes controladoras do Spring MVC são anotadas com @Controller para serem escaneadas de acordo com a configuração (<context:component-scan>). A classe também possui a annotation @RequestMapping para determinar o o formato do recurso que é pedido na requisição (exemplo: @RequestMapping(“/index/**”)), esta mesma anotação é usada em métodos para especificar qual deve tratar a requisição (exemplo: @RequestMapping(value = “/pessoa/create”, method = RequestMethod.GET)).
IndexController.java
package br.com.temposmodernos.web;import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod;@Controller @RequestMapping("/index/**") public class IndexController { @RequestMapping(value = "/index", method = RequestMethod.GET) public String index() { return "index"; } }
A annotation @Autowired é que faz a clássica injeção de dependência, back in the old days usávamos um ‘applicationContext.xml’ e declarávamos as classes de implementação que seriam injetadas, por exemplo:
<bean id="pessoaService" class="br.com.temposmodernos.service.PessoaService"> <!-- Dependência que será injetada --> <property name="pessoaDao" ref="pessoaDaoBean"/> </bean> <bean id="pessoaDaoBean" class="br.com.temposmodernos.dao.impl.MySQLDao">
Como a view ‘index’ foi mapeada, encontramos a página WEB-INF/jsp/index.jsp. Nesta página vamos apresentar uma lista de pessoas e fornecer um link para um formulário.
WEB-INF/jsp/index.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ include file="declareTagLibs.jsp"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<style>
.ui-autocomplete-loading { background: white url('/TudoEhLegado/resources/img/swirl-pessoa.gif') right center no-repeat; }
#procuraPessoas { width: 25em; }
</style>
<title>Insert title here</title>
<link rel="stylesheet" type="text/css"
href="<c:url value="/resources/css/jquery-ui.css"/>" />
<link rel="stylesheet" type="text/css"
href="<c:url value="/resources/css/ui.css"/>" />
<script type="text/javascript"
src="<c:url value="/resources/js/pessoa.js"/>"></script>
</head>
<body>
<h3>
<c:out value="${contexto}" />
Aplicação para POC (proof of concept): JQuery UI
(autocomplete)+Sitemesh+SpringMVC+JDBCTemplate
</h3>
<br />
<div class="ui-widget">
<label for="procuraPessoa">Busca: </label>
<input id="procuraPessoas" />
</div>
<h5>
<a href="<c:url value="/pessoa/create"/>">Novo</a>
</h5>
</body>
</html>
Logo no início, declaramos a tag <script> usando a tag JSTL ‘<c:url’ para adicionar o contexto da aplicação, apesar dos arquivos estáticos estarem dentro da pasta ‘assets’ utilizamos o endereço ‘/resources/js/’ pois o mesmo é mapeado de acordo com a nossa configuração do ‘mvc:resources’.
Logo depois vamos criar o campo que será usado pelo componente ‘auto complete’ do JQuery UI. Segue o código javascript que vai ativar este componente:
pessoa.js
$(document).ready(function() {
jQuery("#procuraPessoas").autocomplete({
minLength: 3,
delay : 400,
source: function(request, response) {
jQuery.ajax(
{
url: "/TudoEhLegado/pessoa/ajaxlist",
data:
{
limit : 15,
term : request.term
},
dataType: "json",
success: function(data)
{
response( jQuery.map( data.pessoaList, function( pessoa ) {
return {
id: pessoa.id,
value: pessoa.nome
};
}));
}
});
},
select: function(e, ui) {
var nome = ui.item.value;
var id = ui.item.id;
alert("O id de "+ nome +" eh: " + id);
}
});
});
Como funciona: Ao digitar no mínimo 3 caracteres, o parâmetro ‘source’ do componente deverá ser populado dinamicamente por uma função anônima de callback que vai usar uma chamada jQuery.ajax() para o endereço ‘/TudoEhLegado/pessoa/ajaxlist’, este endereço é mapeado pela ‘PessoaController’que vai chamar um método, que recebe as 2 variáveis declaradas em ‘data:’ (limit e term) como parâmetros (ver anotação @RequestParam), para resgatar as pessoas do banco de dados em formato JSON. Exemplo:
{"pessoaList":[{"id":1,"nome":"marcelo","endereco":"rua meh","telefone":"85 99999999"},{"id":2,"nome":"marcelo2","endereco":"rua meh2","telefone":"85 9999.9999"}]}
Sempre recuperando pessoas de acordo com o que foi digitado no campo de texto (term), como vamos ver mais na frente na implementação do JDBC template (WHERE nome_usuario LIKE ?) . no parâmetro ‘response’ desta função de callback são configurados 2 atributos: ‘id’ e ‘value’ que correspondem aos ids e aos nomes dos objetos ‘pessoa’ da estrutura JSON, estes atributos são usados na função ‘select’ (que é chamada ao selecionar alguma opção do auto-complete). Veja como fica legal (Coloquei um Thread.sleep() na controladora para vocês verem o swirl
:D ):
O link configurado para criar um novo registro de ‘Pessoa’ vai utilizar outra classe controladora, esta classe é interessante pois mostra como é simples preparar, validar e processar um formulário com o Spring MVC em um estilo RESTfull. Temos um mapeamento de requisição GET para um método invocado antes da renderização do formulário (neste exemplo estou configurando um modelo de número de telefone para a pessoa que será cadastrada), temos um mapeamento de requisição POST para um método invocado depois que o formulário é submetido, podemos implementar até mesmo métodos para tratar requisições DELETE e PUT com ajuda do ‘HiddenHttpMethodFilter‘, REST rlz!
PessoaController.java
package br.com.temposmodernos.web;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import br.com.temposmodernos.model.Pessoa;
import br.com.temposmodernos.service.PessoaService;
import br.com.temposmodernos.service.PessoaValidator;
@Controller
@RequestMapping("/pessoa/**")
public class PessoaController {
@Autowired
private PessoaService pessoaService;
@RequestMapping(value = "/pessoa/list", method = RequestMethod.GET)
public ModelAndView listaPessoas() {
System.out.println("listPessoas");
ModelAndView mav = new ModelAndView();
List<Pessoa> todasAsPessoasDoBd = pessoaService.list();
mav.addObject("pessoaList", todasAsPessoasDoBd);
return mav;
}
@RequestMapping(
value = "/pessoa/ajaxlist",
method = RequestMethod.GET)
public ModelAndView populaAutocompletePessoas(
@RequestParam("limit") int limit,
@RequestParam("term") String term
) throws InterruptedException {
Thread.sleep(5000);
ModelAndView mav = new ModelAndView();
List<Pessoa> todasAsPessoasDoBd = pessoaService.ajaxList(term, limit);
mav.addObject("pessoaList", todasAsPessoasDoBd);
return mav;
}
@RequestMapping(value = "/pessoa/create", method = RequestMethod.GET)
public ModelAndView novo() {
Pessoa pessoa = new Pessoa();
pessoa.setTelefone("99 9999999");
return new ModelAndView("pessoa/create", "pessoa", pessoa);
}
@RequestMapping(value = "/pessoa/create", method = RequestMethod.POST)
public String novo(
@ModelAttribute("pessoa") Pessoa pessoa,
BindingResult result) {
PessoaValidator pessoaValidator = new PessoaValidator();
pessoaValidator.validate(pessoa, result);
if (result.hasErrors()) {
//if validator failed
return "/pessoa/create";
} else {
pessoaService.save(pessoa);
//form success
return "redirect:../index";
}
}
//TODO permitir edição de pessoa
/*@RequestMapping(value = "/edit/{id}", method = RequestMethod.GET)
public ModelAndView edit(@PathVariable Long id) {
Pessoa pessoa = service.findById(id);
return new ModelAndView("pessoa/edit", "pessoa", pessoa);
}
@RequestMapping(value = "/edit", method = RequestMethod.PUT)
public String editar(Pessoa pessoa) {
pessoaService.update(pessoa);
return "redirect:../index";
}*/
}
Com o Spring 3, o recurso de autowiring aliado ao escaneamento de classes de serviço e repositório (<context:component-scan>), a DI (Dependency Injection) é feita de forma bem mais simples. Para quem não sabe o que é Injeção de dependência, segue uma imagem que explica tudo:
Segue a classe de validação. Apenas para mostrar exemplos, decidi verificar se o campo ‘nome’ está em branco e se o telefone possui código de área 85, não criei restrições fortes para o formato, mas uma expressão regular poderia ser usada para garantir o formato do valor para o campo ‘telefone’. A validação não é nada intrusiva, você tem muita liberdade pra fazer o que quiser e manipular os resultados como bem entender.
PessoaValidator.java
package br.com.temposmodernos.service;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import br.com.temposmodernos.model.Pessoa;
public class PessoaValidator implements Validator {
private String allowedAreaCodes = "85";
/** Logger for this class and subclasses */
protected final Log logger = LogFactory.getLog(getClass());
public boolean supports(Class clazz) {
return Pessoa.class.equals(clazz);
}
public void validate(Object obj, Errors errors) {
Pessoa p = (Pessoa) obj;
if (p!=null) {
if (p.getNome().equals("")) {
logger.info("Validando nome");
errors.rejectValue("nome", "error.blank", new Object[]{ "nome" } ,"Blank value.");
} else if ( !(p.getTelefone().substring(0, 2).equals(allowedAreaCodes)) ) {
logger.info("erro de validação");
errors.rejectValue("telefone", "error.forbidden",
new Object[] { allowedAreaCodes }, "Código de área proibido.");
}
}
}
}
Caso a validação falhe, temos tags definidas na página do formulário (form:errors) que configuram onde a mensagem de erro é exibida:
WEB-INF/jsp/pessoa/create.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ include file="/WEB-INF/jsp/declareTagLibs.jsp" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Formulário simples</title>
</head>
<body>
<h1><fmt:message key="pessoaForm.heading"/></h1>
<form:form action="${contexto}/pessoa/create" method="POST" modelAttribute="pessoa">
<table width="95%" bgcolor="f8f8ff" border="0" cellspacing="0" cellpadding="5">
<tr>
<td align="right" width="20%">Nome:</td>
<td width="20%">
<form:input path="nome"/>
</td>
<td width="60%">
<form:errors path="nome" cssClass="error"/>
</td>
</tr>
<tr>
<td align="right" width="20%">Endereço:</td>
<td width="20%">
<form:input path="endereco"/>
</td>
<td width="60%">
</td>
</tr>
<tr>
<td align="right" width="20%">Telefone:</td>
<td width="20%">
<form:input path="telefone"/>
</td>
<td width="60%">
<form:errors path="telefone" cssClass="error"/>
</td>
</tr>
</table>
<br>
<input type="submit" align="center" value="Execute">
</form:form>
</body>
</html>
As tags ‘<fmt:message>’ e mensagens de erro de validação estão armazenadas em um arquivo de propriedades, como definido pelo ‘ResourceBundleMessageSource‘.
/resources/messages.properties
pessoaForm.heading=:: Cadastro de Pessoas ::
error.blank=O campo {0} está em branco!
error.forbidden=Não é permitido inserir um código de área diferente de {0}!
Como todo bom framework J2EE, o formulário já cria pra você uma instância da sua classe de modelo e envia para a controladora (neste caso, através do parâmetro ‘modelAttribute’). Como o método da requisição é POST, vai cair no segundo método da controladora que mapeia a requisição ‘/pessoa/create’, caso não tenha erros na validação, o serviço ‘PessoaService’ vai se encarregar de chamar a nossa classe DAO (Data Access Object) para persistir nosso objeto ‘pessoa’.
PessoaService.java
package br.com.temposmodernos.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import br.com.temposmodernos.dao.PessoaDao;
import br.com.temposmodernos.model.Pessoa;
public interface PessoaService {
public boolean verificaSePessoaExiste(int id);
public void save(Pessoa p);
public void remove(Pessoa p);
public List list();
public List ajaxList(String pessoaNome, int limit);
public Pessoa findById(int id);
}
Não vamos implementar realmente nem metade desses métodos, só deixei assim pra ficar mais legal. Segue a classe de implementação marcada com a annotation @Service (annotation que torna a classe elegível para escaneamento automático, basicamente cumpre o mesmo papel da @component, mas é ideal para serviços).
PessoaServiceImpl.java
package br.com.temposmodernos.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import br.com.temposmodernos.dao.PessoaDao;
import br.com.temposmodernos.model.Pessoa;
import br.com.temposmodernos.service.PessoaService;
@Service("pessoaService")
public class PessoaServiceImpl implements PessoaService{
@Autowired
private PessoaDao pessoaDao;
@Autowired
public PessoaServiceImpl(PessoaDao pessoaDao) {
this.pessoaDao = pessoaDao;
}
public boolean verificaSePessoaExiste(int id){
return pessoaDao.exists(id);
}
public void save(Pessoa p){
pessoaDao.save(p);
}
public void remove(Pessoa p){
pessoaDao.remove(p);
}
public List<Pessoa> list(){
return pessoaDao.list();
}
public List<Pessoa> ajaxList(String nomePessoa, int limit){
return pessoaDao.ajaxList(nomePessoa, limit);
}
public Pessoa findById(int id){
return pessoaDao.findById(id);
}
}
Ok, persistência. Abaixo encontra a classe da camada ‘Model’:
Pessoa.java
package br.com.temposmodernos.model; public class Pessoa { private int id; private String nome; private String endereco; private String telefone; public Pessoa(String nome, String endereco, String telefone){ this.nome = nome; this.endereco = endereco; this.telefone = telefone; }public Pessoa() {
}// Getters & Setters }
Segue a classe da camada DAO:
PessoaDao.java
package br.com.temposmodernos.dao;
import java.util.List;
import br.com.temposmodernos.model.Pessoa;
public interface PessoaDao {
public boolean exists(int id);
public void save(Pessoa p);
public void remove(Pessoa p);
public List<Pessoa> list();
public List<Pessoa> ajaxList(String nome, int limit);
public Pessoa findById(int id);
}
PessoaDaoImpl.java
package br.com.temposmodernos.dao.impl;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import br.com.temposmodernos.dao.PessoaDao;
import br.com.temposmodernos.dao.mapper.PessoaRowMapper;
import br.com.temposmodernos.model.Pessoa;
@Repository("pessoaDao")
public class PessoaDaoImpl implements PessoaDao {
@Autowired
private DataSource dataSource;
@Override
public void save(Pessoa pessoa) {
JdbcTemplate insert = new JdbcTemplate(dataSource);
insert.update("INSERT INTO usuarios (nome_usuario, endereco_usuario, telefone_usuario) VALUES(?,?,?)",
new Object[] { pessoa.getNome(), pessoa.getEndereco(), pessoa.getTelefone() });
}
@Override
public void remove(Pessoa pessoa) {
JdbcTemplate delete = new JdbcTemplate(dataSource);
delete.update("DELETE from usuarios where is_usuario= ?",
new Object[] { pessoa.getId() });
}
@Override
public List<Pessoa> list() {
JdbcTemplate select = new JdbcTemplate(dataSource);
return select.query("SELECT id_usuario, nome_usuario, endereco_usuario, telefone_usuario FROM usuarios",
new PessoaRowMapper());
}
@Override
public List<Pessoa> ajaxList(String nome, int limit) {
JdbcTemplate select = new JdbcTemplate(dataSource);
return select.query("SELECT id_usuario, nome_usuario, endereco_usuario, telefone_usuario " +
"FROM usuarios " +
"WHERE nome_usuario LIKE ? " +
"LIMIT 0, ?",
new Object[] { nome+"%", new Integer(limit) },
new PessoaRowMapper());
}
@Override
public boolean exists(int id) {
JdbcTemplate exists = new JdbcTemplate(dataSource);
return (exists.queryForInt("SELECT COUNT(*) FROM pessoas where ID= ?",
new Object[] { id }) != 0);
}
@Override
public Pessoa findById(int id) {
JdbcTemplate select = new JdbcTemplate(dataSource);
return (Pessoa) select.queryForObject("select ID, NOME, ENDERECO, TELEFONE from pessoas WHERE ID= ?",
new Object[] { id }, new PessoaRowMapper());
}
}
O único objeto injetado que é configurado através de xml é o ‘dataSource’ declarado nesta classe, ele é utilizado para conexão com o MySQL e criar o template JDBC que vai executar as instruções SQL e retornar objetos/listas prontos para uso.
Classes utilitárias do JDBC Template:
PessoaResultSetExtractor.java
package br.com.temposmodernos.dao.mapper;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.ResultSetExtractor;
import br.com.temposmodernos.model.Pessoa;
public class PessoaResultSetExtractor implements ResultSetExtractor {
public Object extractData(ResultSet rs) throws SQLException {
Pessoa pessoa = new Pessoa();
pessoa.setId(rs.getInt(1));
pessoa.setNome(rs.getString(2));
pessoa.setEndereco(rs.getString(3));
pessoa.setTelefone(rs.getString(4));
return pessoa;
}
}
PessoaRowMapper.java
package br.com.temposmodernos.dao.mapper;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;
public class PessoaRowMapper implements RowMapper {
@Override
public Object mapRow(ResultSet rs, int line) throws SQLException {
PessoaResultSetExtractor extractor = new PessoaResultSetExtractor();
return extractor.extractData(rs);
}
}
Segue o SQL para criar a tabela ‘usuarios’ em seu banco de dados:
CREATE TABLE `usuarios` ( `id_usuario` int(10) NOT NULL AUTO_INCREMENT, `nome_usuario` varchar(300) DEFAULT NULL, `endereco_usuario` varchar(500) DEFAULT NULL, `telefone_usuario` varchar(15) DEFAULT NULL, PRIMARY KEY (`id_usuario`) ) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=latin1
Depois de criar todas esses arquivos, seu projeto deve ficar mais ou menos assim:
Segue a lista de JARs que foram utilizados:
jstl.jar junit-3.8.2.jar mysql-connector-java-5.1.14.jar commons-logging.jar commons-dbcp-1.4.jar commons-pool-1.5.6.jar jackson-core-asl-1.6.0.jar jackson-mapper-asl-1.6.0.jar sitemesh-2.4.2.jar spring-webmvc.jar spring-beans.jar spring-context-support.jar spring-context.jar spring-core.jar spring-orm.jar spring-oxm.jar spring-web.jar spring-jdbc.jar spring-dao.jar spring-asm.jar spring-expression.jar standard.jar xstream.jar
PS: Para quem usava Spring 2.5 e não está encontrando o ‘spring-webmvc.jar’ e o ‘spring-dao.jar’, os mesmos foram renomeados para ‘org.springframework.web.servlet-3.0.5.RELEASE.jar’e ‘org.springframework.transaction-3.0.5.RELEASE.jar’ respectivamente.
UPDATE:
Atenção: uma instrução jQuery.ajax não entra na função de ‘success’ (mesmo se você constatar via Firebug que o resultado foi um HTTP 200) se o json estiver mal-formatado.
jQuery.ajax({
Sempre é interessante validar os dados (http://jsonlint.com/), uma simples quebra de linha pode destruir toda a sua aplicação. exemplo:
{"pessoaList":[{"id":1,"nome":"marcelo \n
Rodrigues","endereco":"rua meh","telefone":"85 99999999"},
Para corrigir você deve ter certeza que o código que cria o json está tratando esses dados incorretos:
--RetornaUmJSON.jsp
...
jsonOutput.append(objectNameValue.replaceAll("[\n\r]"," "));
...
Podcast no bar!Aprendendo Objective-C
Posted: June 14, 2011 Filed under: Objective-C | Tags: AVAudioRecorder, IOS, Objective-c, Perfilamento, podcast, Xcode 1 Comment »Eu já gravei um podcast com uns amigos e, acreditem ou não, adivinhei que o Al Pacino era o assassino do filme “Righteous Kill” (duas faces da lei) antes do filme sair (opa, você não tinha assistido? Bom… não é um spoiler quando o filme é ruim). A gravação foi divertida, nós tentamos organizar como o programa iria funcionar, assim como a pauta e a sequência em que cada tópico seria discutido (basicamente era um papo sobre Robert De Niro & Al Pacino).
Não seria legal se eu pudesse encontrar uns amigos em um bar, gravar uma conversa sobre algo e logo depois remover as partes chatas da conversa, inserir umas vinhetas/sons engraçados e umas músicas de fundo? Tudo isso de forma simples e com uma interface amigável? Claro que, a priore, a qualidade do áudio não ficaria 100%, mas iria possibilitar a criação de podcasts para leigos e principalmente ajudar aqueles que, assim como eu, tem preguiça de editar gravações.
"Na natureza nada se cria, nada se perde, tudo se transforma" --Lavoisier, cientista e pederasta
O maior desafio no mercado de desenvolvimento IOS (iPhone, iPad, iPod touch.. e Mac Os) é que pode ser muito difícil pensar em algo que já não tenha sido criado. Então a idéia é adicionar mais features ou simplificar; moldar a idéia principal com algum diferencial que torne sua aplicação atraente.
Bom, pensando nisso tudo, resolvi escrever um tutorial sobre programação com Objective-C. Nos posts a seguir vou tentar montar uma aplicação passo-a-passo e compartilhar minhas descobertas no mundo do desenvolvimento IOS.
Como venho do desenvolvimento Java, fiquei muito impressionado com os detalhes esquisitos da linguagem. Portanto, quando você ver esta imagem
, significa que o trecho de código merece uma merece uma atenção especial.
Ok, vamos começar! Nós vamos precisar de:
1) Um Macbook (Ou uma VM, google ‘vmware + Mac OS X 10.6.6′). No meu caso eu resolvi comprar um MacBook White e acabei abandonando meu Dell Vostro 1510 + OpenSuse 11.2. No Mac OS tudo é muito smooth.. é um outro mundo, eu recomendo.
2) Xcode 4 (Copie de algum colega que pagou os $99 do cadastro de desenvolvedor ou baixe da App Store por $5. Dica: Copie o .dmg e descompacte via linha de comando: hdiutil).
Para a aplicação funcionar, ela tem que gravar a voz do usuário através do iPad, então vamos construir uma aplicação simples que inicia e para a gravação através de um botão. Inicie seu Xcode e crie um novo projeto do tipo “View-Based Application”:
Uma série de arquivos serão criados automaticamente (em uma estrutura virtual, não física), os mais importantes são:
PodcastAtTheBarAppDelegate.h - header da classe AppDelegate PodcastAtTheBarAppDelegate.m - implementações de métodos da AppDelegate* Toda aplicação de IOS tem um objeto (singleton) do tipo UIApplication. Isto fornece algumas funcionalidades básicas para executar e gerenciar uma aplicação. Ações enviadas a UIApplication são delegadas para a AppDelegate, desta forma podemos fazer nossos próprios tratamentos de eventos como: applicationDidfinishLauching, applicationDidBecomeActive, etc.
PodcastAtTheBarViewController.h - Arquivo para declarar assinatura de métodos. PodcastAtTheBarViewController.m - Classe principal onde ficarão os métodos da nossa camada de controle. PodcastAtTheBarViewController.xlib - Arquivo XML da estrutura da view (interface gráfica), tela da app.
Clique (apenas 1 vez) no arquivo .xlib e vamos usar o interface builder e a biblioteca de objetos para criar nossa tela, neste primeiro momento ela terá apenas um simples botão “Gravar”. Clique no ‘Round Rect Button’ na biblioteca de objetos e arraste para a tela do seu iPad:
Agora adicione o seguinte método na sua classe “PodcastAtTheBarViewController.m”:
-
- (IBAction)
-
criarEntradaDeLogQuandoAlguemClicarNoBotao { -
NSString *msg = @"ALGUÉM clicou no botão";
-
NSLog([msg lowercaseString]);
-
}
Vamos tentar explicar um pouco a sintaxe deste código: nas duas primeiras linhas nós temos a assinatura do método , o símbolo ‘-’ é usado para definir métodos de instância (para métodos de classe usa-se o ‘+’). Na linha 3 temos a declaração de uma NSString (Objeto String do Objective-C. É chamado assim por causa da biblioteca da linguagem que foi trazida da empresa comprada pela Apple, a Next/NextStep), veja que temos um asterisco (*msg), isto acontece porquê estamos declarando um ponteiro para o objeto NSString (você pode aprender mais sobre ponteiros aqui). Mais um detalhe, este objeto string possui um arroba (@) para determinar que esta String é uma NSString.
Na linha 4 temos a chamada para o método NSLog (veja que o ‘Quick Help’ sempre aparece no canto superior direito da tela com detalhes sobre Classes e Métodos que você está usando), passamos o objeto da mensagem dentro do NSLog. Neste exemplo estou usando o método ‘lowerCaseString’, se você deseja conhecer outros: que tal experimentar? Digite ‘[msg ' e use o recurso de auto-complete do XCode, quer saber o atalho? É bastante intuitivo: ESC ! É isso mesmo, você deve teclar 'esc' para ver as opções de auto-complete. E o que você achou da sintaxe para invocar métodos e acessar variáveis? Ao invés de algo decente como 'msg.toLowerCase()', usamos colchetes para cercar o objeto e seu método ([msg lowercaseString]), e isso fica ainda melhor quando utilizamos um método a partir de um objeto retornado de outro método, e.g.: NSString *bundlePath = [[[NSBundle mainBundle] resourcePath];
IMPORTANTE! Declare o método sem implementação (como um método abstrato) no arquivo ‘PodcastAtTheBarViewController’, os headers devem ser usados para que sua aplicação conheça os recursos que podem utilizar, se houvesse uma chamada para este método na classe .m antes da declaração do método, o mesmo não seria reconhecido. Então SEMPRE lembre de declarar tudo nos headers (mesmo que o faça depois de escrever a implementação). Assim, no final de nosso arquivo .h teremos:
- -(IBAction)mudarLabelQuandoOCaraClicarNoBotao;
- @end
Você ainda quer continuar? Ok, vamos mostrar mais uma pérola… Você deve estar se perguntando: “Mas como eu sei que este método será chamado quando eu clicar naquele botão específico?”, boa pergunta. A imagem abaixo mostra como “linkar” um evento do botão com um determinado método, após clicar com o botão direito no ‘Round Rect Button’ veremos uma lista de eventos, vamos clicar no pequeno círculo a direita do evento ‘Touch Up inside’ (algo próximo de ‘on release’) e arrastar, veja que uma linha azul aparece, arraste esta linha até o icone do cubo amarelo (File`s owner) e solte, você deverá ver a lista de métodos declarados, selecione o método ‘criarEntradaDeLogQuandoAlguemClicarNoBotao‘ e sua aplicação estará pronta para o primeiro teste.
Agora execute o simulador (⌘ + R) e clique no botão:
Você deve ver uma saída parecida com essa:
2011-05-31 22:53:07.155 PodcastAtTheBar[827:207] alguém clicou no botão
Agora vamos dar uma olhada na biblioteca que vai trabalhar com Audio:
http://developer.apple.com/library/ios/#documentation/AVFoundation/
Reference/AVAudioRecorder_ClassReference/
Ok, vamos inserir esta biblioteca no nosso projeto:
1) Clique no ícone de seu projeto que se encontra no menu do lado esquerdo, uma janela deve surgir, clique na aba “Build Phases”.
2) Clique em ‘Link Binary With Libraries’ e use o botão + para adicionar a nossa biblioteca ‘AVFoundation.framework’:
Agora precisamos declarar algumas coisas que serão usadas no nosso header (PodcastAtTheBarViewController.h):
- #import <UIKit/UIKit.h>
- #import <AVFoundation/AVFoundation.h>
- @interface recordViewController : UIViewController
- <AVAudioRecorderDelegate, AVAudioPlayerDelegate>
- {
- AVAudioRecorder *audioRecorder;
- AVAudioPlayer *audioPlayer;
- UIButton *recordButton;
- }
- @property (nonatomic, retain) IBOutlet UIButton *recordButton;
- -(IBAction) recordAudio;
- @end
Novamente, vamos tentar entender a sintaxe: Nas linhas 1 e 2 nós temos imports de outros headers. Na linha 3 temos a declaração da nossa classe ‘recordViewController’ (sim, classes são declaradas com a notação @interface) que estende(:) ‘UIViewController’. As classes declaradas entre ‘<>’ representam, o que chamamos em Java, Interfaces, mas em objective-c é chamado de @protocol. Essas classes são frequentemente usadas como classes “Delegate”, ou seja, são classes as quais eventos serão delegados (permitindo que seja feito override/implementação de seus métodos para realizar ações específicas), nós já temos um exemplo concreto que é a classe criada junto com nosso projeto (PodcastAtTheBarAppDelegate.h). Veja as seguintes linhas desta classe:
- @class PodcastAtTheBarViewController;
- @interface PodcastAtTheBarAppDelegate : NSObject <UIApplicationDelegate> {
Veja que o objeto pai é NSObject e que ela implementa a classe ‘UIApplicationDelegate’ para servir como ponto de implementação dos métodos invocados pela “UIApplication” (como discutido anteriormente neste post).
Mais um detalhe interessante na linha 1: ao declarar uma classe, usamos @interface, mas quando estamos importando uma classe usamos @class.
Voltando a classe ‘PodcastAtTheBarViewController.h’, nas linhas 6,7 e 8 criamos ponteiros para objetos que serão necessários na implementação dos botões. Na linha 10 declaramos um ponteiros referente ao botão como proprieade, mas os ‘getters’ e ‘setters’ para este ‘atributo’ da classe só será sintetizado depois. Os objetos são referenciados com um recurso do InterfaceBuilder chamado ‘IBOutlet’ e cada uma destas propriedades possui as instruções ‘nonatomic’ e ‘retain’. Explicando:
- nonatomic: Significa que quando os getters e setters da propriedade forem gerados através do @synthetize (na classe de implementação, arquivo .m), eles não serão implementados usando nenhum lock. Então, quando acessados, os valores desta propriedade podem ser mudados a qualquer momento e não vai haver nenhum bloqueio (quem vem de Java deve estar lembrando do modificador ‘synchronized’), i.e.: os acessos não são serializados.
- retain: Vai incrementar o contador de objetos retidos para esta propriedade, desta forma ela não será desalocada quando ela sair do escopo. Usando esta abordagem será necessário usar o ‘dealloc’ para liberar memória. Saiba mais aqui.
Logo depois temos a declaração do método (-(IBAction) recordAudio) e o fim da classe(@end).
* Importante: Lembre-se também de referenciar o botão como Outlet no ‘PodcastAtTheBarViewController.xib’, clique com o botão direito no botão ‘Gravar’ e arraste a linha azul a partir da seção ‘Referencing Outlets’ até o File`s owner, selecione o UIButton que você declarou: recordButton. Associe também o evento ‘Touch up inside’ ao novo método ‘recordAudio’.
Precisamos que um objeto AVAudioRecorder esteja disponível ao iniciarmos nossa aplicação, para isso utilizamos o seguinte método:
- - (void)viewDidLoad
AVAudioRecorder_ClassReference/Reference/Reference.html#//apple_ref/occ/instm/AVAudioRecorder/
initWithURL:settings:error:
- - (void)viewDidLoad {
- [super viewDidLoad];
- _
- _
- NSString * filePath = [NSHomeDirectory()
- stringByAppendingPathComponent:@"Documents/recording.caf"];
- NSDictionary *recordSettings = [NSDictionary
- dictionaryWithObjectsAndKeys:
- [NSNumber numberWithInt:AVAudioQualityMin],
- AVEncoderAudioQualityKey,[NSNumber numberWithInt:16],
- AVEncoderBitRateKey,
- [NSNumber numberWithInt: 2],
- AVNumberOfChannelsKey,
- [NSNumber numberWithFloat:44100.0],
- AVSampleRateKey,
- nil];
- NSError *error = nil;
- audioRecorder = [[AVAudioRecorder alloc] initWithURL: [NSURL fileURLWithPath:filePath]
- settings: recordSettings
- error: &error];
- if (error)
- {
- NSLog(@”error: %@”, [error localizedDescription]);
- } else {
- [audioRecorder prepareToRecord];
- }
- }
Na linha 5, usamos uma atrocidade (stringByAppendingPathComponent) para criamos uma NSString para referenciar um arquivo .caf (Core Audio File, formato suportado pelo Mac OS X), logo depois, na linha 7, definimos nossas configurações de gravação (sobre os valores armazenados no NSDictionary ‘recordSettings’: não faço a mínima idéia do que significam estes valores.. apenas copiei). Na linha 18 criamos (alocamos na memória) um objeto AVAudioRecorder e utilizamos o método initWithURL para inicializar a instância e configurar os devidos parâmetros, repare que criamos um objeto NSError para acompanhar os resultados da configuração da instância e, caso nenhum erro ocorra, chamamos o método ‘prepareToRecord’.
E finalmente, para implementarmos o que foi declarado no header, vamos escrever o seguinte código na classe ‘PodcastAtTheBarViewController.m’ para controlar a ação de gravar:
- #import “PodcastAtTheBarViewController.h”
- @implementation PodcastAtTheBarViewController
- @synthesize recordButton;
- - (void)viewDidLoad
- { …
- - (IBAction)
- criarEntradaDeLogQuandoAlguemClicarNoBotao {…
- -(void)recordAudio {
- if (!audioRecorder.recording)
- {
- [audioRecorder record];
- [recordButton setTitle:@"Parar/Play" forState:UIControlStateNormal];
- } else {
- [audioRecorder stop];
- [recordButton setTitle:@"Gravar" forState:UIControlStateNormal];
- audioPlayer = [[AVAudioPlayer alloc]
- initWithContentsOfURL:audioRecorder.url
- error:nil];
- audioPlayer.delegate = self;
- [audioPlayer play];
- }
- }
Logo após a declaração da classe sintetizamos (criamos os getters e setters) das nossas propriedades com o @synthesize. Ignoramos os métodos que já foram implementados/explicados até chegar ao nosso novo método (-(void)recordAudio), na linha 8. Como a idéia é criar um exemplo simples, decidi utilizar um botão só para gravar, parar e reproduzir o áudio. Então, ao clicar no botão, os seguintes eventos irão ocorrer: Se não estiver gravando inicia a gravação com o método ‘record’ do nosso objeto audioRecorder e muda o label do botão para ‘Parar/Play’ quando no estado normal (esse é um daqueles métodos de nomes compostos, setTitle:forState:). Caso esteja gravando, o ‘audioRecorder’ será parado, o label vai mudar novamente para ‘Gravar’ e criaremos um novo objeto do tipo ‘AVAudioPlayer’, ele será iniciado apontando para o mesmo arquivo (NSURL) que o ‘audioRecorder’ e, como não estou preocupado com erros aqui, faço com que seja atribuído ao método ‘initWithContentsOfURL:error:’ o valor ‘nil’ (nulo). A tarefa de reproduzir o áudio é delegada para o próprio objeto (linha 19) e finalmente ouvimos nossa gravação com o método ‘play’.
E se quisermos realizar alguma ação quando o ‘audioPlayer’ parar de reproduzir? Para ações como essa: temos os métodos delegados do nosso lado:
- -(void)audioPlayerDidFinishPlaying:
- (AVAudioPlayer *)player successfully:(BOOL)flag {
- NSLog(@”Terminou de reproduzir!”);
- }
Então é isso. Você deve estar pensando: “é muito fácil, vou colocar esta app na Apple Store e me preparar pra contar as verdinhas”. Não é bem assim, lembre-se que não estamos programando em Java, não temos um Garbage Collector nos ajudando a limpar os objetos que foram alocados em memória. Nesta pequena aplicação, por mais simples que seja, já existem trechos de código que causam memory leak. Para identificar estes possíveis ‘leaks’, utilizamos ferramentas de perfilamento, neste caso vamos usar a do próprio xcode. Inicie novamente a aplicação mas desta vez selecione ‘Profile’ ao invés de ‘Run’:
Na tela de opções de instrumentos, selecione o ‘Leaks’. Quando o ‘Instruments’ iniciar marque a opção ‘Leaks’ enquanto o simulator inicia sua aplicação, comece a gravar e reproduzir sua voz, continue até que ele identifique o vazamento:
Para corrigir isso? Sempre tenha em mente que, uma vez que algo é alocado em memória (alloc), terá que ser liberado em algum momento (release), então para corrigir o leak do ‘AVAudioPlayer’ utilizamos a seguinte abordagem (adapte as linhas 4 e 5 no código do método ‘recordAudio’):
- …
- -(void)recordAudio {
- …
- if (audioPlayer)
- [audioPlayer release];
- audioPlayer = [[AVAudioPlayer alloc]
- initWithContentsOfURL:audioRecorder.url
- error:nil];
UPDATE: Talvez isso não seja mais necessário.
Então chegamos ao final de mais um post. Para quem ficou curioso sobre a tentativa frustrada de gravar um podcast, segue um trecho da gravação:
Dia do Orgulho Nerd
Posted: May 20, 2011 Filed under: Meh | Tags: dia da toalha, mundo nerd, orgulho nerd Leave a comment »O incrível #zip -u
Posted: May 17, 2011 Filed under: Troubleshooting | Tags: zip linux troubleshooting java jsp logs 1 Comment »O cliente ligou… de acordo com ele, uma feature que estava funcionando muito bem, misteriosamente, parou de funcionar e cabe a você investigar o problema. Lembrando que você ainda tem aquelas outras 17 coisas em backlog que deveriam estar prontas ontem e que isso é algo crítico, o telefone não para de tocar, vários business users estão ligando, o gerente passa perto da sua baia, as paredes se fecham… você encontra o componente, veja só! Isso foi desenvolvido por um bando de indianos em 2009, que ótimo! Resumindo: mais um dia feliz no incrível mundo da tecnologia.
A primeira coisa a fazer é entender o problema, tentar reproduzir, ver os componentes envolvidos (se você não tem acesso ao ambiente, forneça instruções sobre como configurar o Log4J para modo ‘debug’, peça um pacote de logs e veja se tem algo novo no theoatmeal.com enquanto isso). Se você TEM acesso ao ambiente então é hora de entrar em ação, descubra qual JSP esta página está usando (verifique includes, iframes*ugh, portlets, etc.) e vamos ‘debugar’.
No meu exemplo, o problema ocorre porquê um formulário de criação de arquivos de Midia está retornando um erro (Javascript Alert = ”CatastrophicError #30666 <w3iRr7-c0D>: The URL is blank or invalid!”) ao tentar criar arquivos do tipo ‘Podcast’, você lê o código JS e descobre que este alert ocorre devido ao valor do campo ‘LINK’:
-
if (document.forms[0].LINK.value == '') { -
alert("<%=errorMsgAndCode %>: The URL is blank or invalid!");
E como esse campo é populado?
-
<input type="hidden" name="LINK" value="<%= sLink %>">
Ok. Agora vamos ter que rastrear como esta variável está sendo populada. Segue abaixo um trecho com pequenas alterações do código. Muitas linhas de código que você encontrar podem ser irrelevantes mas, na vida real, é pouco provável que você encontre alguma documentação sobre a implementação da feature (isso reforça o paradigma do XP “a documentação é o código”,… Mesmo que às vezes o código leve a algo no estilo GoHorse):
-
<%@ page import=”com.company.utils.CaothicUtils,
-
<%@ page contentType=”text/html; charset=UTF-8” %>
-
<%@ page errorPage=”/common/errorpage.jsp” %>
-
<html>
-
<head>
-
<%
-
-
Map paramMap=request.getParameterMap();
-
-
String staticPath = null;
-
String linkType = null;
-
//achei fantástico o nome dessa variável…
-
boolean flag =true;
-
-
String[] linkTypeArray = (String[])paramMap.get(“linkType“);
-
String[] staticPathArray = (String[])paramMap.get(“staticPath“);
-
-
if ((linkTypeArray != null) && (linkTypeArray.length > 0))
-
linkType = linkTypeArray[0];
-
if ((staticPathArray != null) && (staticPathArray.length > 0))
-
staticPath = staticPathArray[0];
-
-
if(“SF“.equalsIgnoreCase(linkType))
-
if( staticPath!=null && staticPath.matches(“[a-zA-Z0-9_ /.-]*“)) {
-
flag= true;
-
} else {
-
flag= false;
-
}
-
if(flag) {
-
-
String sLink=”“;
-
//O tipo de link do nosso formulário é ‘PC’ (Podcast) então não vamos entrar aqui
-
if(linkType.equalsIgnoreCase(“IMG“)){
-
sLink = CaothicUtils.buildImage(paramMap);
-
sLink = sLink.replaceAll(“static_file(.+)([0-9a-zA-Z]{40})“, “static_file$1“);
-
} else {//Aqui está a linha de código que procuramos!
-
sLink = CaothicUtils.buildLink(paramMap);
-
}
-
…
É interessante ter certeza que nosso formulário está enviando os dados corretos na requisição, vamos dar uma olhadinha nos parâmetros com nosso amigo Firebug:
É.. parece que está tudo ok por aqui… Vamos agora verificar o que a classe ‘CaothicUtils’ está fazendo. Para encontrar a classe vocês podem usar o ‘whereis.jar‘:
$ java -jar whereis.jar CaothicUtils /opt/company/webapps/ /opt/company/webapps/ext_ear/APP-INF/lib::ext-links.jar::com/company/xxx/utils/CaothicUtils.class
Ok, vamos descompilar esta classe e ver o que está acontecendo no método ‘buildLink()’, para isso vocês podem usar o ‘JD Decompiler‘:

No início do método buildLink() a variável ‘builderClass’ é populada com o seguinte código:
-
String[] builderClassArray = (String[])paramMap.get("builderClass"); -
if ((builderClassArray != null) && (builderClassArray.length > 0))
-
{ builderClass = builderClassArray[0]; }
Como foi constatado na análise dos parâmetros passados a requisição, o valor do parâmetro ‘builderClass’ é: com.company.things.otherthings.caothicLinkBuilder. Vamos procurar por esta classe usando o ‘whereis.jar’ e mais uma vez descompilar código.
Bom, vamos parar por aqui… O real objetivo deste post não é mostrar a resolução deste problema e sim apresentar alguns métodos/ferramentas de troubleshooting. Agora que todo um contexto foi passado é hora de voltar ao título deste post. Apresento a vocês o incrível ‘zip -u‘.
Pra quem já tem uma vivência com Linux o comando zip é algo bem comum, mas eu quero colocar o holofote no parâmetro -u:
-u Replace (update) an existing entry in the zip archive
only if it has been modified more recently than the ver-
sion already in the zip archive. For example:
zip -u stuff *
Imagine se, para fazer debug da JSP e das classes citadas anteriormente, nós precisássemos colocar linhas com instruções para ver se as variáveis estão sendo devidamente populadas. Imagine o trabalho de descompactar e compactar cada .war e .jar para inserir uma JSP ou classe durante sua rotina de troubleshooting. Imagine atualizar um pacote em produção com algumas JSPs numa janela de alguns minutos para corrigir algo crítico.
Para todas estas situações, você pode usar o ‘zip -u’. Vamos colocar a seguinte linha na nossa JSP para ter certeza que o valor da variável ‘linkType’ está correto (com comentários de html para não afetar o look and feel):
-
out.println("<!-- link type = "+ linkType +" -->");
É claro… Usar o código descompilado para fazer debug remoto poderia tembém ser uma opção, você poderia observar os valores através da tela de Debug de sua IDE:
-Djava.compiler\=NONE -Xdebug -Xnoagent -Xrunjdwp\:transport\=dt_socket,server\=y,address\=6065,suspend\=n
Mas você teria que alterar os argumentos da JVM, configurar o host e a porta 6065 na sua IDE, os breakpoints vão afetar as threads que estão em execução no ambiente, a configuração da rede teria que permitir essa conexão, etc.
Ok, para atualizar a JSP no war, vamos criar a mesma estrutura de pastas fora do war e colocar uma cópia da JSP dentro dessa estrutura, por exemplo:
caothic.war
|-- scripts
|-- common
|-- jsps
|-- links
|-- exemplo.jsp
|-- WEB-INF
Se você estiver no mesmo diretório do arquivo ‘caothic.war’, use o comando ‘mkdir -p jsps/links‘ para criar a estrutura de pastas e depois copie a JSP para a pasta ‘links’. Após alterar a JSP (e.g.: usando o vi) é só executar:
$ zip -u caothic.war jsps/links/exemplo.jsp
Você deve ver algo parecido com isso:
updating: jsps/links/exemplo.jsp (deflated 66%)
* Se você usou o comando e no outro dia esqueceu os parâmetros passados, use ‘history | grep zip -u‘ e continue a debugar
** LEMBRANDO que será necessário um redeploy do pacote .ear (caso seja um .jar dentro de APP-INF/lib) ou do .war (em caso de JSP ou .jar) através do servidor de aplicação para que as alterações tenham efeito.
Use também para as classes dentro dos pacotes .jar. Se nenhum logger foi configurado na classe e você está inserindo System.out.println() por todo o código, descubra qual é a saída padrão das mensagens que estão sendo geradas por esta classe com a seguinte abordagem:
$ ps fuxa | grep java
Confirme o PID do seu servidor de aplicação
someuser 11058 3.6 10.1 2607008 1660612 ? Sl May13 225:40 \_ java -Dprogram.name=run-default.sh -server -Xms1536m -Xmx1536m -XX:MaxPermSize=192M -XX:PermSize=192M -XX:+UseParallelGC -Xloggc:gc.log -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:-TraceClassUnloading -Djava.net.preferIPv4Stack=true -Djava.endorsed.dirs=/opt/company/jboss-4.2.2.GA /lib/endorsed -classpath /opt/company/jboss-4.2.2.GA/bin/run.jar org.jboss.Main -c default -b 0.0.0.0
Através dos file descriptors, vamos descobrir em que logs este processo está escrevendo:
$ ls -la /proc/11058/fd | grep .log l-wx------ 1 someuser somegroup 64 May 17 14:44 1 -> /opt/company/jboss-4.2.2.GA/bin/scripts/stdout_stderr_201105130805.log
Ou coloque algo bem diferente no texto de saída e faça uma busca:
$ find /opt/company -type f -print | tr '\n' '00' | xargs -0 grep -i ####linkVar####
Então, é isso… Espero que o ‘zip -u’ facilite a vida de todos vocês.
Out Of Memory e Heap Dumps
Posted: January 21, 2011 Filed under: Troubleshooting | Tags: heap, jvm, leakage, out of memory, OutOfMemoryError Leave a comment »Corram para as montanhas! Este erro apareceu nos logs!
Exception in thread “NOM-EdaMinhaThread-0″ java.lang.OutOfMemoryError: Java heap space.
Nestas horas é comum aparecer alguém e dizer:
“É só aumentar a memória então…”.
Deixe de lado este pobre ignóbil e *Investigue* o que realmente aconteceu, colocar mais RAM no servidor ou reconfigurar o -Xmx (argumento da JVM que define o espaço máximo da Heap) não é a solução definitiva. Veja por exemplo esta bacia cheia de água:
Imagine que no fundo desta bacia existe um espaço que serve para escoar a água, a água entra e vai enchendo até um certo nível e vai sendo escoada em um ritmo legal. Imagine agora o mesmo cenário onde a torneira está jorrando mais água do que o buraco no fundo da bacia pode escoar, eventualmente esta bacia vai transbordar. Então, diante deste cenário temos que nos perguntar: Adianta aumentar o tamanho da bacia?
A água vai transbordar de um jeito ou de outro, o ‘OutOfMemoryError’ vai ocorrer de qualquer maneira pois existem objetos gigantes (ou vários objetos de vida longa criados constantemente) ocupando toda a heap (http://en.wikipedia.org/wiki/Memory_leak).
Então você deve gerar uma Heap Dump e analizar que objetos estão ocupando a maior parte da heap (objetos que não deveriam ocupar tanto espaço ou não deveriam estar ali em primeiro lugar).
Alguns métodos para gerar HeapDumps:
1) -XX:+HeapDumpOnOutOfMemoryError
2) -XX:+HeapDumpOnCtrlBreak (Para ambientes Windows)
3) jmap -dump:format=b,file=HeapDump.hprof <pid> (o PID é o process identifier da JVM)
*O HeapDump será gerado no 'working directory' (ex: no Tomcat a Heap é gerada dentro da pasta /bin)
Segue uma das páginas que melhor reúne abordagens para gerar Heap Dumps:
http://wiki.sdn.sap.com/wiki/pages/viewpage.action?pageId=33456
Como eu faço para ler um HeapDump?
Você vai precisar de uma ferramenta que possa interpretar o Heap Dump, eu recomendo o IBM Heap Analyzer e o Eclipse Memory Analyzer. Se você já gerou o seu dump no formato ‘.hprof’ então está pronto para analizar o que estava na Heap naquele momento.
Para baixar o IBM Heap Analyzer: http://www.alphaworks.ibm.com/tech/heapanalyzer
1) Execute o arquivo ha398.jar com o seguinte comando:
java -jar ha398.jar
Se o Heap Dump for gigantesco talvez você precise alocar mais memória para a ferramenta:
java -Xmx1800m -jar ha398.jar
Se você possuir um computador decente, eventualmente vai passar desta tela aqui:
2) Depois disso temos um sumário com uma série de informações:
3) A análise deve tomar caminhos diferentes dependendo do cenário, se você procura um objeto em específico, poucas instâncias que estejam ocupando muito espaço ou muitos objetos que ocupam a maior parte da Heap (Leak suspect), a ferramenta fornece formas diferentes de vizualizar a lista de objetos (tipos de objetos, instâncias e associações):
O Eclipse Memory Analyzer funciona de uma maneira parecida, mas além de possuir um gráfico mais legal concentra a análise partindo das threads que estavam execução no momento em que o HeapDump foi gerado:
Lembrando que as ferramentas tem rotinas de investigação de suspeitos que as vezes podem apontar conclusões erradas, não confie em qualquer análise arbitrária, entenda como sua aplicação funciona e verifique o que é esperado e que não é, não presuma que você pode abrir qualquer heap dump e apontar o culpado, entender funcionalidades de baixo nível ajuda muito nessas horas.
Outra vantagem do Eclipse Memory Analyzer é que a interface é bem mais agradável ,veja esta tela em “tree view”:
A Young Generation
Posted: January 21, 2011 Filed under: Java | Tags: hotspot, jvm, survivor space, young generation Leave a comment »From,To = Survivor
Dentro da área Young temos 3 áreas menores que são: o Eden, From e To. Para visualizar:
Todo objeto quando criado deve imediatamente ir para o Eden (caso ele seja muito grande para o espaço do Eden ele será automaticamente ‘promovido’ para a Old Generation, este é o conceito de ‘premature promotion’, isso não é legal), depois que uma minor collection acontece movemos apenas os objetos que sobreviveram para a 1º área de sobreviventes, ou seja, o ‘From space’, depois que outra minor collection ocorre os mesmos objetos sobreviventes vão para a 2º área, o ‘To space’, neste espaço não ocorre GC, o objeto fica protegido contra a coleta de lixo, ele vai ficar neste estado até que aconteça a “troca”: em um determinado momento (quando a JVM achar melhor) o espaço ‘To’ vai se tornar ‘From’ e vice-versa.
Isso pode estar parecendo meio confuso, então vou tentar complicar mais ainda com um desenho:
Então o objeto é criado no ‘Eden’, é movido para o ‘From’, depois para o ‘To’, as áreas ‘From’ e ‘To’ trocam de lugar e os objetos que estavam numa boa na área ‘To’ (isentos da coleta de lixo) agora estão na ‘From’ (de volta a área de risco), depois de outro minor collection a “Troca” acontece novamente e assim somente os objetos escolhidos serão promovidos para a Old Generation. Você deve estar se perguntado “quantas vezes estes objetos sambam entre minor collections e trocas de survivor?”, quem define isso é o argumento ‘-XX:MaxTenuringThreshold‘ (de acordo com o que pesquisei, o valor default é 31).
Controlando os espaços
Os argumentos mais óbvios são os clássicos ‘-Xms’ e ‘-Xmx’ (mínimo e máximo de espaço inicial) mas para redimensionar a Heap de forma proporcional podemos usar o ‘newRatio’ e o ‘SurvivorRatio’, o primeiro é bem simples, por default a JVM da Sun (Hotspot) tem esse parâmetro definido como ’2′ (-XX:NewRatio=2), ou seja, a Old Generation possui 2 vezes o tamanho da Young, o argumento -XX:NewRatio define uma proproção ’1:n’ entre a Young e a Old.
O argumento -XX:SurvivorRatio é mais interessante, ele define uma proporção ’1:n’ entre o Survivor e o Eden. Ou seja, se este parâmetro estiver configurado como ‘-XX:SurvivorRatio=6′ a área de survivor vai ocupar 1/8 da Young.O Eden ocupa 6 espaços e o survivor(from,to)2, portanto 6+2=8. Segue outro desenho:
Acho que neste ponto podemos concluir que o maior desafio de uma análise de problema ou tuning é criar o equilíbrio perfeito (ou melhor possível) de parâmetros passados para a JVM e o entendimento da aplicação (como os objetos se comportam na Heap).
Logs do Garbage Collector
Posted: January 21, 2011 Filed under: Java, Troubleshooting | Tags: Full GC, garbage collector, gc logs, minor collection, PSYoungGen Leave a comment »Minor Collection e Full GCs
Quando a coleta de lixo ocorre na Young generation temos uma minor collection e quando ocorre na Old generation temos um Full GC (Stop the world, M&S), veja como uma minor collection é representada nos logs do Garbage Collector:
17767.137: [GC [PSYoungGen: 1052672K->175413K(1228096K)] 3100706K->2233257K(4035264K), 0.4126550 secs] [Times: user=0.78 sys=0.02, real=0.42 secs]
Viu? É tão simples quanto isso:
Você só precisa entender o que cada valor representa:
17767.137 = Tempo em segundos desde que a JVM foi iniciada.
[PSYoungGen: 1052672K->175413K(1228096K)]
espaço ocupado na Young antes do GC->espaço ocupado na Young depois do GC (Total da Young) 3100706K->2233257K(4035264K) espaço ocupado na Heap inteira antes do GC->espaço ocupado na Heap depois do GC (Total da Heap*)*Sem contar com o PermGen
0.4126550 secs
Tempo da operação do GC[Times: user=0.78 sys=0.02, real=0.42 secs]
Ver tabela abaixo:| Real | is wall clock time – time from start to finish of the call. This is all elapsed time including time slices used by other processes and time the process spends blocked (for example if it is waiting for I/O to complete). |
| User | is the amount of CPU time spent in user-mode code (outside the kernel) within the process. This is only actual CPU time used in executing the process. Other processes and time the process spends blocked do not count towards this figure. |
| Sys | is the amount of CPU time spent in the kernel within the process. This means executing CPU time spent in system calls within the kernel, as opposed to library code, which is still running in user-space. Like ‘user’, this is only CPU time used by the process. See below for a brief description of kernel mode (also known as ‘supervisor’ mode) and the system call mechanism. |
Não é incrível? Agora você pode ver uma linha como essa e realmente interpretar o que está acontecendo (Para gerar estas linhas de log com sua JVM utilize o argumento: -verbose:gc ou, se preferir ver isso em um log específico, -Xloggc:file), você pode começar criando gráficos mostrando o que está acontecendo com sua JVM em um determinado momento, como este:User+Sys will tell you how much actual CPU time your process used.
Desta forma comprovamos que 10 mb de objetos foram promovidos para a Old Generation, concorda?
Veja como um Full GC é representado nos logs:
17684.691: [Full GC [PSYoungGen: 175417K->0K(1228096K)] [ParOldGen: 2715481K->2019446K(2807168K)] 2890899K->2019446K(4035264K) [PSPermGen: 118571K->118548K(163840K)], 6.1196070 secs] [Times: user=11.34 sys=0.02, real=6.12 secs]
Você pode utilizar ferramentas para visualizar estas atividades, como por exemplo, o GC Viewer:
Porquê isso é interessante? Porquê com estas informações você pode resolver problemas de performance e até ‘tunar’ sua JVM! Os problemas de performance mais comuns (envolvendo memória) são causados por: 1) número e tamanho de objetos, 2) Tempo médio de vida de objetos e 3) Tamanho da Heap.
Permanent Generation
Posted: January 21, 2011 Filed under: Java | Tags: class, MaxPermSize, permanent generation, Permgen, pool de strings Leave a comment »Um breve comentário sobre a Permanent Generation (non-heap)
Esta área armazena as instâncias das Class (java.lang.Class) e o Pool de Strings (tem um texto abaixo bem legal, mas você pode ignorar se quiser…), ou seja, se sua aplicação carregar muitas classes na JVM você pode ver o seguinte erro: ‘java.lang.OutOfMemoryError: PermGen space’, você pode resolver isso de várias formas, uma delas é configurar o argumento -XX:PermSize para um tamanho maior. Na JVM 1.6 o tamanho padrão é 8mb para JVM Client e 16mb para Server, e o tamanho máximo (-XX:MaxPermSize) para ambas é: 64mb.
Você pode dizer, “Bem, isso pode ser legal, mas e se alguém sobrescrever a funcionalidade da classe String; não poderia isso causar problemas na pool?” Isso é uma das principais razões do porquê da classe String ser marcada como final. Ninguém pode sobrescrever os comportamentos de nenhum método de String, então você pode ficar sossegado que os objetos String serão sempre imutáveis.





























