Você está na página 1de 103

INTRODUCTION TO THE SPRING FRAMEWORK

Spring is a lightweight inversion of control and aspect oriented container framework Spring makes developing J2EE application easier and more fun!!! Introduced by Rod Johnson in J2EE Design & Development

Lightweight J2EE Architecture

Overview

Review current state of J2EE Lightweight Frameworks and what they can do Todays Frameworks
Spring Hibernate Spring and Hibernate Integration

Demos along the way Review

Current J2EE State


J2EE has done a great deal to standardize our industry These J2EE services are tremendously successful with wide adoption: JDBC Servlet/JSP/WAR JNDI JMS

Useful EJB Services


Declarative transactions support Persistence Security Distributed component model Instance pooling and caching

Genesis of EJB

1998 Distributed Computing was the big buzz-word Sun made EJBs remote to compete with technologies like CORBA The specification was intrusive And we have been *dealing* with these decisions ever since Lets take a look at the Sun Best practices.

EJB Issues

Intrusive Noisy

Must be an EJBObject Must run within the container

Rapid iterative code/debug/test cycles difficult


If the Sun Pet Store is the best, were in trouble Too many different patterns Performs poorly Reliance on Entity Beans Too many different files needed to do simple things

Treating the Symptoms

Tools exist to make EJB development easier

XDoclet IDE Wizards Cactus


Data Transfer Objects (DTOs) Service Locator

Work-a-rounds

The Way Ahead

Development teams need a better frameworks that supports these ideas:


Implementing business requirements first Simplicity Productivity Testability Object Orientation

Lightweight Frameworks can get us there

What are Lightweight Frameworks?

Non-intrusive No container requirements Simplify Application Development


Remove re-occurring pattern code Productivity friendly Unit test friendly

Very Pluggable Usually Open Source

Leading Lightweight Frameworks

Spring, Pico, Hivemind Hibernate, IBatis, Castor WebWork Quartz Sitemesh JBoss is NOT a lightweight framework

Spring

Spring is in the Air

Spring is a glue framework that ties many frameworks together in a consistent way Inversion of Control Framework (Dependency Injection)

Dont call us well call you Service Injection rather than Service Lookup Encourages programming to Interfaces Reduces coupling between components Supports Testing

Configuration

Supports externalizing the configuration

And more

Spring LifeCycle

The Bean Container finds the definition of the Spring Bean in the Configuration file. The Bean Container creates an instance of the Bean using Java Reflection API. If any properties are mentioned, then they are also applied. If the property itself is a Bean, then it is resolved and set. If the Bean class implements the BeanNameAware interface, then the setBeanName() method will be called by passing the name of the Bean. If the Bean class implements the BeanFactoryAware interface, then the method setBeanFactory() will be called by passing an instance of BeanFactory object. If there are any BeanPostProcessors object associated with the BeanFactory that loaded the Bean, then the method postProcessBeforeInitialization() will be called even before the properties for the Bean are set.

Spring LifeCycle

If the Bean class implements the InitializingBean interface, then the method afterPropertiesSet() will be called once all the Bean properties defined in the Configuration file are set. If the Bean definition in the Configuration file contains a 'init-method' attribute, then the value for the attribute will be resolved to a method name in the Bean class and that method will be called. The postProcessAfterInitialization() method will be called if there are any Bean Post Processors attached for the Bean Factory object. If the Bean class implements the DisposableBean interface, then the method destroy() will be called when the Application no longer needs the bean reference. If the Bean definition in the Configuration file contains a 'destroymethod' attribute, then the corresponding method definition in the Bean class will be called.

Spring Modules

Core IoC container Application context module AOP module JDBC abstraction and DAO module Object/relational mapping integration module Web module MVC module Quartz, Email, WebServices Integration and more.

Why use IoC?

IoC Types

Three basic types

Type I

Interface Injection Your service or component implement a specific interface Much like EJBs Method Injection Dependent objects are provided to the object by methods Based on JavaBeans constructs Ordering of method calls may not be achievable Constructor Injection Dependent objects are provided to the object by constructors Large constructor argument lists

Type II

Type III

AOP Introduction

