O encapsulamento de código possui várias características interessantes para o desenvolvimento de código, como proibir o acesso direto aos atributos da classe evitando assim a atribuição de valores inválidos, permitir verificações e modificações através de métodos acessores e modificadores, diminuir o acoplamento, entre outras. Por exemplo um objeto Endereco não pode ter o atributo numeroRua menor ou igual a zero, por isso, podemos colocar essa verificação em um método modificador, não permitindo valores inválidos. Contudo, em alguns casos o acesso à atributos e métodos privados se faz necessário. Por exemplo, quando achamos um bug em uma biblioteca de terceiros e não temos o código para modificar. Se via depuração de código identificamos que precisamos acessar um método ou modificar um atributo privado para realizar um workaround, é necessário acessar esses atributos. Ou, em outro caso, precisamos saber todos os atributos que são serializáveis de uma determinada classe, incluindo os atributos privados.
Por esses motivos, existe uma maneira de violar o encapsulamento. Atenção: GAMBIARRA WARNING! :D. Utilize somente esse recurso, caso seja realmente necessário, pois por permitir acesso à atributos e métodos privados, temos acesso à coisas proibidas e que não deveriam ser acessadas.
Para demonstrar essa funcionalidade, utilizaremos a seguinte classe:
import java.util.UUID; public class Produto { private String idInterno; private int sku; private String nome; private double valor; public int getSku() { return sku; } public void setSku(int sku) { this.sku = sku; this.idInterno = generateIdInterno(sku); } public String getNome() { return nome; } public void setNome(String nome) { this.nome = nome; } public double getValor() { return valor; } public void setValor(double valor) { if (valor > 0) { this.valor = valor; } } private String generateIdInterno(int sku) { return UUID.randomUUID() + "-" + sku; } public String getIdInterno() { return idInterno; } @Override public String toString() { return "Produto [idInterno=" + idInterno + ", sku=" + sku + ", nome=" + nome + ", valor=" + valor + "]"; } }
Verifque na linha 31 que o valor só pode ser um double positivo e diferente de zero, pois o atributo é privado (linha 07) e só é modificado através do setter (linha 30). Verifique também que o método generateIdInterno (linha 36) é privado e somente a própria classe tem acesso (linha 15). Para termos acesso a esses membros privados, criei uma classe utilitária que facilita o trabalho com reflection do Java.
import java.lang.reflect.Field; import java.lang.reflect.Method; public class ReflectionUtil { private Object object; public ReflectionUtil(Object obj) { this.object = obj; } public Field[] getAllFields() { return this.object.getClass().getDeclaredFields(); } public Method[] getAllMethods() { return this.object.getClass().getDeclaredMethods(); } private Field getFieldByName(String fieldName) { Field field = null; try { field = this.object.getClass().getDeclaredField(fieldName); } catch (Exception e) { e.printStackTrace(); } return field; } private Method getMethodByName(String methodName, Class<?>... parameterTypes) { Method method = null; try { method = this.object.getClass().getDeclaredMethod(methodName, parameterTypes); } catch (Exception e) { e.printStackTrace(); } return method; } public Object getValue(String fieldName) { return getValue(getFieldByName(fieldName)); } public Object getValue(Field field) { try { if (field != null && this.object != null) { if (!field.isAccessible()) { field.setAccessible(true); } return field.get(this.object); } } catch (Exception e) { e.printStackTrace(); } return null; } public void setValue(String fieldName, Object value) { setValue(getFieldByName(fieldName), value); } public void setValue(Field field, Object value) { try { if (field != null && this.object != null) { if (!field.isAccessible()) { field.setAccessible(true); } field.set(this.object, value); } } catch (Exception e) { e.printStackTrace(); } } public Object invokeMethodByName(String methodName, Object... params) { int paramsSize = params.length; Class<?>[] paramsTypes = new Class<?>[paramsSize]; for (int idx = 0; idx < paramsSize; idx++) { paramsTypes[idx] = params[idx].getClass(); } return invokeMethod( getMethodByName(methodName, paramsTypes), params); } public Object invokeMethod(Method method, Object... params) { try { if (method != null && this.object != null) { if (!method.isAccessible()) { method.setAccessible(true); } return method.invoke(this.object, params); } } catch (Exception e) { e.printStackTrace(); } return null; } }
O segredo de permitir acesso aos metodos privados é chamar o método setAccessible com o valor true para métodos ou atributos. Dessa forma o JRE ignora a verificação de acesso e podemos acessar métodos private, default e protected que não eram acessados à partir de outras classes.
Nesse utilitário, o método getValue retorna um objeto com o valor de um determinado atributo, independente do modificador de acesso, se o valor for do tipo primitivo ele vai retornar o seu wrapper correspondente. O método setValue, atribui um valor para um atributo, se o valor passado for o wrapper e o campo for primitivo, o valor é convertido automaticamente. O método invokeMethod chama um método, também independente do modificador de acesso passando os devidos parametros. Pode-se utilizar os métodos getAllFields e getAllMethods para listar os atributos e métodos respectivamente. Uma melhoria nessa classe seria lançar o erro ocorrido, mas para a finalidade que pretendo utilizar, o tratamento dentro do método já é suficiente.
Para validar o funcionamento, vamos atribuir um produto um valor negativo e invocar o método privado generateIdInterno, o que não seria possível utilizando o código da maneira convencional.
import java.lang.reflect.Method; public class PrivateAccessTest { public static void main(String[] args) { Produto p = new Produto(); p.setNome("ABCD"); p.setSku(1234); p.setValor(100.0); System.out.println(p); p.setValor(-50.0); System.out.println("Alteração para valor negativo não permitida: " + p); ReflectionUtil ru = new ReflectionUtil(p); ru.setValue("valor", -50); System.out.println("Alteração via reflexão: " + p); System.out.println("Recuperação de atributo privado: " + ru.getValue("idInterno")); Method[] methods = ru.getAllMethods(); for(Method m: methods) { if("generateIdInterno".equals(m.getName())) { System.out.println("Invocação de método privado: " + ru.invokeMethod(m, 888)); } } } }
Na linha 11 tentamos alterar o valor do produto para um valor negativo, o setter correspondente não vai permitir, mantendo o valor do produto igual anteriormente. Se invocarmos com reflexão (linha 15), o atributo é acessado diretamente, sem passar pelo método e o valor na classe é alterado. Na linha 18 recuperamos um valor de um atributo privado e na linha 24 invocamos um método privado.
A saída desse código é a seguinte:
Produto [idInterno=14e77645-506c-46b1-94fa-d0c1f7bc6b32-1234, sku=1234, nome=ABCD, valor=100.0] Alteração para valor negativo não permitida: Produto [idInterno=14e77645-506c-46b1-94fa-d0c1f7bc6b32-1234, sku=1234, nome=ABCD, valor=100.0] Alteração via reflexão: Produto [idInterno=14e77645-506c-46b1-94fa-d0c1f7bc6b32-1234, sku=1234, nome=ABCD, valor=-50.0] Recuperação de atributo privado: 14e77645-506c-46b1-94fa-d0c1f7bc6b32-1234 Invocação de método privado: 6a4f73f6-e580-461e-b741-c6fe40f20c95-888
Dessa maneira, conseguimos ter acessos à métodos e atributos privados de maneira simples. Reforçando novamente, só use quando necessário!
[...] Vespa em 04/04/2011 · Deixe um Comentário Aproveitando o embalo do post: http://www.thiagovespa.com.br/blog/2011/03/31/violando-o-encapsulamento/ resolvi criar uma aplicação para explorar a sessão de uma aplicação Web e verificar quais [...]