2. června 2009

Jaký máte vztah k Java anotacím? - výsledky

Poslední anketa se týkala anotací a dopadla následovně (hlasovalo 125 lidí):

  • Používám, ale jen někde (43%)
  • Mám je rád, používám, kde se dá (38%)
  • Nemám je rád, snažím se jim vyhnout (10%)
  • Nevadí mi (8%)


Já sám jsem hlasoval pro "Používám, ale jen někde" a nejsem moc zastáncem používání anotací všude, kde se dá. Osobně nejčastěji používám Hibernate Annotations nebo JPA anotace a Spring a jUnit anotace při psaní testů.

Zeptal jsem se pár kolegů na jejich názor na anotace (1, 2) a v následujícím resumé se to pokusím nějak shrnout:

Argumenty pro:
  • jednoduché, intuitivní - toto se může lišit anotace od anotace, ale dost často je zápis pomocí anotace jednodušší než nějaká jiná podoba konfigurace.

  • větší flexibilita - díky používání anotací může být určité řešení flexibilnější než standardní verze bez anotací. Jako příklad bych uvedl Spring MVC - klasická verze vs. verze s anotacemi. Anotace přináší určitě větší flexibilitu, asi i větší jednoduchost používání a menší nároky na učení se klasického API kontrolerů, ale dle mého názoru to jeden nedostatek má. Zejména u větších projektů se hodí, když má projekt nějakou strukturu, nějaká pravidla a toto se hůře vynucuje pomocí anotací než při používání klasického API. Uvedu jeden konkrétní příklad: myslím, že je vhodné mít odkazy na JSP stránky a URL adresy mimo vlastní Java kód kontrolerů. V klasickém API mám settery na viewname, ale co u anotací? Vytvořím si vlastní předky, ale už to zase bude na úkor nějaké funkcionality, které mi anotace nabízejí.

  • pro popis metadat - anotace mi umožňují přidat klasickým třídám (POJO) nové vlastnosti, které není potřeba implementovat přímo v Java kódu. Např. že je třída resp. metoda deprecated, že je třída volána v transakci pouze pro čtení, že je metoda pouze pro uživatele s rolí READ apod.


Argumenty proti:
  • již není POJO - pokud chcete psát "lehké", testovatelné a flexibilní aplikace (z pohledu budoucích změn), pak je určitě dobré používat POJOs. Jen pak je možné používat dané objekty v různých scénářích a prostředích - testy vs. produkční nasazení, objekty ve webové vrstvě vs. DAO vrstva apod. Neplatí to obecně pro všechny anotace, ale některé anotace vyjadřují určitou implementační závislost nebo předepisují dané třídě určité chování, které je platné jen pro určité použití. Konkrétně třeba @Transactional. Nebo nastavení zabezpečení objektů pomocí anotací, EJB anotace apod.

  • konfigurace aplikace nepatří do Java kódu - já sám mám raději několik konfiguračních souborů, kde najdu veškerou konfiguraci než to vše mít roztroušené po jednotlivých Java třídách. Na to zase většina řekne, že je super vidět u každé třídy hned dané vlastnosti (např. nastavení transakce, zabezpečení) než to hledat někde v separátních souborech. Dle mého názoru je lepší mít konfiguraci oddělenou od Java kódu a mít tak možnost nezávislého nastavení aplikace (nebo jejich částí) pro více prostředí.


Anotace mi přijdou jako velice flexibilní nástroj, ale i tak pořád spíše tíhnu ke klasickému vývoji bez anotací. Ale zase je potřeba se na vše dívat z různých pohledů, takže vlastně nelze obecně říci, co je lepší - takto dopadly i výsledky ankety :).

25. května 2009

JavaDoc nedostatky

