preface

This article introduces the adapter pattern, source analysis of Spring AOP, JPA, MVC adapter pattern

Recommended reading

Design patterns and typical application of design patterns | | simple factory pattern factory method pattern and a typical application of design patterns | the abstract factory pattern and a typical application of design pattern model and typical application design | | builders prototype model and typical application design model and typical application design | | appearance Decorator pattern and typical application

For more, visit my personal blog at laijianfeng.org

Pay attention to the wechat official account of xiao Shuang Feng and receive blog posts timely

Adapter mode

Adapter Pattern: An interface is converted into another interface that the customer wants so that classes that are incompatible with the interface can work together. The alias is Wrapper. The adapter pattern can be used as either a class schema or an object schema.

In the adapter pattern, we address interface incompatibilities by adding a new adapter class so that otherwise unrelated classes can work together.

According to the relationship between adapter class and adaptor class, adapter mode can be divided into object adapter and class adapter. In object adapter mode, adapter and adaptor are associated. In the class adapter pattern, there is an inheritance (or implementation) relationship between the adapter and the adapter.

role

Target (Target Abstract Class) : The Target abstract class defines the interface required by the customer. It can be an abstract class, an interface, or a concrete class.

Adapter (Adapter class) : An Adapter can call another interface, acting as a converter, to adapt Adaptee and Target. The Adapter class is the core of the Adapter pattern, and in the object Adapter, it connects the two by inheriting Target and associating them with an Adaptee object.

Adaptee: An Adaptee is a role to be adapted. It defines an existing interface that needs to be adapted. An Adaptee class is usually a concrete class that contains the business methods that the customer wants to use.

Default Adapter Pattern: When you don’t need to implement an interface provided by all methods, can design an abstract class implements the interface first, and provide a method for each interface of the default implementation (empty methods), then a subclass of the abstract class can selectively override some methods to implement the requirements of the parent class, it is suitable for don’t want to use all the methods in an interface, Also known as the single-interface adapter pattern. The default adapter pattern is a variation of the adapter pattern and is widely used. The default adapter pattern, such as WindowAdapter, KeyAdapter, and MouseAdapter, is widely used in java.awt. Event, the event handling package of the JDK class library.

The sample

The class adapter

First there is an existing class that will be adapted

public class Adaptee {
    public void adapteeRequest() {
        System.out.println("The method of the adapted."); }}Copy the code

Define a target interface

public interface Target {
    void request();
}
Copy the code

How can I call Adaptee’s adapteeRequest() method from request() in the target interface?

It doesn’t work if you implement Target directly

public class ConcreteTarget implements Target {
    @Override
    public void request() {
        System.out.println("ConcreteTarget Target method"); }}Copy the code

If you implement the Target interface through an adapter class, inherit the Adaptee class, and then call the parent adapteeRequest() in the request() method

public class Adapter extends Adaptee implements Target{
    @Override
    public void request() {/ /... Some operations... super.adapteeRequest(); / /... Some operations... }}Copy the code

So let’s test that out

public class Test { public static void main(String[] args) { Target target = new ConcreteTarget(); target.request(); Target adapterTarget = new Adapter(); adapterTarget.request(); }}Copy the code

The output

ConcreteTarget Method of the target method adaptorCopy the code

This allows us to adapt the old interface or class in the new interface Target

Object adapter

Object adapters differ from class adapters in that class adapters are adapted through inheritance, whereas object adapters are adapted through association, where a slight modification of the Adapter class transforms the Adapter into an object Adapter

Public class Adapter implements Target{// Adaptee Adaptee = new Adaptee(); @Override public voidrequest() {
        //...
        adaptee.adapteeRequest();
        //...
    }
}
Copy the code

Note that the Adapter here treats the Adaptee as a member property, rather than inheriting it

Voltage adapter

Another good example to understand, our country’s civil electricity is 220V, Japan is 110V, and our mobile phone charging generally need 5V, this time to charge, you need a voltage adapter, will be 220V or 100V input voltage conversion to 5V output

Define the output ac ports for 220V ac and 110V ac

public interface AC {
    int outputAC();
}

public class AC110 implements AC {
    public final int output = 110;

    @Override
    public int outputAC() {
        return output;
    }
}

