Este artigo foi traduzido por máquina.

Cutting Edge

Interceptores no Unity

Dino Esposito

Na coluna do mês passado de , apresentei resumidamente o mecanismo de interceptação usado no contêiner de injeção de dependência Unity 2. 0. Depois que ilustram os princípios básicos da programação orientada a aspecto (AOP), apresentei um exemplo concreto de interceptação é provavelmente muito próximos às necessidades de muitos desenvolvedores de hoje.

Você já quis estender o comportamento de uma parte existente do código sem precisar tocar o código-fonte de qualquer maneira? Ter apenas desejar ser capaz de executar um código adicional em todo o código existente?

AOP foi criado para fornecer uma abordagem que isola o código básico de outras preocupações crosscut lógica de negócios principal. Unidade 2. 0 oferece uma estrutura baseada no .NET Framework 4 da Microsoft para conseguir isso de maneira surpreendentemente fácil e rápida.

Para compreender completamente a finalidade deste artigo de acompanhamento, começarei somando-se até o que abordei no mês passado. Como você descobrirá, no código do mês passado fez algumas suposições e alguns componentes padrão usados. Neste mês eu poderá voltar e discutem mais detalhadamente as opções que geralmente encontrados ao longo do processo e as opções.

AOP em Unity

Imagine que você tenha um aplicativo implantado em que, em algum momento, você realizar alguma ação específica para os negócios. Um dia, o cliente pede para estender esse comportamento para executar algum trabalho a mais. Capturar o código-fonte, modificá-lo e cobrar algumas horas do horário de consultoria para backup de codificação e teste os novos recursos. Mas seria melhor se você pode adicionar novos comportamentos perfeitamente e sem tocar no código-fonte existente?

Imagine um cenário ligeiramente diferente. Em primeiro lugar, e se você não é um consultor independente, mas tempo total de trabalho para a empresa? As mais solicitações de mudança que chegar, o maior número de horas que gasto fora seu projeto atual e, pior ainda, você corre o risco ramificações de criação de novas (e não necessariamente o obrigatório) de sua base de código. Bem-assim, você poderia heartily vindo qualquer solução que poderia permitir que você adicione novos comportamentos perfeitamente e sem exposição ao código-fonte.

Por fim, imagine que alguém relata um erro ou um desempenho grave de visitas. É necessário investigar e corrigir o problema e você quiser que ele seja absolutamente discreta. Nesse caso, também, seria melhor adicionar novos comportamentos perfeitamente e sem exposição ao código-fonte.

AOP Ajuda em todos esses cenários.

No mês passado, demonstrei como adicionar código de pré e pós-processamento em torno de um método existente sem tocá-lo utilizando a API de interceptação Unity 2. 0. Essa demonstração fez algumas suposições, no entanto.

Em primeiro lugar, ele trabalhou em um tipo registrado com a infra-estrutura Unity inversão de controle (IoC) e criar uma instância por meio da camada de fábrica Unity.

Em segundo lugar, o pointcut só foi definida através de uma interface. No jargão da AOP, um pointcut representa uma coleção de lugares no corpo da classe de destino em que a estrutura injetará comportamentos extras sob demanda. Um pointcut baseada em interface significa que somente os membros da interface serão estendidos no tempo de execução por meio da inclusão de código.

Em terceiro lugar, eu principalmente com foco nas definições de configuração para ativar a interceptação e desconsiderada a API fluente que lhe permite configurar Unity no código.

O restante deste artigo, explorarei a API fluente e maneiras alternativas de definição de interceptadores Unity.

Instâncias interceptable

Para adicionar um novo comportamento para uma instância existente ou recém-criada de uma classe, você deve levar algum controle sobre a fábrica. Em outras palavras, AOP não é puramente mágica e nunca será capaz de ligar a uma classe CLR simples instanciada via o novo operador padrão:

var calculator = new Calculator();

A maneira na qual uma estrutura AOP assumir o controle sobre a instância pode ser um pouco diferente. Em Unity, você pode recorrer a algumas chamadas explícitas que retornam um proxy para o objeto original ou manter tudo em execução por trás de uma estrutura de IoC. Por esse motivo, a maioria das estruturas de IoC oferecem recursos de AOP. Spring.NET e Unity estão dois exemplos. Quando AOP atende a IoC, o resultado é uma experiência de codificação simples, fácil e eficiente.

