Java — znakové kódování
Jedno z nejčastěji tázaných témat, v diskusních skupinách věnovaných Jave,
se točí kolem kódování znaků. Nejčastěji se to týká kódování při čtení/zápisu
servletu (JSP), databáze a souborů. Obecně to může generalizovat na operace
probíhající na hranici mezi JVM a okolním světem, kde dochází převodu bytové
reprezentace na znakovou reprezentaci a naopak. Mě osobně nejvíce
problematiku kódování v Jave osvětlil Makub v diskusní skupině
na serveru java.cz a tak se pokusím podělit.
Nemám problém protože všechno je Unicode
Výše uvedený nadpis si najdete v kdejakém pamfletu o Jave. Ano je to skutečně
tak, že Java pracuje vnitřně se všemi znaky jako s Unicode. Unicode přiřazuje každému znaku
jedinečné číslo a definuje tři formy, jak může být toto číslo reprezentováno
v byte, word nebo double word formě (8, 16, 32).
To že Java pracuje vnitřně s Unicode (používá UTF-16), je tak omlácenou
frází, že vede mnoho vývojářů k přesvědčení, že se nemusí o kódování starat.
Opak je pravdou. Při jakékoliv probíhající na hranici mezi JVM a okolním
světem, například čtení textového souboru nebo zápis do databáze, dochází k
převodu z vnitřní unicodovské reprezentace znaku na jeho bytovou reprezentaci
a naopak.
Vnější bytová reprezentace pak odpovídá zvolenému kódování. Pokud toto
kódování neurčíme, použije JVM defaultní kódování, které závisí na prostředí,
v němž běží. Právě při převodu znaku na bajtovou vnější reprezentace a naopak
dochází k problémům. Pokud se defaultní kódování JVM liší s kódováním, které
chceme na vstupu/výstupu a my toto kódování explicitně Jave neurčíme, dojde k
převodu na bytovou reprezentaci či naopak, podle defaultního kódování.
Klasickým příkladem je zpracování HTTP požadavku v servletu. Pokud začneme
číst parametry HTTP požadavku, před tím než zavoláme metodu
setCharacterEncoding, dojde k tomu, že servletový kontejner
použije defaultní kódování pro všechny parametry.
Jednoduchá rada, plynoucí z výše uvedeného, tedy zní:Explicitně
určujte kódování pro všechny vstupně/výstupní operace mimo JVM.
Problém této rady je v tom, že vývojáři si často neuvědomí co je mimo JVM.
Takže jedná se například o práci s databází, souborovým systémem nebo
zpracování HTTP požadavku.
Na závěr jsem si nechal pár praktických rad, které řeší 90% problémů.
Obecné — žádné texty ve zdrojových kódech
Vyčleňte všechny aplikační texty do resource bundle a ušetříte si s tím
plno problému.
Obecné — čtení/zápis textových zdrojů
Používejte třídy java.io.InputStreamReader a java.io.OutputStreamWriter.
Obecné — nastavte kódování pro kompiler
Pokud používáte ve zdrojovém kódu aplikační texty např. pro výjimky,
nezapomeňte určit kompileru kódování. Slouží pro to parametr
encoding.
Obecné — zkonvertujte properties resource bundle před použitím
Pokud používáte resource bundle založený na properties souboru, prožeňte
jej, v rámci build procesu, přes utilitku native2ascii. Ta je
standardní součástí Java SDK.
Antovský task:
<target name="native2ascii">
<native2ascii
encoding="utf-8"
src="${src.dir}"
dest="${web.dir}/WEB-INF/classes"
includes="**/*.properties"
ext=".properties"/>
</target>
Server - nastavte kódování pro HTTP request/response
Před tím než přečtete první parametr a zapíšete první znak na výstup
nastavte pro oba požadované kódování. V případě HTTP požadavku se k tomu dá
elegantně využít servletový filtr. Filtrem zamezíte případným problémům,
které mohou nastat v případě, že si na parametry "šáhne" někdo před vámi,
např. validátor vstupních parametrů.
public class RequestEncodindFilter implements Filter{ public void doFilter( ServletRequest rq, ServletResponse re, FilterChain chain) throws IOException, ServletException {rq.setCharacterEncoding("utf-8");//kodovani chain.doFilter(rq,re);}
public void init(FilterConfig fc) throws ServletException {} public void destroy() {} }
web.xml
<filter>
<filter-name>RequestEncodindFilter</filter-name><filter-class>filterpackage.RequestEncodindFilter</filter-class> </filter>
<filter-mapping> <filter-name>RequestEncodindFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
Pro JSP stránky používejte direktivu page a atribut
pageEncoding a contentType
<%@ page contentType=“text/html; charset=utf-8” pageEncoding=“utf-8” %>
Databáze - nastavte kódování pro JDBC ovladač
JTDS ovladače mají parametr, kterým určíte kódování pro práci s
CHAR/VARCHAR/TEXT typy.
Connection string MySQL JDBC ovladače:
jdbc:mysql://localhost/devdb1?useUnicode=true&characterEncoding=UTF-8Verze na Medium · Archiv původní stránky
Související články
Související epizody CZ Podcastu
- #3 · Java 6 SE, Vývoj webových aplikací
Dagi a Filemon o novinkách v Javě 6 a webových frameworcích.
- #1 · Vývojová prostředí v Javě
První díl CZ Podcastu se věnuje vývojovým prostředím pro Javu.