public class AC220 implements AC {
    public final int output = 220;

    @Override
    public int outputAC() {
        returnoutput; }}Copy the code

The support() method is used to check whether the input voltage matches the adapter, and the outputDC5V() method is used to transform the input voltage to 5V before output

public interface DC5Adapter {
    boolean support(AC ac);

    int outputDC5V(AC ac);
}
Copy the code

Realize Chinese transformer adapter and Japanese transformer adapter

public class ChinaPowerAdapter implements DC5Adapter {
    public static final int voltage = 220;
    
    @Override
    public boolean support(AC ac) {
        return(voltage == ac.outputAC()); } @Override public int outputDC5V(AC ac) { int adapterInput = ac.outputAC(); // Transformer... int adapterOutput = adapterInput / 44; System.out.println("Using ChinaPowerAdapter transformer adapter, enter AC:" + adapterInput + "V" + ", output DC: + adapterOutput + "V");
        return adapterOutput;
    }
}

public class JapanPowerAdapter implements DC5Adapter {
    public static final int voltage = 110;

    @Override
    public boolean support(AC ac) {
        return(voltage == ac.outputAC()); } @Override public int outputDC5V(AC ac) { int adapterInput = ac.outputAC(); // Transformer... int adapterOutput = adapterInput / 22; System.out.println("Using the JapanPowerAdapter transformer, enter AC:" + adapterInput + "V" + ", output DC: + adapterOutput + "V");
        returnadapterOutput; }}Copy the code

Test, prepare one transformer adapter for China and one transformer adapter for Japan, define a method to find the suitable transformer according to the voltage, and then test

public class Test {
    private List<DC5Adapter> adapters = new LinkedList<DC5Adapter>();

    public Test() { this.adapters.add(new ChinaPowerAdapter()); this.adapters.add(new JapanPowerAdapter()); } public DC5Adapter getPowerAdapter(AC AC) {DC5Adapter adapter = null;for (DC5Adapter ad : this.adapters) {
            if (ad.support(ac)) {
                adapter = ad;
                break; }}if (adapter == null){
            throw new  IllegalArgumentException("No suitable transformer adapter found.");
        }
        return adapter;
    }