Let’s começar com um exemplo onde não há recursos de IoC são usados. Eis alguns códigos básicos que faz com que uma instância existente da classe Calculadora interceptable:

var calculator = new Calculator();
var calculatorProxy = Intercept.ThroughProxy<ICalculator>(calculator,
  new InterfaceInterceptor(), new[] { new LogBehavior() });
Console.WriteLine(calculatorProxy.Sum(2, 2));

Você terminar de trabalhar com um proxy interceptable que encapsula o objeto original. Nesse caso, suponho que a Calculadora classe implementa a interface de ICalculator. Para ser interceptable, uma classe deve implementar uma interface ou herdam de MarshalByRefObject. Se a classe derivada de MarshalByRefObject, o interceptador deve ser do tipo TransparentProxyInterceptor:

var calculator = new Calculator();
var calculatorProxy = Intercept.ThroughProxy(calculator,
  new TransparentProxyInterceptor(), new[] { new LogBehavior() });
Console.WriteLine(calculatorProxy.Sum(2, 2));

A classe de interceptação também oferece um método NewInstance, que você pode chamar para criar um objeto interceptable de forma mais direta. Eis como utilizá-lo:

var calculatorProxy = Intercept.NewInstance<Calculator>(
  new VirtualMethodInterceptor(), new[] { new LogBehavior() });

Observe que quando você usa NewInstance, o componente interceptor deve ser um pouco diferente — nem InterfaceInterceptor nem TransparentProxyInterceptor, mas em vez disso, um objeto VirtualMethodInterceptor. Assim como muitos tipos de interceptadores consta Unity?

Instância e o tipo de interceptores

Um interceptador é o componente de Unity responsável por capturar chamada original para o objeto de destino e de roteamento-lo por meio de um pipeline de comportamentos para que cada comportamento tenha sua chance de ser executado antes ou depois da chamada de método normal. Interceptação pode ser de dois tipos: interceptação de instância e o tipo de interceptação.

Interceptadores de instância de criar um proxy para filtrar as chamadas de entrada direcionadas para a instância interceptada. Interceptadores de tipo de geram uma nova classe derivada do tipo que estão sendo interceptado e derivados de trabalho em uma instância desse tipo. Obviamente, o delta entre o tipo derivado e original é tudo na lógica necessária para as chamadas de entrada de filtro.

No caso de interceptação de instância, o código do aplicativo primeiro cria o objeto de destino usando uma fábrica clássica (ou o novo operador) e, em seguida, é forçado para interagir com ele através do proxy fornecido pelo Unity.

No caso do tipo de interceptação, o aplicativo cria o objeto de destino através da API ou Unity e funciona com aquela instância. (Você não é possível criar o objeto diretamente com o novo operador e obter o tipo de interceptação). O objeto de destino, no entanto, não é do tipo original. O tipo real é derivado por Unity imediatamente e incorpora a lógica de interceptação (consulte do Figura 1).

Figura 1 de interceptador de instância e o interceptador de tipo

InterfaceInterceptor e TransparentProxyInterceptor são dois interceptadores Unity que pertencem à categoria interceptador de instância. VirtualMethodInterceptor pertence à categoria tipo interceptador.

InterfaceInterceptor pode interceptar os métodos de instância pública em uma única interface no objeto de destino. O interceptador pode ser aplicado a instâncias de novas e existentes.

TransparentProxyInterceptor pode interceptar os métodos de instância pública em mais de uma interface e objetos de marshaling por referência. Essa é a abordagem mais lenta para interceptação, mas ele pode interceptar o conjunto mais amplo de métodos. O interceptador pode ser aplicado a instâncias de novas e existentes.

VirtualMethodInterceptor pode interceptar os métodos virtuais públicos e protegidos. O interceptador pode ser aplicado somente às novas instâncias.

Deve-se observar a interceptação de instância pode ser aplicada a quaisquer métodos de instância pública, mas não a construtores. Isso é bastante óbvio para o cenário em que a interceptação se aplica a uma instância existente. É um pouco menos óbvio quando interceptação é aplicada a uma instância recém-criada. A implementação de interceptação de instância é que o construtor já foi executado no momento em que o código do aplicativo obtém de volta um objeto para trabalhar com. Como resultado, qualquer ação interceptable necessariamente segue a criação da instância.