V poslední době často pracuji s cizím kódem a je to opravdu zázrak narazit na kvalitně napsaný a okomentovaný kód. Největší problém komentářů je ten, že buď vůbec nejsou a nebo jsou, ale jen papouškují to, co je hned zřejmé ze samotného kódu. O přínosu komentování jsem již psal, dnes bych rád uvedl několik nedostatků v JavaDoc komentářích, na které jsem měl možnost narazit (malá poznámka pro upřesnění - i když často používám slovíčko komentáře, tak v tomto článku dále vždy myslím JavaDoc komentáře, ne komentáře přímo v kódu):

  • míchání češtiny (někdy i s diakritikou) a angličtiny - dříve jsem preferoval pouze angličtinu pro psaní komentářů, ta čeština mi tam prostě neseděla. Dnes jsem spíše pro používání češtiny z těchto důvodů: čeština vždy bude pro nás čitelnější než jakýkoliv jiný jazyk a i psaní komentářů je jednodušší, větší přínos z pohledu poskytované informace. Osobně s angličtinou nemám problém, ale sám jsem zjistil, že v češtině jsem schopen se vyjádřit o dost lépe. Dělám nyní většinou na interních projektech bez zahraničních kolegů, takže není problém s mateřským jazykem. Každopádně když už, tak zvolit jeden jazyk, aby to nevypadalo jako na jednom projektu, kde bylo možné narazit na české, slovenské, anglické a dokonce německé komentáře.

  • zvýraznění první věty komentáře - První věta komentáře má větší význam oproti dalším větám - jednak je to první informace, kterou uživatel čte a jednak je používaná pro stručný popis v přehledu metod. První věta (z pohledu JavaDoc nástroje) končí tečkou, kterou následuje mezera, tabulátor, znak konce řádku a nebo první blokový tag. Takto tedy ne:

    /**
    * <p>
    * If is set, it use special ScrollableResultsDecorator, that enable or
    * disable to add object in final list
    * </p>
    * <h4>NOTE:</h4>
    * <p>
    * Also, it respects the total count of entries that overlap your paged
    * list.
    * </p>
    */


  • @author tag u metod - používat @author tag u metod nemá žádný význam, protože to JavaDoc nástroj nepodporuje.

  • nepopisovat to, co za nás udělá JavaDoc automaticky - Je zbytečné kopírovat komentáře z metod rozhraní do implementace, je zbytečné uvádět implementující třídy rozhraní atd. JavaDoc nástroj má algoritmus na kopírování komentářů z nadřazených tříd a na rozdíl od verze 1.3 se vždy nekopíruje celý JavaDoc komentář (buď celý nebo nic), ale třeba i jen jeho části jako @param, @throws a @return. Někdo to sice nekopíruje, ale používá @inheritDoc a @see, ale to nejsou srovnatelné alternativy. Např. při použití @inheritDoc se kopíruje jen určitá část celého JavaDoc komentáře a tag @see má zcela odlišnou sémantiku. @inheritDoc je vhodné používat, pokud chci rozšířit určitou část dokumentace nadřazené třídy nebo metody.

  • zdokumentovat synchronized a native modifikátory - V JavaDoc API nenajdete informace o tom, zda je metoda synchronizovaná nebo ne (je to implementační záležitost). Proto je nutné tuto informaci uvést v JavaDoc.

  • zbytečně neopakovat význam tagů - Zde mám na mysli toto "@return Returns the statementBuilder." Proč psát 2x, že metoda vrací statementBuilder?

  • místo názvů tříd a metod používat odkazy @link - Hodně často se v komentářích odkazujeme na jiné třídy nebo metody a o dost větší přidaná hodnota bude, když si uživatel mezi jednotlivými komentáři může překlikávat, než aby to musel manuálně hledat. Proto je vhodné používat tagy @link resp. @linkplain.

  • používat @literal nebo @code pro psaní s <> - S příchodem Javy 5 a možností parametrizace může být problém zápisu těchto konstrukcí do JavaDoc komentářů. To stejné platí i o HTML a XML kódu. Než používat &lt; &gt; jako náhradu za <>, tak je lepší použít tagy @literal nebo @code. Tím se výrazně zvýší čitelnost JavaDoc i bez nutnosti zpracování JavaDoc nástrojem. Bohužel tyto tagy nelze použít na víceřádkové ukázky kódu, kde nám jde o přesné zarovnání kódu. Snad v příští verzi, pořád je tedy (bohužel) nutné používat tento způsob:

    * <p>Ukazka konfigurace ve web.xml:
    * <pre>
    &lt;listener&gt;
    &lt;listener-class&gt;com.o2bs.globals.web.common.envconfig.EnvAwareLog4jConfigListener&lt;/listener-class&gt;
    &lt;/listener&gt;
    * </pre>


  • psát přehledné komentáře i bez nutnosti použití JavaDoc nástroje - Hodně lidí hledí zejména na výslednou HTML podobu JavaDoc komentářů a ne na to, jak to vypadá přímo v Java kódu. Je pravda, že výsledná HTML podoba je asi nejdůležitější, ale dost často se prochází čistý Java kód bez IDE (a nebo i v IDE je rychlejší číst JavaDoc bez nutnosti si je zobrazovat v HTML podobě) a pak se i ocení, když to vypadá pěkně bez JavaDoc nástroje. Dle mého názoru se nemá cenu snažit o správně formátované HTML (tedy používání ukončovacích tagů), protože JavaDoc nástroj generuje HTML ve verzi 3.2 (i když hlavičku stránek dává pro HTML 4.0).