    public static void main(String[] args) {
        Test test= new Test(); AC chinaAC = new AC220(); DC5Adapter adapter = test.getPowerAdapter(chinaAC); adapter.outputDC5V(chinaAC); // The voltage is 110V AC japanAC = new AC110(); adapter = test.getPowerAdapter(japanAC); adapter.outputDC5V(japanAC); }}Copy the code

The output

Use the ChinaPowerAdapter transformer adapter, input AC:220V, output DC:5V Use the JapanPowerAdapter transformer adapter, input AC:110V, output DC:5VCopy the code

Adapter Pattern Summary

Main advantages:

  1. Decouple the target class from the adapter class and reuse the existing adapter class by introducing an adapter class without modifying the original structure.
  2. It increases the transparency and reuse of the class, encapsulates the specific business implementation process in the adapter class, which is transparent to the client class and improves the reuse of the adapter. The same adapter class can be reused in multiple different systems.
  3. Flexibility and extensibility are very good, through the use of configuration files, it is easy to replace the adapter, but also without modifying the original code on the basis of the new adapter class, in full compliance with the “open closed principle”.

Specifically, the adapter-like pattern has the following advantages:

  • Because the adapter class is a subclass of the adapter class, you can substitute some of the adapter methods in the adapter class to make the adapter more flexible.

The object adapter pattern also has the following advantages:

  • An object adapter can adapt multiple different adaptors to the same target;
  • An adapter can be adapted to a subclass of an adapter. Because an adapter is associated with an adapter, a subclass of an adapter can also be adapted to the adapter according to the Richter substitution principle.

The disadvantages of the adapter-like pattern are as follows:

  1. For languages that do not support multiple class inheritance, such as Java and C#, a maximum of one adaptor class can be adapted at a time.
  2. The adapter class cannot be the final class. For example, it cannot be the final class in Java or sealed class in C#.
  3. In Java, C# and other languages, the target abstract class in the class adapter pattern can only be an interface, not a class, and its use has certain limitations.

The disadvantages of the object adapter pattern are as follows:

  • In contrast to the class adapter pattern, it is more cumbersome to replace some of the methods of the adapter class in the adapter. If you must replace one or more methods of the adapter class, you can first make a subclass of the adapter class to replace the method of the adapter class, and then take the subclass of the adapter class as the real adapter for adaptation. The implementation process is complicated.

Applicable scenarios:

  • The system needs to use existing classes whose interfaces (such as method names) do not meet the needs of the system, and there is not even source code for these classes.
  • You want to create a reusable class that works with classes that don’t have much to do with each other, including classes that may be introduced in the future.

A typical application of the source analysis adapter pattern

Adapter patterns in Spring AOP

In Spring’s Aop, Advice is used to enhance the proxyed class.

The types of Advice are MethodBeforeAdvice, AfterReturningAdvice, and ThrowsAdvice

In each type of Advice has a corresponding interceptors, MethodBeforeAdviceInterceptor, AfterReturningAdviceInterceptor, ThrowsAdviceInterceptor

Spring needs to wrap each Advice into a corresponding interceptor type and return it to the container, so the Advice needs to be transformed using the adapter pattern

The three Adaptee classes are as follows:

public interface MethodBeforeAdvice extends BeforeAdvice {
    void before(Method var1, Object[] var2, @Nullable Object var3) throws Throwable;
}

public interface AfterReturningAdvice extends AfterAdvice {
    void afterReturning(@Nullable Object var1, Method var2, Object[] var3, @Nullable Object var4) throws Throwable;
}

public interface ThrowsAdvice extends AfterAdvice {
}
Copy the code

The Target interface has two methods, one to determine whether the Advice type matches, and the other is a factory method to create an interceptor corresponding to the Advice type

public interface AdvisorAdapter {
    boolean supportsAdvice(Advice var1);

    MethodInterceptor getInterceptor(Advisor var1);
}
Copy the code

The three Adapter classes are as follows. Pay attention to the correspondence between Advice, Adapter, and Interceptor

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {
	@Override
	public boolean supportsAdvice(Advice advice) {
		return (advice instanceof MethodBeforeAdvice);
	}

	@Override
	public MethodInterceptor getInterceptor(Advisor advisor) {
		MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
		return new MethodBeforeAdviceInterceptor(advice);
	}
}

@SuppressWarnings("serial")
class AfterReturningAdviceAdapter implements AdvisorAdapter, Serializable {
	@Override
	public boolean supportsAdvice(Advice advice) {
		return (advice instanceof AfterReturningAdvice);
	}
	@Override
	public MethodInterceptor getInterceptor(Advisor advisor) {
		AfterReturningAdvice advice = (AfterReturningAdvice) advisor.getAdvice();
		return new AfterReturningAdviceInterceptor(advice);
	}
}

class ThrowsAdviceAdapter implements AdvisorAdapter, Serializable {
	@Override
	public boolean supportsAdvice(Advice advice) {
		return (advice instanceof ThrowsAdvice);
	}
	@Override
	public MethodInterceptor getInterceptor(Advisor advisor) {
		returnnew ThrowsAdviceInterceptor(advisor.getAdvice()); }}Copy the code

The client DefaultAdvisorAdapterRegistry

public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable {
    private final List<AdvisorAdapter> adapters = new ArrayList(3);

    public DefaultAdvisorAdapterRegistryRegistered the adapter () {/ / here enclosing registerAdvisorAdapter (new MethodBeforeAdviceAdapter ()); this.registerAdvisorAdapter(new AfterReturningAdviceAdapter()); this.registerAdvisorAdapter(new ThrowsAdviceAdapter()); } public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException { List<MethodInterceptor> interceptors = new ArrayList(3); Advice advice = advisor.getAdvice();if (advice instanceof MethodInterceptor) {
            interceptors.add((MethodInterceptor)advice);
        }

        Iterator var4 = this.adapters.iterator();

        while(var4.hasNext()) {
            AdvisorAdapter adapter = (AdvisorAdapter)var4.next();
            if(Adapter.supportsadvice (advice)) {// Here we call the adapter method interceptors.add(Adapter.getInterceptor (Advisor)); // Call the adapter method here}}if (interceptors.isEmpty()) {
            throw new UnknownAdviceTypeException(advisor.getAdvice());
        } else {
            return(MethodInterceptor[])interceptors.toArray(new MethodInterceptor[0]); }} / /... Omit... }Copy the code

In the while loop, fetch the registered adapters one by one, call the supportsAdvice() method to determine the type of Advice, and then call getInterceptor() to create an interceptor of the corresponding type

This is supposed to be the object adapter pattern, and the keyword instanceof can be thought of as an Advice method, but here the Advice object is passed in from outside rather than a member property

Adapter patterns in Spring JPA

Support for JPA in Spring’s ORM package also follows the adapter pattern, first defining a JpaVendorAdapter for an interface, which is then implemented by different persistence frameworks.

JpaVendorAdapter: used to set specific properties that implement vendor JPA implementations, such as Hibernate’s attribute generateDdl to automatically generateDdl; These properties are vendor-specific, so they are best set here; Spring provide HibernateJpaVendorAdapter, OpenJpaVendorAdapter, EclipseLinkJpaVendorAdapter, TopLinkJpaVendorAdapter four implementation. One of the most important attributes is database, which specifies the type of database to use so that decisions can be made based on the database type, such as how to convert database-specific exceptions to Spring conformance exceptions. Currently supports the following databases (DB2, DERBY, H2, HSQL, INFORMIX, MYSQL, ORACLE, POSTGRESQL, SQL_SERVER, SYBASE)

Public interface JpaVendorAdapter {// Returns a specific PersistenceProvider public Abstract PersistenceProvider getPersistenceProvider(); / / returns the persistence layer provider package name public abstract String getPersistenceProviderRootPackage (); Public abstract Map<String,? > getJpaPropertyMap(); JpaDialect public abstract JpaDialect getJpaDialect(); // Return persistence layer manager factory public abstract Class<? extends EntityManagerFactory> getEntityManagerFactoryInterface(); // Return persistence layer manager public abstract Class<? extends EntityManager> getEntityManagerInterface(); / / custom callback methods public abstract void postProcessEntityManagerFactory (EntityManagerFactory paramEntityManagerFactory); }Copy the code

Let’s look at one of the adapter implementation class HibernateJpaVendorAdapter

Public class HibernateJpaVendorAdapter extends AbstractJpaVendorAdapter {/ / set the persistence layer provider private final PersistenceProvider persistenceProvider; Private final JpaDialect JpaDialect; publicHibernateJpaVendorAdapter() { this.persistenceProvider = new HibernatePersistence(); this.jpaDialect = new HibernateJpaDialect(); } // Return the persistence layer dialect public PersistenceProvidergetPersistenceProvider() {
        returnthis.persistenceProvider; } // Return the persistence layer provider public StringgetPersistenceProviderRootPackage() {
        return "org.hibernate"; } // Return JPA attributes public Map<String, Object>getJpaPropertyMap() {
        Map jpaProperties = new HashMap();

        if(getDatabasePlatform() ! = null) { jpaProperties.put("hibernate.dialect", getDatabasePlatform());
        } else if(getDatabase() ! = null) { Class databaseDialectClass = determineDatabaseDialectClass(getDatabase());if(databaseDialectClass ! = null) { jpaProperties.put("hibernate.dialect", databaseDialectClass.getName()); }}if (isGenerateDdl()) {
            jpaProperties.put("hibernate.hbm2ddl.auto"."update");
        }
        if (isShowSql()) {
            jpaProperties.put("hibernate.show_sql"."true");
        }

        returnjpaProperties; } / / set the Database protected Class determineDatabaseDialectClass Database (Database) {switch (1).$SwitchMap$org$springframework$orm$jpa$vendor$Database[database.ordinal()]) 
        {                                                                                     
        case 1:                                                                             
          return DB2Dialect.class;                                                            
        case 2:                                                                               
          return DerbyDialect.class;                                                          
        case 3:                                                                               
          return H2Dialect.class;                                                             
        case 4:                                                                               
          return HSQLDialect.class;                                                           
        case 5:                                                                               
          return InformixDialect.class;                                                       
        case 6:                                                                               
          return MySQLDialect.class;                                                          
        case 7:                                                                               
          return Oracle9iDialect.class;                                                       
        case 8:                                                                               
          return PostgreSQLDialect.class;                                                     
        case 9:                                                                               
          return SQLServerDialect.class;                                                      
        case 10:                                                                              
          return SybaseDialect.class; }                                                       
        returnnull; } // return JPA dialect public JpaDialectgetJpaDialect() {
        returnthis.jpaDialect; } // return JPA entity manager factory public Class<? extends EntityManagerFactory>getEntityManagerFactoryInterface() {
        returnHibernateEntityManagerFactory.class; } // return JPA entity manager public Class<? extends EntityManager>getEntityManagerInterface() {
        returnHibernateEntityManager.class; }}Copy the code