AOP Aspect Oriented Programming

Separates concerns of an object into their own aspects Helps to remove infrastructure code from the application

AOP can remove replicated code that is scattered across an entire system Used to apply cross-cutting concerns uniformly to objects.

Logging Security Transaction Management

AOP Crosscutting

Without AOP

With AOP

AOP Definitions

Aspect - An application wide concern that is often duplicated in many classes, that could be implemented before, after, or upon failure across a group of methods. JoinPoint - A join of methods across which an aspect can be implemented. Spring allows method interception. This means when a method is called the framework can attach functionality. Pointcut - Description of the collection of methods for which advice is to be applied Introduction - Adding methods or properties to a class with advice. AOP Proxies - Surrogate object that invokes advice and advises the invoked class

AOP Example

Spring AOP Code Example

Spring Tips and Tricks


Start with just the IoC and build from there Be careful with the autowire changes in Interfaces and refactoring can change autowire rules

AOP supports proxying of classes via interface and via CGLib


Interfaces are recommended to reduce coupling CGLib is available to support legacy code or 3rd party code

Methods marked as final can not be advised Dynamic Pointcuts (ControlFlowPointcuts) can be slow so try to avoid them Avoid applying Advise to fine grained objects Use autoproxing when you want system wide

Spring Hibernate Integration

Spring Hibernate Integration

HibernateTemplate

Many convenience methods Use is restricted to unchecked exceptions Uses anonymous inner class callbacks
Any exception types can be thrown within the data access code DAO must extend HibernateDAOSupport Very clean DAO methods

Hibernate AOP

Spring Transactions

Clear application layering supporting most data access and transaction technologies Not bound to a container Transactions can either be managed programmatically or declarative using AOP TransactionTemplate

Allows execution of transactional code without the need to re-implement transaction workflows (Exception Handling) Triggers a rollback if the callback throws a runtime exception or if it sets the transaction to rollback-only Business object does not need to be transaction aware (similar to EJB) Check and unchecked exceptions supported Default rollback behavior is to rollback on runtime exceptions but this can be configured

TransactionInterceptor

Spring helps J2EE developers by:


Offering a lightweight JavaBean container that eliminates the need to write repetitive plumbing code such as lookups Providing an inversion of control framework that allows bean dependencies to be automatically resolved upon object instantiation Allowing cross cutting concerns such as transaction management to be woven into beans as aspects rather than becoming the concern of the business object Offering layers of abstraction on top of popular existing technologies such as JDBC and Hibernate that ease their use and organize configuration management

while adhering to certain principals:


Your application code should not depend on Spring APIs Spring should not compete with good existing solutions, but should foster integration Writing testable code is critical and the container should help not interfere with this objective Spring should be a pleasure to use

Spring Architecture

INVERSION OF CONTROL AND AOP CONCEPTS


What is it? A way of sorting out dependencies between objects and automatically injecting references to collaborating objects on demand Who determines how the objects should be injected? The IoC framework, usually via XML configuration files Benefits Removes the responsibility of finding or creating dependent objects and moves it into configuration Reduces coupling between implementation objects and encourages interface based design Allows an application to be reconfigured outside of code Can encourage writing testable components

Traditional way of obtaining references.


private AccountService accountService = null; public void execute(HttpServletRequest req, .) throws Exception { Account account = createAccount(req); AccountService service = getAccountService(); service.updateAccount(account); } private AccountService getAccountService() throws { if (accountService == null) { Context ctx = new InitialContext(); Context env = (Context) ctx.lookup(java:comp/env); Object obj = env.lookup(ejb/AccountServiceHome); AccountServiceHome home = (AccountServiceHome) PortableRemoteObject.narrow(env, AccountService.class); accountService = home.create(); } return accountService; }

With Spring IoC the container will handle the injection of an appropriate implementation

private AccountService accountService = null; public void setAccountService(AccountService accountService) { this.accountService = accountService; } public void execute(HttpServletRequest req, .) throws Exception { Account account = createAccount(req); accountService.updateAccount(account); }

AOP Concepts