Psát správné (krátké, jasné s vypovídající hodnotou) JavaDoc komentáře není vůbec jednoduché a sám se občas nachytám, když se po nějakém čase vracím ke svému kódu. Pro ty, kdo chtějí JavaDoc dotáhnout k dokonalosti, doporučuji referenční příručku k JavaDoc nástroji a dokument s popisem konvence psaní komentářů.

20. května 2009

Spring security namespaces

Koncept "namespaců" resp. možnost vytváření vlastních konfiguračních XML tagů je ve Springu již od verze 2.0 a již je celkem hodně zajímavých tagů - ať už přímo ve Spring frameworku nebo v jiných Spring knihovnách nebo i v knihovnách třetích stran, např. DWR. Cíl je jasný - umožnit jednodušší (= rychlejší, přehlednější, jasnější, ...) konfiguraci Spring beanů.

Spring security přišel s podporou namespaců ve verzi 2.0. Sice moc namespaci nevyužívám (zvyk je železná košile), ale nedalo mi to, abych možnosti namespaců ve Spring security nevyzkoušel na jedné menší testovací aplikaci.

Začal jsem následující magickou ukázkou v dokumentaci:

<http auto-config='true'>
<intercept-url pattern="/**" access="ROLE_USER" />
</http>

<authentication-provider>
<user-service>
<user name="jimi" password="jimispassword" authorities="ROLE_USER, ROLE_ADMIN" />
<user name="bob" password="bobspassword" authorities="ROLE_USER" />
</user-service>
</authentication-provider>

A pak jsem to začal upravovat podle mojí "standardní" konfigurace Spring security. Většinu jsem dokázal pořešit pouze konfigurací na úrovní namespaců, ale ne vždy to bylo dostačující:
  • u LogoutFiltru jsem zvyklý používat vlastní LogoutHandler pro zalogování potřebných informací při odhlášení. Abych mohl použít vlastní handler, tak jsem si musel filter nakonfigurovat standardním způsobem s použitím <custom-filter position="LOGOUT_FILTER"/>

  • podobně u použití vlastního AccessDeniedHandleru. V rámci konfigurace <http> je již implicitní ExceptionTranslationFilter nastaven. Stejně jako u LogoutFiltru jsem musel provést konfiguraci filtru standardní cestou a uvést, že se jedná o vlastní filtr. Pozice vlastního filtru nesmí kolidovat z pozicí implicitního filtru, takže buď je potřeba dát implicitní filtr úplně pryč (neuvádět ho v <http>) a nebo umístit vlastní filtr před/za nějaký již existující, např. u ExceptionTranslationFilteru.