Tipo de interceptação usa geração dinâmica de código para retornar um objeto que herda do tipo original. Ao fazer isso, são substituídos quaisquer métodos virtuais protegidos e públicos para dar suporte à interceptação. Considere o código a seguir:

var calculatorProxy = Intercept.NewInstance<Calculator>(
  new VirtualMethodInterceptor(), new[] { new LogBehavior() });

A classe de calculadora tem esta aparência:

public class Calculator {
  public virtual Int32 Sum(Int32 x, Int32 y) {
    return x + y;
  }
}

A Figura 2 mostra o nome real do tipo resultante de uma inspeção dinâmica da variável calculatorProxy.

A Figura 2 do tipo real após o tipo de interceptação

Também vale a pena observar que há algumas diferenças significativas entre a instância e o tipo de interceptação — por exemplo, interceptando chamadas pelo objeto em si mesmo. Se um método é chamar outro método no mesmo objeto ao usar o tipo de interceptação, em seguida, self-call que pode ser interceptada porque a lógica de interceptação está no mesmo objeto. No entanto, com interceptação de instância, a interceptação ocorre apenas se a chamada é através do proxy. Self-Calls, obviamente, não vá através do proxy e, portanto, não há interceptação ocorre.

Usando o contêiner IoC

Exemplo do mês passado, usei o contêiner IoC da biblioteca Unity para cuidar da criação do objeto. Um contêiner IoC é uma camada extra em torno da criação do objeto adiciona flexibilidade ao aplicativo. Isso é mesmo truer se você considerar que as estruturas de IoC com recursos adicionais de AOP. Além disso — e como posso ver as coisas, o nível de flexibilidade de código cresce além da imaginação, se você combinar, também contêineres IoC com configuração off-line. No entanto, let’s começar com um exemplo que usa o recipiente do Unity com configuração fluente baseada em código:

// Configure the IoC container
var container = UnityStarter.Initialize();

// Start the application
var calculator = container.Resolve<ICalculator>();
var result = calculator.Sum(2, 2);

Qualquer código necessário para o recipiente de inicialização pode ser isolado em uma classe distinta e chamado uma vez na inicialização do aplicativo. O código de inicialização instruirá o recipiente como resolver os tipos de aplicativo e como lidar com a interceptação. Uma chamada ao método resolve protege você contra todos os detalhes de interceptação. A Figura 3 mostra uma possível implementação de código de inicialização.

A Figura 3 de inicialização Unity

public class UnityStarter {
  public static UnityContainer Initialize() {
    var container = new UnityContainer();

    // Enable interception in the current container 
    container.AddNewExtension<Interception>();

    // Register ICalculator with the container and map it to 
    // an actual type.
In addition, specify interception details.
container.RegisterType<ICalculator, Calculator>(
      new Interceptor<VirtualMethodInterceptor>(),
      new InterceptionBehavior<LogBehavior>());

    return container;
  }
}

A vantagem é que esse código pode ser movido para um assembly separado e carregado ou alterado dinamicamente. Além disso, você tem um único local para configurar Unity. Isso não acontecer, contanto que você usar a classe de interceptação de que se comporta como uma fábrica inteligente e precisa ser preparado toda vez que usá-lo. Portanto, se você precisar AOP em seus aplicativos, por tudo isso de forma alguma obtê-lo por meio de um contêiner IoC. A mesma solução pode ser implementada de forma ainda mais flexível ao mover os detalhes de configuração para o arquivo app. config (ou Web. config) se for um aplicativo da Web. Nesse caso, o código de inicialização consiste em duas linhas seguintes:

var container = new UnityContainer();
container.LoadConfiguration();

Figura 4 mostra o script, que você precisa ter no arquivo de configuração. Aqui eu registrado dois comportamentos para o tipo de ICalculator. Isso significa que todas as chamadas para membros públicos da interface serão pré e post-processed LogBehavior e BinaryBehavior.

De adicionando detalhes de interceptação por meio de configuração, a Figura 4

<unity xmlns="https://schemas.microsoft.com/practices/2010/unity">
  <assembly name="SimplestWithConfigIoC"/>
  <namespace name="SimplestWithConfigIoC.Calc"/>
  <namespace name="SimplestWithConfigIoC.Behaviors"/>

  <sectionExtension 
    type="Microsoft.Practices.Unity.