Applications must be concerned with things like: Transaction management Logging Security Do these responsibilities belong to the implementation classes? Should our Service class be responsible for transaction management, logging, or security? These concerns are often referred to as crosscutting concerns

AOP Concepts

Separate these concerns and define them as an advice Before Advice After Advice Around Advice Throws Advice Declaratively weave them into the application based on certain criteria (pointcuts). Proxies then intercept relevant methods and silently introduce the advice

Types of AOP:
Static AOP Aspects are typically introduced in the byte code during the build process or via a custom class loader at runtime AspectJ (byte code manipulation at compile time) JBoss 4, AspectWerkz (Class loader approach) Dynamic AOP Create proxies for all advised objects Slightly poorer performance Easier to configure

AOP Improvements

Simplified AOP XML Configuration AspectJ Pointcut Language @AspectJ Aspects Dependency Injection of Domain Object

Spring and AOP


AOP is a enabling technology for proper design Spring leverages AOP in many support classes Transactions Remoting Uses a proxy based setup that enables method call interception Spring 1.2.x has own pointcut system but could support AspectJ if needed

Spring and AOP


Raw AOP was used but not as widely as it could have been Springs proxy based system made AOP more approachable than AspectJ However, creating advice was very verbose Pointcut language wasnt standard Spring 2.0 makes AOP simpler and more powerful

Simplified AOP Configuration


Spring 1.2.x <bean id="exampleAspect" class="...aop.support.NameMatchMethodPointcutAdvisor"> <property name="advice" value="exampleAdvice" /> <property name="mappedNames"> <list> <value>setMessage1</value> <value>setMessage2</value> </list> </property> </bean>

Simplified AOP Configuration Spring 2.0


<bean id=exampleAdvice class=ExampleAdvice> <aop:config> <aop:aspect id="exampleAspect" ref="exampleAdvice"> <aop:before method="exampleMethod" pointcut="execution(* void set*(*))" /> </aop:aspect> </aop:config>

AspectJ Pointcut Language


Spring AOP now supports pointcuts based on AspectJs pointcut language Only supports a subset of the language That which can be intercepted via proxy Typically anything with execution() Uses AspectJs interpreter No chance of Spring introduced bugs Preferred method of writing pointcuts going forward

AspectJ Pointcut Language

execution(public !void get*()) execution(public void set*(*)) execution(void Point.setX(int)) execution(public !static * *(..))

@AspectJ Aspects
A method provided by AspectJ that allows the use of annotations to describe aspects and pointcuts Enables the ability to compile an aspect with a simple Java compiler and then be woven by the AspectJ weaver at a later time Subsequent build step Class load-time

@AspectJ Aspects

@Aspect public class AjLoggingAspect { @Pointcut("execution(* *..Account.*(..))") public void callsToAccount() { } @Before("callsToAccount()") public void before(JoinPoint jp) { System.out.println("Before [" + jp.toShortString() + "]."); } @AfterReturning("callsToAccount()") public void after() { System.out.println("After."); } }

THE SPRING BEAN CONTAINER


Uses IoC to manage components that make up an application Components are expressed as regular Java Beans Container manages the relationships between beans and is responsible for configuring them Manages the lifecycle of the beans

Types of Bean Containers:


Bean Factory Provides basic support for dependency injection Configuration and lifecycle management Application Context Builds on Bean Factory and adds services for: Resolving messages from properties files for internationalization, Loading generic resources Publishing events

Loading a Bean Factory:


public interface Greeting { String getGreeting(); } public class WelcomeGreeting implements Greeting { private String message; public void setMessage(String message) { this.message = message; } public String getGreeting() { return message; } }

Loading a Bean Factory (cont):


<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id=greeting class=WelcomeGreeting> <property name=message> <value>Welcome</value> </property> </bean> </beans>

Features of Managed Beans:


Default to Singletons Properties can be set via dependency injection References to other managed beans Strings Primitive types Collections (lists, set, map, props) Inner Beans Autowiring Parameters may be externalized to property files

SPRING IN THE BUSINESS TIER


A layer of abstraction for persisting data via one or more of the following technologies JDBC Hibernate, OJB JDO iBatis A robust infrastructure for declarative transaction management that supports local transactions as well as global transactions via JTA A simplifying layer for remoting technologies including EJB, RMI, Hession/Burlap, and Web Services Useful support for JNDI, JMS, email, task scheduling