This can be specified in the configuration file

<bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> 
   <property name="generateDdl" value="false" />  
   <property name="database" value="HSQL"/>  
</bean>  
<bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>  
Copy the code

The adapter pattern in Spring MVC

The adapter pattern in Spring MVC is primarily used to perform request handling in the target Controller.

In Spring MVC, the DispatcherServlet is the user, the HandlerAdapter is the expected interface, the specific adapter implementation class is used to adapt the target class, and the Controller is the class to be adapted.

Why use the adapter pattern in Spring MVC? There are many different types of Controllers in Spring MVC, and different types of controllers handle requests in different ways. If the adapter mode is not used, the DispatcherServlet directly fetches the corresponding Controller type, as shown in the following code:

if(mappedHandler.getHandler() instanceof MultiActionController){  
   ((MultiActionController)mappedHandler.getHandler()).xxx  
}else if(mappedHandler.getHandler() instanceof XXX){  
    ...  
}else if(...). {... }Copy the code

Assuming that if we add a HardController, we add an if line (mappedHandler.gethandler () instanceof HardController) to the code, which makes the program difficult to maintain. It also violates the open and closed principle of design pattern-open for extension, closed for modification.

Let’s look at the source code, the first is the adapter interface HandlerAdapter

public interface HandlerAdapter {
    boolean supports(Object var1);

    ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;