Takto jsem se dostal hodně daleko a konfigurace aplikace již vypadala skoro dle mých představ - uměla vše co jsem požadoval, místy byl zápis opravdu čitelnější a kratší. A i s použitím namespaců jsem mohl být flexibilní, snad všechny implicitní konfigurace jsem mohl nahradit svými, např. použít vlastní AccessDecisionManager

Jen jedna věc mi pořád vadila - při použití <http> se všechny uvedené filtry aplikují na všechny URL. Nemám tedy možnost říci, že na nějaké URL použiji nějaké filtry a na jiné URL zase jiné filtry. Použité filtry je např. vhodné odlišit pro dynamický a statický obsah. Z tohoto důvodu jsem opustil konfiguraci pomocí <http> a použil jsem standardní FilterChainProxy s <filter-chain-map>.

Na začátku jsem měl pár tagů a na konci jsem skončil skoro u "normálního" nastavení bez namespaců. I tak jsem byl velice mile překvapen, jak pěkně to mají navržené a jakou míru flexibility to má. Já Spring Security již celkem znám a i při použití namespaců vím, co se děje pod pokličkou, proto nevím, zda je tato zjednodušená forma konfigurace vhodná i pro začátečníky. Začátek s namespaci bude určitě rychlý, ale v každé aplikaci je potřeba něco nastavit jinak než standardně a pak bude problém.

Pro možnou inspiraci posílám moji výslednou konfiguraci:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.4.xsd"
>

<description>
Konfigurace Spring security.
</description>

<!--
Base filter chain of used filters. Filter's order is important,
more info here http://static.springframework.org/spring-security/site/reference/html/ns-config.html#ns-custom-filters.
-->
<bean id="springSecurityFilterChain" class="org.springframework.security.util.FilterChainProxy">
<security:filter-chain-map path-type="ant">
<security:filter-chain pattern="/img/**" filters="none" />
<security:filter-chain pattern="/css/**" filters="none" />
<security:filter-chain pattern="/js/**" filters="none" />
<security:filter-chain pattern="/accessdenied.*" filters="anonymousProcessingFilter" />
<security:filter-chain pattern="/error*.*" filters="anonymousProcessingFilter" />
<security:filter-chain pattern="/**"
filters="sessionContextIntegrationFilter, logoutFilter, userProfileStubFilter, authProcessingFilter,
userProfileAwareFilter, anonymousProcessingFilter, exceptionTranslationFilter,
filterInvocationInterceptor"
/>
</security:filter-chain-map>
</bean>


<!-- Musi byt, i kdyz neni mozne vyuzivat session. -->
<bean id="sessionContextIntegrationFilter"
class="org.springframework.security.context.HttpSessionContextIntegrationFilter">
<property name="allowSessionCreation" value="true"/>
</bean>

<bean id="logoutFilter" class="org.springframework.security.ui.logout.LogoutFilter">
<!-- URL redirected to after logout -->
<constructor-arg value="/"/>
<constructor-arg>
<list>
<bean class="com.o2bs.globals.web.springsecurity.utils.LoggingLogoutHandlerImpl"/>
<bean class="org.springframework.security.ui.logout.SecurityContextLogoutHandler"/>
</list>
</constructor-arg>
<property name="filterProcessesUrl" value="/j_spring_security_logout"/>
</bean>

<!-- "natvrdo" nastaveni prihlaseneho uzivatele (pouze pro vyvoj) -->
<bean id="userProfileStubFilter" class="com.o2bs.globals.web.springsecurity.utils.UserProfileStubFilter">
<constructor-arg value="123"/>
<constructor-arg value="bob"/>
<constructor-arg value="heslo"/>
<constructor-arg>
<list>
<value>ROLE_WRITE</value>
<value>ROLE_READ</value>
</list>
</constructor-arg>
</bean>