Spring Persistence Support

A technology agnostic data access API that helps isolate and streamline the way data is served by the business tier A consistent and rich exception hierarchy that is smart enough to map technology specific exceptions to generalized exceptions A series of template and wrapper classes for working with JDBC, Hibernate, JDO, etc.

Traditional JDBC coding techniques:


public void updateCustomer(Customer customer) { Connection conn = null; PreparedStatement ps = null; try { conn = getConnection(); ps = conn.prepareStatement(update customer set + firstName = ?, lastName = ?, ); ps.setString(1, customer.getFirstName()); ps.setString(2, customer.getLastName()); ps.executeUpdate(); } catch SQLException e) { log.error(e); } finally { try { if (ps != null) ps.close(); } catch (SQLException e) {log.error(e);} try {if (conn != null) conn.close();} catch (SQLException e) {log.error(e);} } } private Connection getConnection() { more plumbing code here }

Using Spring JDBC template:


public void updateCustomer(Customer customer) { String sql = update customer set + firstName = ?, lastName = ?, ); Object[] params = new Object[] { customer.getFirstName(), customer.getLastName(), }; int[] types = new int[] { Types.VARCHAR, Types.VARCHAR, }; jdbcTemplate.update(sql, params, types); } jdbcTemplate can be injected by container

Operations can also be modeled as objects:


public class UpdateCustomer extends SqlUpdate { public UpdateCustomer(DataSource ds) { setDataSource(ds); setSql(update customer set values (?,?..)); declareParameter(new SqlParameter(Types.VARCHAR)); declareParameter(new SqlParameter(Types.VARCHAR)); compile(); } public int update(Customer customer) { Object[] params = new Object[] { customer.getFirstName(), customer.getLastName() }; return update(params); } }

Hibernate Support :
configuration and session management for business objects A HibernateTemplate class is provided to reduce need for boilerplate code HibernateDaoSupport class that can be extended for further abstraction HibernateException management and mapping Seamlessly plugs into Spring transaction framework

Spring Transaction Support


Programmatic Transaction Management Not as popular as declarative approach When finegrained control is required Similar to manually controlling JTA UserTransactions Available via the Spring TransactionTemplate class Reference typically passed to the business object via DI Transaction template holds a property of type PlatformTransactionManager PlatformTransactionManager can be an implementation of Hibernate, JDO, JDBC, or JTA transaction manager

Spring Transaction Support


Declarative Transaction Management Uses AOP to wrap calls to transactional objects with code to begin and commit transactions Transaction propagation behavior Mandatory, Never, Not Supported, Required, Requires New, Support, Nested Similar to EJB style behaviors Also supports isolation levels Default, Read Uncommitted, Read Committed, Repeatable Read, Serializable

Spring Remoting
RMI Provides a RmiProxyFactoryBean that can be configured to connect to an RMI service Provides a RmiServiceExporter that can be configured to expose any Spring managed bean as an RMI service Caucho (Hessian/Burlap) Provides a Hession(Burlap)ProxyFactoryBean and ServiceExporter to connect to and expose Caucho remote services respectively Also requires configuration of web application context and mapping of appropriate servlet url Spring Http Invoker Similar to Caucho except using nonproprietary object serialization techniques

Spring Remoting
EJB Spring allows EJBs to be declared as beans within the Spring configuration file making it possible to inject references to them in other beans Spring provides the ability to write EJBs that wrap Spring configured Java Beans Web Services via JAXRPC Client side support via the JaxRpcPortProxyFactoryBean ServletEndpointSupport class allows services that sit behind a servlet to access the Spring Application Context

Accessing EJBs in Spring Apps:


LocalStatelessSessionProxyFactoryBean Proxy for local SLSB Finds and caches the EJB Home upon first access Intercepts method calls and invokes corresponding method on ejb SimpleRemoteStatelessSessionProxyFactoryBean Proxy for remote SLSB Will also catch Remote Exceptions and rethrow as unchecked exceptions Stateful session beans are NOT proxied Simplified lookup via Spring JndiObjectFactoryBean

