Aplicación de patrón de plantilla en primavera

prefacio

El modo de plantilla se usa ampliamente en Spring. Aquí, combinaremos el código fuente de JdbcTemplate para aprender con usted y obtener una comprensión más profunda del modo de plantilla, de modo que podamos usar el modo de plantilla de manera flexible en el desarrollo diario para reducir el código repetitivo y mejorar la escalabilidad del código.

¿Qué es el modo plantilla?

Definición del método de plantilla clásico:

La clase principal define el esqueleto (qué métodos se llaman y en qué orden), y la subclase implementa algunos métodos específicos. El mayor beneficio: reutilización de código, reduciendo la duplicación de código. Excepto por los métodos específicos que implementará la subclase, otros métodos y el orden de las llamadas a métodos están preescritos en la clase principal. Entonces, hay dos tipos de métodos en el método de plantilla de clase principal:

Método común:  código usado por todas las subclases

Diferentes métodos:  Los métodos a ser anulados por las subclases se dividen en dos tipos:

  • Método abstracto: la clase principal es un método abstracto y la subclase debe anularlo
  • Método de enlace: es un método vacío en la clase principal, y la subclase hereda el valor predeterminado y también está vacío

Nota: ¿Por qué se llama enlace? Las subclases pueden controlar la clase principal a través de este enlace (método), ¡porque este enlace es en realidad un método (método vacío) de la clase principal!

public abstract class AbstractTemplate {
    
    public void templateMethod() {
        // 执行一些通用的操作
        
        step1();
        step2();
        step3();
        
        // 执行一些通用的操作
    }
    
    protected abstract void step1();
    
    protected abstract void step2();
    
    protected abstract void step3();
}

public class ConcreteTemplate extends AbstractTemplate {
    
    protected void step1() {
        // 实现具体的业务逻辑
    }
    
    protected void step2() {
        // 实现具体的业务逻辑
    }
    
    protected void step3() {
        // 实现具体的业务逻辑
    }
}

public class Client {
    public static void main(String[] args) {
        AbstractTemplate template = new ConcreteTemplate();
        template.templateMethod();
    }
}

En el ejemplo anterior, AbstractTemplate es una clase de plantilla que define un método templateMethod() como método de plantilla. Este método contiene operaciones comunes y llama a tres métodos abstractos (step1(), step2(), step3()) para completar la lógica comercial específica. ConcreteTemplate es una clase de plantilla concreta que implementa tres métodos abstractos. En la clase Client, creamos una instancia del objeto ConcreteTemplate y llamamos al método templateMethod(), completando así el uso de todo el patrón del método template. Al usar el patrón del método de plantilla, podemos personalizar la lógica comercial específica implementando diferentes métodos de devolución de llamada sin cambiar la clase de plantilla en Spring, para lograr el propósito de la reutilización y extensión del código.

¿Cómo usa Spring el patrón de plantilla?

La esencia del patrón del método de plantilla de Spring: es la combinación del patrón del método de plantilla y el patrón de devolución de llamada, y es otra implementación del método de plantilla que no necesita ser heredada. Casi todas las extensiones complementarias de Spring usan este modo. Echemos un vistazo al diagrama de clases de JdbcTemplate

 Defina el método abstracto de ejecución en la interfaz JdbcOperations

El método de ejecución se implementa en JdbcTemplate

@Override
	public <T> T execute(StatementCallback<T> action) throws DataAccessException {
		Assert.notNull(action, "Callback object must not be null");

		Connection con = DataSourceUtils.getConnection(getDataSource());
		Statement stmt = null;
		try {
			Connection conToUse = con;
			if (this.nativeJdbcExtractor != null &&
					this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
				conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
			}
			stmt = conToUse.createStatement();
			applyStatementSettings(stmt);
			Statement stmtToUse = stmt;
			if (this.nativeJdbcExtractor != null) {
				stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
			}
			T result = action.doInStatement(stmtToUse);
			handleWarnings(stmt);
			return result;
		}
		catch (SQLException ex) {
			// Release Connection early, to avoid potential connection pool deadlock
			// in the case when the exception translator hasn't been initialized yet.
			JdbcUtils.closeStatement(stmt);
			stmt = null;
			DataSourceUtils.releaseConnection(con, getDataSource());
			con = null;
			throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
		}
		finally {
			JdbcUtils.closeStatement(stmt);
			DataSourceUtils.releaseConnection(con, getDataSource());
		}
	}

 En el método anterior, algunos códigos comunes para establecer un vínculo, crear un objeto Declaración, liberar el objeto Declaración y cerrar el vínculo son todos de lógica fija. La lógica cambiada real está en T result = action.doInStatement(stmtToUse); preste atención a la acción aquí, que es un objeto de devolución de llamada, podemos ver la definición de este objeto