<!-- Processes an authentication form. -->
<bean id="authProcessingFilter"
class="com.o2bs.globals.web.springsecurity.auth.blocking.AuthenticationBlockingProcessingFilter">
<security:custom-filter after="AUTHENTICATION_PROCESSING_FILTER" />
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationFailureUrl" value="/login.do?login_error=1"/>
<property name="defaultTargetUrl" value="/"/>
<property name="authenticationBlockingManager" ref="authBlockingManager"/>
<property name="mandatoryRoles">
<list>
<value>ROLE_WRITE</value>
<value>ROLE_READ</value>
</list>
</property>
</bean>

<bean id="authBlockingManager"
class="com.o2bs.globals.web.springsecurity.auth.blocking.PeriodBlockingManagerMemoryImpl">
</bean>

<bean class="com.o2bs.globals.web.springsecurity.auth.blocking.AuthenticationFailureListener">
<property name="authenticationBlockingManager" ref="authBlockingManager"/>
</bean>

<bean id="userProfileAwareFilter"
class="com.o2bs.globals.web.springsecurity.utils.UserProfileHolderAwareRequestFilter">
</bean>

<!-- I need to use IS_AUTHENTICATED_ANONYMOUSLY - anonymous user has to be handled -->
<bean id="anonymousProcessingFilter"
class="org.springframework.security.providers.anonymous.AnonymousProcessingFilter">
<property name="key" value="anonymKey"/>
<property name="userAttribute" value="anonymousUser,ROLE_READ"/>
</bean>

<bean id="exceptionTranslationFilter" class="org.springframework.security.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint">
<bean class="org.springframework.security.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl" value="/login.do"/>
</bean>
</property>
<property name="accessDeniedHandler">
<!-- errorPage neni definovana, protoze se vyhazuje 403 a ta je namapovana ve web.xml -->
<bean class="com.o2bs.globals.web.springsecurity.utils.LoggingAccessDeniedHandlerImpl"/>
</property>
</bean>

<!-- protect web URIs -->
<bean id="filterInvocationInterceptor" class="org.springframework.security.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="objectDefinitionSource">
<security:filter-invocation-definition-source>
<security:intercept-url pattern="/create*" access="ROLE_WRITE" />
<security:intercept-url pattern="/**" access="ROLE_READ" />
</security:filter-invocation-definition-source>
</property>
</bean>





<!--
AffirmativeBased implementation will grant access if one or more ACCESS_GRANTED votes were
received (ie a deny vote will be ignored, provided there was at least one grant vote).
In other words principal must have corresponding ROLE and particular level of authentication.
-->
<bean id="accessDecisionManager" class="org.springframework.security.vote.AffirmativeBased">
<property name="allowIfAllAbstainDecisions" value="false"/>
<property name="decisionVoters">
<list>
<!-- class will vote if any ConfigAttribute begins with PERM_. -->
<bean class="org.springframework.security.vote.RoleVoter">
<property name="rolePrefix" value="ROLE_"/>
</bean>
<!-- allow attributes IS_AUTHENTICATED_FULLY or IS_..._REMEMBERED or IS_..._ANONYMOUSLY -->
<bean class="org.springframework.security.vote.AuthenticatedVoter"/>
</list>
</property>
</bean>

<!-- There is implicit authenticationManager when namespaces are used. -->
<security:authentication-manager alias="authenticationManager" />

<security:authentication-provider>
<security:user-service>
<security:user name="jimi" password="heslo" authorities="ROLE_READ"/>
<security:user name="bob" password="heslo" authorities="ROLE_READ, ROLE_WRITE"/>
</security:user-service>
</security:authentication-provider>

<security:global-method-security access-decision-manager-ref="accessDecisionManager">
<security:protect-pointcut
expression="execution(* com.o2bs.globals.example.competence.service.*.save*(..))"
access="ROLE_WRITE"/>
</security:global-method-security>

