Saiba um pouco mais sobre os proxies do Spring

Outro dia no grupo de discussões sobre o livro de Spring MVC apareceu uma dúvida que tenho uma impressão que é um tanto recorrente. O leitor explicou mais ou menos assim: “Tenho a classe UserDAO que implementa a interface UserDetailsService. Possuo um ponto de injeção onde declaro um atributo do tipo UserDAO e marco o mesmo com @Autowired. Quando subo a minha aplicação, recebo uma exception”. Pensando no código, a situação é a que está descrita abaixo.

	@Repository
	public class UserDAO implements UserDetailsService {

		@PersistenceContext
		private EntityManager em;

		@Override
		public UserDetails loadUserByUsername(String username) 
			throws UsernameNotFoundException {
			//implementacao
		}
	}
	@EnableWebSecurity
	@Configuration
	public class SecurityConfiguration extends WebSecurityConfigurerAdapter{

		@Override
		protected void configure(HttpSecurity http) throws Exception {
			//implementacao
		}	
		
                //injeção problemática
		@Autowired
		private UserDAO users;
		
		@Override
		protected void configure(AuthenticationManagerBuilder auth)
				throws Exception {
			//implementacao
		}
		
		
	}

A exception lançada é a seguinte:

Caused by: java.lang.IllegalArgumentException: Can not set br.com.casadocodigo.loja.daos.UserDAO field br.com.casadocodigo.loja.conf.SecurityConfiguration.users to com.sun.proxy.$Proxy49

Em uma primeira olhada talvez não faça muito sentido. Como não podemos injetar um objeto cuja classe é a mesma do ponto de injeção? Aí você começa a pesquisar e descobre que se trocar pela interface a injeção funciona corretamente.

	@Autowired
	private UserDetailsService users;

Para que isso fique mais claro, é necessário entender como que o Spring instância o seu objeto quando a classe dele implementa alguma interface.

	UserDAO delegate = new UserDAO();
	InvocationHandler handler = new InvocationHandler() {

		@Override
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			System.out.println("interceptando a invocação do metodo " + method.getName());
			return method.invoke(delegate, args);
		}
	};

        ClassLoader classLoader = //pega um classLoader;
	UserDAO instance = (UserDAO) Proxy.newProxyInstance(
			classLoader, 
			new Class[] { UserDetailsService.class }, handler);

	//invocando o método como se fosse no objeto real		
	instance.loadUserByUsername("alberto");	

A classe Proxy do pacote java.lang.reflect.Proxy é a classe padrão do Java quando necessitamos criar um objeto que segue esse design pattern. Sempre que você recebe algum objeto injetado em seu projeto com Spring, na verdade está recebendo um Proxy. O que vai causar a exception é justamente a maneira como o Proxy está sendo criado. Por exemplo, no código acima, caso tentemos executá-lo será lançada a seguinte exception.

Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to br.com.casadocodigo.loja.daos.UserDAO

Parecida com a que foi lançada quando você subiu o seu projeto :). Por default, quando implementamos alguma interface, o Spring vai criar o Proxy usando o mecanismo padrão do Java. Só para tentar deixar claro. Basicamente a implementação de um Proxy exige que uma classe seja construída em tempo de execução, herdando da classe base ou/e implementando as interfaces dessa mesma classe base. O mecanismo padrão do Java só fornece a segunda opção. Essa é a razão da exception ser lançada, não temos uma filha de UserDAO, e sim uma implementação da interface UserDetailsService.

Caso você realmente queira receber a injeção pela classe concreta, você pode configurar o Spring para criar o Proxy em função da classe concreta.

	@Repository
	@Scope(value=ConfigurableBeanFactory.SCOPE_SINGLETON,
		proxyMode=ScopedProxyMode.TARGET_CLASS)
	public class UserDAO implements UserDetailsService {
                 ...
        }

Para possibilitar esse estilo de criação de Proxy, o Spring recorre a uma biblioteca chamada cglib. Para não ficarmos curiosos, abaixo segue um exemplo de código de criação de Proxy usando o cglib.

	Enhancer enhancer = new Enhancer();
	enhancer.setSuperclass(UserDAO.class);
	UserDAO delegate = new UserDAO();
	org.springframework.cglib.proxy.InvocationHandler handler = new org.springframework.cglib.proxy.InvocationHandler() {

		@Override
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			System.out.println("interceptando a invocação do metodo " + method.getName());
			return method.invoke(delegate, args);
		}
	};
	enhancer.setCallback(handler);
	UserDAO proxy = (UserDAO) enhancer.create();
	proxy.loadUserByUsername("alberto");

A ideia desse post foi desmistificar a maneira como seus objetos são criados e injetados dentro do Spring. Em geral, toda exception faz bastante sentido. Precisamos ficar atentos e tentar sempre procurar o real motivo dela. Dessa forma nossa produtividade só cresce em cada novo projeto que pegamos, já que vamos passando cada vez menos por erros de infraestrutura.

Advertisements

3 thoughts on “Saiba um pouco mais sobre os proxies do Spring

  1. Boa Alberto!

    Se você sempre trabalha com interfaces este problema não ocorre. Creio que só tive esse problema durante os testes de integração com Spring, mas havia resolvido com a injeção via interface mesmo.

    Outro problema comum e até chatinho é que quando não usamos interfaces e o Spring precisa aplicar interceptors (AOP) nos métodos da classe somos obrigados a ter o construtor default, exatamente porque o Spring ao usar CGLIB precisa instanciar a classe manualmente como você demonstrou. Isso também acontece com CDI e Weld.

    Uma dúvida, acho que o exemplo da Java API está incorreto, não? Pois o retorno da Proxy.newProxyInstance() seria do tipo da interface, não?

    Um abraço!

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s