Spring J2EE
JNDI Lookup Moves verbose lookup code into the framework Sending Email Template classes that support COS mail Java Mail Event Scheduling Support JDK Timer OpenSymphony Quartz events

Agenda

Introduction to Spring MVC Overview of architecture Overview and examples of individual MVC components In depth examples of forms processing and validation Miscellaneous topics

What is Spring MVC?

Request-response based web application framework, such as Struts and WebWork Flexible and extensible via components interfaces Simplifies testing through Dependency Injection Simplifies form handling through its parameter binding, validation and error handling Abstracts view technology (JSP, Velocity, FreeMarker Excel , PDF)

Spring MVC Architecture

DispatcherServlet

DispatcherServlet

Example of Front Controller pattern - entry point for all Spring MVC requests Loads WebApplicationContext and well are parent contexts Controls workflow and mediates between MVC components Loads sensible default components if none are configured

Configuring the DispatcherServlet


web.xml

<servlet> <servlet-name>dispatcher</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>*.htm</url-pattern> </servlet-mapping>

Configuring the DispatcherServlet (contd)


web.xml

<servlet> <servlet-name>dispatcher</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>WEB-INF/mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>

Configuring the DispatcherServlet (contd)


web.xml

<context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/services.xml</param-value> </context-param>

configures parent context


<listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener>

HandlerMapping

HandlerMapping

Maps incoming requests to a corresponding Handler, typically a Controller Rarely needs to be implemented directly many useful implementations are provided Also ties Interceptors to a mapped Controllers Can provided multiple HandlerMappings; priority is set using Ordered interface

BeanNameUrlHandlerMapping

Maps a URL to a bean registered with the same name e.g. /simple.htm maps to a bean named /simple.htm Can give bean multiple names (aliases) separated by spaces Must use name attribute / is not allowed in XML id attribute Can use wild card in bean names (/simple*) Default HandlerMapping, but not preferred

BeanNameUrlHandlerMapping
dispatcher-servlet.xml
<bean class="org.springframework.web.servlet. handler.BeanNameUrlHandlerMapping"/>

<bean name="/simple.htm /simpleSimon.htm" class="com.twoqubed.mvc.web.SimpleController"> </bean>

SimpleUrlHandlerMapping

Most common way to map request URLs to handlers Configured by a list of name/value pairs consisting of URLs and bean names Can use wild card in bean names (/simple*)

SimpleUrlHandlerMapping
dispatcher-servlet.xml

<bean class="org.springframework.web.servlet. handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key=/simple.htm>simpleController</prop> <prop key=/test*>testController </prop> </props> </property> </bean>

ControllerClassNameHandlerMapping
Part of Spring 2.0's Convention over Configuration Maps a URL to the shortened class name of a Controller bean

Removed "Controller" from class name Converts to all lower case Prepend and "/" and append a "*" SimpleController -> /simple*

Example:

Greatly reduces amount of mapping configurations

ControllerClassNameHandlerMapping

dispatcher-servlet.xml

<bean class="org.springframework.web.servlet.mvc. support.ControllerClassNameHandlerMapping" />

Controllers

Controller interface

Handles the processing of the request Interface parameters mimics HttpServlet

handleRequest(HttpServletRequest, HttpServletResponse)

Returns a ModelAndView object


Implementations are typically thread-safe Rarely implemented directly Spring provides many useful implementations

ModelAndView object

Encapsulates both model and view that is to be used to render model Model is represented as a java.util.Map Objects can be added to without name:
addObject(String, Object) added with explicit name addObject(Object) added using name generation (Convention over Configuration) View is represented by String or View object

Analogous to Struts Action

Controller implementations

Typical Controllers in your application will:

Be completely configured via wiring (no code) Contain simple web processing Handle web layer and defer to service layer for additional processing

Parameter handling View determination Input validation

AbstractController

Provides minimal behavior Used for handling simple requests

protected ModelAndView handleRequestInternal( HttpServletRequest request, HttpServletResponse response) { String text = service.getText(); return new ModelAndView( "simple", "text", text); }