    long getLastModified(HttpServletRequest var1, Object var2);
}
Copy the code

Now each Controller of this interface has an adapter corresponding to it, so that each custom Controller needs to define an adapter that implements the HandlerAdapter.

The Controller implementation classes provided in SpringMVC are as follows

The HandlerAdapter implementation class provided in SpringMVC is as follows

HttpRequestHandlerAdapter this adapter code is as follows

public class HttpRequestHandlerAdapter implements HandlerAdapter {
    public HttpRequestHandlerAdapter() {
    }

    public boolean supports(Object handler) {
        return handler instanceof HttpRequestHandler;
    }

    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        ((HttpRequestHandler)handler).handleRequest(request, response);
        return null;
    }

    public long getLastModified(HttpServletRequest request, Object handler) {
        returnhandler instanceof LastModified ? ((LastModified)handler).getLastModified(request) : -1L; }}Copy the code

When the Spring container is started, all the defined adapter objects are stored in a List. When a request comes in, the DispatcherServlet finds the corresponding adapter by the handler type and returns the adapter object to the user. The methods in Controller that handle the request can then be called uniformly through the adapter’s hanle() method.

public class DispatcherServlet extends FrameworkServlet { private List<HandlerAdapter> handlerAdapters; Private void initHandlerAdapters(ApplicationContext Context) {//.. Omit... } // Run through all HandlerAdapters, Protected HandlerAdapter getHandlerAdapter(Object Handler) throws ServletException {for (HandlerAdapter ha : this.handlerAdapters) {
			if (logger.isTraceEnabled()) {
				logger.trace("Testing handler adapter [" + ha + "]");
			}
			if (ha.supports(handler)) {
				returnha; }}} // Distribute the request, which needs to find a matching adapter to process protected voiddoDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;

		// Determine handler forthe current request. mappedHandler = getHandler(processedRequest); // Determine the matching adapter currently requested. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); ha.getLastModified(request, mappedHandler.getHandler()); mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); } / /... Omit... }Copy the code

Through the adapter mode, all controllers are assigned to HandlerAdapter, which saves the need to write a large number of if-else statements to judge the controller, and is more convenient to expand the new controller type.

Debug mode + Memory analysis Lonely: Adapter pattern in Spring MVC ToughMind_ : Simple design pattern (5) : 7 Adapter mode