如何使“builder”设计模式和Spring依赖注入相互兼容?

jum4pzuy  于 2023-05-27  发布在  Spring
关注(0)|答案(1)|浏览(252)

“builder”设计模式和Spring依赖注入的兼容性如何?考虑一下这段代码

@Test
    @Sql(executionPhase = BEFORE_TEST_METHOD, value = BASE_SCRIPT_PATH + "GetCommentTest/before.sql") // inserting sample rows
    @Sql(executionPhase = AFTER_TEST_METHOD, value = BASE_SCRIPT_PATH + "GetCommentTest/after.sql") // truncating
    private void getPageOneDefaultTest() throws Exception {
        MockHttpServletResponse response = mockMvc.perform(get(BASE_URI + "page/" + 1)
                        .header(HttpHeaders.AUTHORIZATION, token))
                .andExpect(status().isOk())
                .andReturn()
                .getResponse();

        /*
        The whole idea with "expectation testers" may look a bit unusual, but if you consider that
        I also have methods getPageOneSizeFiveTest(), getPageTwoSizeFiveTest() (probably, I
        should add more of them for a better coverage), you should realize it removes a lot of
        code duplication
        */

        expectationTester = new GetCommentPageExpectationTester.Builder(response)
                .setExpectedPageCount(10)
                .setExpectedPageDtoListSize(10)
                .setExpectedOwnerUsername("mickey_m")
                .build();

        expectationTester.test();
    }
public class GetCommentPageExpectationTester implements ExpectationTester {
    private final String serializedResponseBody;
    private final int expectedPageCount;
    private final int expectedPageDtoListSize;
    private final int expectedFirstCommentId;
    private final String expectedCommentText;
    private final int expectedQuestionId;
    private final int expectedOwnerId;
    private final String expectedOwnerUsername;
    private final ObjectMapper objectMapper;

    private GetCommentPageExpectationTester(Builder builder) {
        this.serializedResponseBody = builder.serializedResponseBody;
        this.expectedPageCount = builder.expectedPageCount;
        this.expectedPageDtoListSize = builder.expectedPageDtoListSize;
        this.expectedFirstCommentId = builder.expectedFirstCommentId;
        this.expectedCommentText = builder.expectedCommentText;
        this.expectedQuestionId = builder.expectedQuestionId;
        this.expectedOwnerId = builder.expectedOwnerId;
        this.expectedOwnerUsername = builder.expectedOwnerUsername;
        this.objectMapper = new ObjectMapper();
    }
    @Override
    public void test() throws JsonProcessingException {
        Data<Page<QuestionCommentResponseDto>> deserializedResponseBody =
                objectMapper.readValue(serializedResponseBody, Data.class); // once I post it on CodeReview, you may comment on this unchecked cast
        assertNotNull(deserializedResponseBody.getData());
        Page<QuestionCommentResponseDto> page = deserializedResponseBody.getData();
        assertEquals(expectedPageCount, page.getCount());
        assertNotNull(page.getDtos());
        List<QuestionCommentResponseDto> dtoList = page.getDtos();
        assertEquals(expectedPageDtoListSize, dtoList.size());

        QuestionCommentResponseDto dto;
        for (int i = 1; i <= dtoList.size(); i++) {
            dto = dtoList.get(i);
            assertEquals(expectedFirstCommentId, dto.getId());
            assertEquals(expectedQuestionId, dto.getQuestionId());
            assertEquals(expectedCommentText, dto.getText());
            assertNotNull(dto.getCreatedDate());
            assertNotNull(dto.getModifiedDate());

            AccountResponseDto actualOwner = dto.getOwner();
            assertEquals(expectedOwnerId, actualOwner.getId());
            assertEquals(expectedOwnerUsername, actualOwner.getUsername());
        }
    }
    public static class Builder {
        private final String serializedResponseBody;
        private int expectedPageCount;
        private int expectedPageDtoListSize;
        private int expectedFirstCommentId = 1;
        private String expectedCommentText = "text";
        private int expectedQuestionId = 1;
        private int expectedOwnerId = 1;
        private String expectedOwnerUsername;