<bean class="com.o2bs.globals.web.springsecurity.utils.LoggingAuthenticationListener"/>

</beans>

13. května 2009

Jaké jiné zabezpečení místo Spring security?

Při přípravě školení o Spring security jsem se zamýšlel nad tím, jaké jiné způsoby zabezpečení aplikace jsou možné, když bych vynechal Spring security. Já osobně jsem vždy používal Spring security, proto mě samotného tato otázka trochu zaskočila.

Našel jsem (= vymyslel, vyhledal, znal) následující způsoby:

  • self-made řešení - pod tím si představuji taková řešení, kde využiji základních možností JEE, tedy filtrů a servletů, a na základě toho si vytvořím své vlastní filtry, kontroly. K tomu si dodělám přihlašovací formulář, napojím to na databázi a mám základní řešení hotové.

  • JEE servery - zabezpečení aplikace nechat na serverech.

  • Seam a Drools - framework Seam integruje knihovnu Drools a dohromady to vytváří pěkné řešení pro kompletní zabezpečení aplikace.

Jednotlivé způsoby lze vhodně kombinovat, např. použít servery pro autentifikaci uživatelů a autorizaci řešit pomocí Spring security.

První řešení mi není sympatické z toho důvodu, že nerad vytvářím něco, co již někdo jiný udělal (a většinou mnohem lépe). Snažím se držet zásady, že čím méně sám naprogramuji, tím lépe :).
Druhé řešení nemám rád z důvodu závislosti na aplikačním serveru, tedy omezené možnosti přenositelnosti a hlavně při každé instalaci to musím řešit znovu a znovu. Také nabízené možnosti zabezpečení (zejména co se týče autorizace) nejsou takové jako u jiných řešení.

Znáte prosím nějaké další způsoby zabezpečení aplikací?

11. května 2009

GUTs = good unit tests

K problematice testování jsem četl výborný článek (1, 2) na JavaWorld, který všem vřele doporučuji - nejen kvůli obsahu, ale i kvůli množství odkazů na další články a zajímavé knihovny.

K obvyklým a častým "best-practices" (např. JUnit best practices) bych ještě přidal z mých zkušeností následující:

  • testovací kód by měl splňovat stejné kvalitativní nároky jako produkční kód. Místo jedné dlouhé testovací metody, která navíc ještě obsahuje duplicitní kód střídající se v jedn. testovacích metodách, je vhodné společný kód vyčlenit do separatních metod nebo i tříd, aplikovat pravidla refactoringu, když už kód "začne smrdět" (1, 2).
  • testy jsou ukázkou použití produkčního kódu, testy jsou součástí popisu API aplikace.
  • jednotlivé testy musí být na sobě nezávislé, pořadí spouštění testů může být libovolné
  • otestovat lze (skoro) všechno, jen je potřeba najít rovnováhu mezi náročností napsání testů a jejich přidanou hodnotou. Nesnažit se tedy za každou cenu dosáhnout 100% pokrytí produkčního kódu testy, ale spíše se držet pravidla 20/80, kdy otestováním 20% kódu otestujeme 80% funkcionality.
  • testy nesmí mít žádné vedlejší efekty, např. přidaný záznam v DB.
  • při psaní testů se nespoléhat na konkrétní prostředí (Locale, adresář na lokálním disku apod.), protože testy se mohou spouštět kdekoliv.

Co mě ale v uvedeném článku nejvíce zaujalo byla knihovna hamcrest. Při psaní testů jsem měl pořád problémy se psaním smysluplných komentářů do assertů a navíc mi ty všechny kontroly nepřisly úplně přehledné, když toho bylo více. Toto vše řeší hamcrest díky jednoduchému a výstižnému API - na ukázky a možnosti se podívejte do tutorialu nebo do uvedeného článku.