public interface StatementCallback<T> {

	/**
	 * Gets called by {@code JdbcTemplate.execute} with an active JDBC
	 * Statement. Does not need to care about closing the Statement or the
	 * Connection, or about handling transactions: this will all be handled
	 * by Spring's JdbcTemplate.
	 * <p><b>NOTE:</b> Any ResultSets opened should be closed in finally blocks
	 * within the callback implementation. Spring will close the Statement
	 * object after the callback returned, but this does not necessarily imply
	 * that the ResultSet resources will be closed: the Statement objects might
	 * get pooled by the connection pool, with {@code close} calls only
	 * returning the object to the pool but not physically closing the resources.
	 * <p>If called without a thread-bound JDBC transaction (initiated by
	 * DataSourceTransactionManager), the code will simply get executed on the
	 * JDBC connection with its transactional semantics. If JdbcTemplate is
	 * configured to use a JTA-aware DataSource, the JDBC connection and thus
	 * the callback code will be transactional if a JTA transaction is active.
	 * <p>Allows for returning a result object created within the callback, i.e.
	 * a domain object or a collection of domain objects. Note that there's
	 * special support for single step actions: see JdbcTemplate.queryForObject etc.
	 * A thrown RuntimeException is treated as application exception, it gets
	 * propagated to the caller of the template.
	 * @param stmt active JDBC Statement
	 * @return a result object, or {@code null} if none
	 * @throws SQLException if thrown by a JDBC method, to be auto-converted
	 * to a DataAccessException by a SQLExceptionTranslator
	 * @throws DataAccessException in case of custom exceptions
	 * @see JdbcTemplate#queryForObject(String, Class)
	 * @see JdbcTemplate#queryForRowSet(String)
	 */
	T doInStatement(Statement stmt) throws SQLException, DataAccessException;

}

Puede ver dónde se llama aquí a execute(StatementCallback<T> action), correspondiente a las operaciones de consulta, actualización y actualización por lotes.

 Vamos a UpdateStatementCallback para ver

	@Override
	public int update(final String sql) throws DataAccessException {
		Assert.notNull(sql, "SQL must not be null");
		if (logger.isDebugEnabled()) {
			logger.debug("Executing SQL update [" + sql + "]");
		}

		class UpdateStatementCallback implements StatementCallback<Integer>, SqlProvider {
			@Override
			public Integer doInStatement(Statement stmt) throws SQLException {
				int rows = stmt.executeUpdate(sql);
				if (logger.isDebugEnabled()) {
					logger.debug("SQL update affected " + rows + " rows");
				}
				return rows;
			}
			@Override
			public String getSql() {
				return sql;
			}
		}

		return execute(new UpdateStatementCallback());
	}

Se puede ver que UpdateStatementCallback implementa la interfaz StatementCallback y coloca la lógica de operación de actualización específica en la capa externa de ejecución, de modo que el cambio se pasa al método de ejecución como un objeto de devolución de llamada. Puede ir a QueryStatementCallback para ver, lo mismo es cierto

	@Override
	public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
		Assert.notNull(sql, "SQL must not be null");
		Assert.notNull(rse, "ResultSetExtractor must not be null");
		if (logger.isDebugEnabled()) {
			logger.debug("Executing SQL query [" + sql + "]");
		}

		class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
			@Override
			public T doInStatement(Statement stmt) throws SQLException {
				ResultSet rs = null;
				try {
					rs = stmt.executeQuery(sql);
					ResultSet rsToUse = rs;
					if (nativeJdbcExtractor != null) {
						rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
					}
					return rse.extractData(rsToUse);
				}
				finally {
					JdbcUtils.closeResultSet(rs);
				}
			}
			@Override
			public String getSql() {
				return sql;
			}
		}

		return execute(new QueryStatementCallback());
	}

Resumir

¿Por qué JdbcTemplate no usa la herencia? Debido a que hay demasiados métodos en esta clase, pero aún queremos usar la conexión de base de datos pública y estable existente de JdbcTemplate, entonces, ¿qué debemos hacer?

 Podemos extraer las cosas modificadas y pasarlas al método JdbcTemplate como parámetro. Pero lo que cambia es un fragmento de código, y este código usará variables en JdbcTemplate. ¿qué hacer? Luego usamos el objeto de devolución de llamada. En este objeto de devolución de llamada, defina un método para manipular variables en JdbcTemplate. Cuando implementemos este método, concentraremos los cambios aquí. Luego pasamos este objeto de devolución de llamada a JdbcTemplate para completar la llamada.

Supongo que te gusta

Origin blog.csdn.net/qq_28165595/article/details/131751192
Recomendado
Clasificación