        public Builder(MockHttpServletResponse response) throws UnsupportedEncodingException {
            this.serializedResponseBody = response.getContentAsString();
        }

        public Builder setExpectedPageCount(int expectedPageCount) {
            this.expectedPageCount = expectedPageCount;
            return this;
        }
        public Builder setExpectedPageDtoListSize(int expectedPageDtoListSize) {
            this.expectedPageDtoListSize = expectedPageDtoListSize;
            return this;
        }
        // the rest of the setters are omitted
        public GetCommentPageExpectationTester build() {
            return new GetCommentPageExpectationTester(this);
        }
    }
}

当我调用build()时,它调用一个私有构造函数,并返回复制Builder的字段值的顶级类的示例。现在,假设我想要ObjectMapper自动布线。我将顶级类和Builder注解为@Component s。那么如果我调用build()ObjectMapper不会被注入,对吗?,因为顶级类示例将通过一个简单的构造函数调用创建,所以它不是来自Spring容器,对吗?**我可以有一个这样的构建器,同时也自动绑定依赖吗?**虽然在本例中,我可以简单地使用无参数构造函数初始化ObjectMapper字段(希望它与autowired的ObjectMapper相同),但这对于使用EntityManager的“期望测试人员”可能有意义。

// most fields are omitted for brevity
@Component
public class GetCommentExpectationTester implements ExpectationTester {
    @PersistenceContext
    private EntityManager entityManager;
    @Override
    public void test() {
        assertTrue(entityManager.createQuery("""
                SELECT COUNT(qc.id) = 1
                FROM QuestionComment qc
                JOIN qc.owner ow
                JOIN qc.question q
                WHERE qc.createdDate IS NOT NULL
                AND qc.modifiedDate IS NOT NULL
                AND qc.text = 'text'
                AND ow.id = 1
                AND q.id = 1
                """, Boolean.class)
                .getSingleResult());
    }

我可以在Builder级别自动连接一个字段,例如ObjectMapperEntityManager。但是这样我就不能创建一个Builder示例并在这样的测试方法中传递响应了,是吗?
在创建Builder示例时,我还可以简单地将autowired EntityManager作为方法参数(测试类有一个)传递。但是自从阅读了罗伯特·马丁的书,我对通过任何东西都非常谨慎。越少通过越好,这是我的结论

kmbjn2e3

kmbjn2e31#

为了使ObjectMapper可以自动连接到一个类中,需要首先将该类定义为springbean。
对于您的情况,将Builder定义为原型spring bean并自动将ObjectMapper连接到其中会更自然:

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public static class Builder {

   @Autowired
   private ObjectMapper objectMapper; 

    public GetCommentPageExpectationTester build() {
          return new GetCommentPageExpectationTester(this, objectMapper);
     }
}

public class GetCommentPageExpectationTester implements ExpectationTester {

    private GetCommentPageExpectationTester(Builder builder, ObjectMapper obhjectMapper) {
        .........
        this.objectMapper = objectMapper;
    }

}

然后通过@ImportBuilder定义为spring bean:

@SpringBootTest
@Import(GetCommentPageExpectationTester.Builder.class)
public class FooTest {

    @Autowired
    private ObjectFactory<GetCommentPageExpectationTester.Builder> builder;

}

请注意,由于它是prototype scope bean,您必须使用ObjectFactory将其注入到测试中,以便每当您使用builder.getObject()获取构建器示例时,它将始终确保您将获得一个新示例。
顺便说一下,我觉得ExpectationTester的目的只是为了重用代码来AssertMockHttpServletResponse,这对我来说似乎很复杂。相反,我将尝试定义自定义AssertJAssert,看看它是否更有帮助。

相关问题