AbstractController

Provides minimal behavior Used for handling simple requests

protected ModelAndView handleRequestInternal( HttpServletRequest request, HttpServletResponse response) { String text = service.getText(); return new ModelAndView( "simple", "text", text); }

ThrowawayController

Not part of the Controller hierarchy

Parameters are mapped directly onto the controller Useful when there is not model object to map to Must scope bean as prototype since these are inherently not thread-safe

ThrowawayController
dispatcher-servlet.xml

<bean id="exampleThrowawayController" class="com.twoqubed.mvc.web. ExampleThrowawayController" scope="prototype" />


configured as prototype bean

ThrowawayController
public class ExampleThrowawayController implements ThrowawayController {

private String message;


public void setMessage(String message) { this.message = message; } public ModelAndView execute() throws Exception { String hashCodeMessage = "[" + hashCode() + "] - " + message;

return new ModelAndView("throwaway", "message", hashCodeMessage);


} }

Command Controllers

Family of Controllers that bind request parameters to command objects Command object can be any POJO typically a domain object Provides functionality:

Binding custom types Automatic and custom validation Automatically or programmatically creating command object

We will cover in more detail later

Command Controllers

AbstractCommandController Provides binding and validation SimpleFormController In addition to binding and validation, provides rich work flow for processing forms

Most useful for processing form Detailed example to come later

AbstractWizardFormController For forms that span multiple pages

Additional Controllers

ServletWrappingController and ServletForwardingController specialty Controllers to wrap Struts servlet in Spring interceptors ParameterizableViewController simply forwards to a configured view without exposing view technology to client UrlFilenameViewController converts a URL to a view name

Interceptors

Interceptors

Add additional functionality before and after request Contains to interception methods preHandle and postHandle

Contains one callback method afterCompletetion Associated with a set of Controllers via a HandlerMapping

Interceptor implementations

Implement either HandlerInterceptor or WebRequestInterceptor Spring provides a few implementations OvenXxxInViewInteceptor Used with
ORM frameworks JDO, JPA and Hibernate

UserRoleAuthorizationInterceptor
Provides authorization against a set of roles

Other useful customizations: custom security, caching,

ViewResolver

ViewResolver

Resolves a logical view name to a View object Orderable, so they be chained For JSP user, typical implementation is InternalResourceViewResolver:

<bean id="internalResourceViewResolver" <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean>

Other ViewResolver Implementations

VelocityViewResolver Convenience class for Velocity templates FreeMarkerViewResolver Convenience class for FreeMarker templates ResourceBundleViewResolver

Mappings are contained within a properties file Supports internationalization

XmlViewResolver - Mappings are contained


with an XML file

View

View

Contains implementations for several template technologies:


InternalResourceView (JSP) JstlView (JSP + JSTL) VelocityView (Velocity) FreeMarkerView (FreeMarker) TilesView (Tiles) TilesJstlView (Tiles + JSTL)

View

Also contains views that support rendering Excel files PDF files XSLT results Jasper Reports

Processing forms with Spring MVC

Leveraging workflow provided by SimpleFormController

Provides both form display and processing work flow with custom hooks Be default, GET indicates form displaying and POST indicates form processing

Displaying and processing done by same Controller Complete workflow is quite involved we will only hit the highlights

Registering the Command class

SimpleFormControllers are associated with a Command class

Since these are tightly coupled, configuring with the Controller class is acceptable

public class PlayerFormController extends SimpleFormController { public PlayerFormController() { setCommandClass(Player.class); setCommandName("player"); }

Displaying a form

We will discuss three methods in the work flow for displaying the form

formBackingObject Returns the command object used in the form. initBinder Registered custom PropertyEditors for rendering command properties referenceData Loads additional data to be displayed on page

Processing a form

Two main methods for form processing are:

onBindAndValidate() Allows for custom binding and validation doSubmitAction() Callback to handle successful form submission. Typical implementation would be to persist command object to database.

Spring MVC

Other Spring MVC functionality Transparent handling of Multipart requests

Support for customized Look & Feels through Themes Support for internalization Convenience ServletContextListener for initializing Log4J

Você também pode gostar