InterceptionExtension.Configuration.
InterceptionConfigurationExtension,     
      Microsoft.Practices.Unity.Interception.Configuration" />

  <container>
    <extension type="Interception" />

    <register type="ICalculator" mapTo="Calculator">
      <interceptor type="InterfaceInterceptor"/>
      <interceptionBehavior type="LogBehavior"/>
      <interceptionBehavior type="BinaryBehavior"/>
    </register>

    <register type="LogBehavior">
    </register>

    <register type="BinaryBehavior">
    </register>

  </container>
</unity>

Observe que, como LogBehavior e BinaryBehavior são tipos concretos, você realmente não precisa registrá-los em todos os. Padrões de unidade automaticamente irão funcionar para eles.

Behaviors

Unity, comportamentos são objetos que realmente implementam as preocupações crosscutting. Uma classe que implementa a interface IInterceptionBehavior, um comportamento regrava o ciclo de execução do método interceptado e modifique os parâmetros de método ou valores de retorno. Comportamentos podem ainda parar o método de chamada em todos os ou chamá-la várias vezes.

Um comportamento é composto de três métodos. A Figura 5 mostra um comportamento de exemplo que intercepta o método de soma e reconfigure seu valor de retorno como uma seqüência de caracteres binária. O método WillExecute é simplesmente uma maneira para otimizar o proxy. Se ela retorna false, o comportamento de não ser executado.

Do comportamento de um exemplo, a Figura 5

public class BinaryBehavior : IInterceptionBehavior {
  public IEnumerable<Type> GetRequiredInterfaces() {
    return Type.EmptyTypes;
  }

  public bool WillExecute {
    get { return true; }
  }

  public IMethodReturn Invoke(
    IMethodInvocation input, 
    GetNextInterceptionBehaviorDelegate getNext) {

    // Perform the operation
    var methodReturn = getNext().Invoke(input, getNext);

    // Grab the output
    var result = methodReturn.ReturnValue;

    // Transform
    var binaryString = ((Int32)result).ToBinaryString();

    // For example, write it out
    Console.WriteLine("Rendering {0} as binary = {1}", 
      result, binaryString);

    return methodReturn;
  }
}

Isso é realmente um pouco subtler. Invocar irá ser chamado, para que seu comportamento será executado na verdade, mesmo que você retornou false. No entanto, quando o tipo derivado de proxy ou está sendo criado, se todos os comportamentos registrados para o tipo tem WillExecute definido como false, em seguida, próprio proxy não ser criado e você vai trabalhar com o objeto não processado novamente. Ele realmente sobre otimização proxy criação.

O método GetRequiredInterfaces permite que o comportamento adicionar novas interfaces para o objeto de destinointerfaces retornados por esse método serão adicionados ao proxy. Assim, o núcleo de um comportamento é o método Invoke. O parâmetro de entrada ganha acesso para o método que está sendo chamado no objeto de destino. O parâmetro getNext é o delegado para que você mova para o seguinte comportamento no pipeline e, eventualmente, executar o método no destino.

O método Invoke determina a lógica real usada para executar uma chamada para um método público no objeto de destino. Observe que todos os métodos interceptados no objeto de destino serão executado de acordo com a lógica que expressa em Invoke.

Se você deseja usar regras de correspondência mais específicas? Com interceptação de simples como descrito neste artigo, tudo o que você pode fazer é executado por meio de um conjunto de se as instruções para descobrir qual método é na verdade, que está sendo chamado, da seguinte forma:

if(input.MethodBase.Name == "Sum") {
  ...
}

No próximo mês eu vai continuar daqui para discutir as maneiras mais eficazes para aplicar a interceptação e definir regras de correspondência para os métodos interceptados.

Dino Esposito é autor de “ Programming Microsoft ASP.NET MVC ” (Microsoft Press, 2010) e co-autor “ Microsoft .net:Architecting Applications for the Enterprise” (Microsoft Press, 2008). . Residente na Itália, Esposito é um palestrante sempre presente em eventos do setor no mundo inteiro. Consulte seu blog em weblogs.asp.net/despos.

Meus agradecimentos aos seguinte especialista técnico para revisar este artigo: Chris Tavares