📜 ⬆️ ⬇️

Spring Framework without XML ... really!

In the light of the current era of the definition of everything and all the annotations I suggest you an article about the Spring Framework and the possibilities of annotating projects. Note trans.
In the beginning, there was EJB 2.1, with its huge number of XML files wherever possible. It would not be a special exaggeration if you say that at least 10 lines of code from the framework and two XML pages were needed to write one line of code for business logic. Local and remote interfaces, manual JNDI-lookup, multi-level try-catch, checks for RemoteException ... enterprise, in general. Even the tools were appropriate for the automatic generation of all this "kitchen".

Then a couple of guys created the Spring framework . After the castes of the obscure PortableRemoteObject.narrow () this became a breath of fresh air. The time has passed (by the way, does someone remember how many years ago was the last major release of the JRE?) And Sun realized the lesson. EJB 3.0 was even simpler than Spring, XML-free, with annotations, dependency injection. 3.1 was another huge step towards simplification. Logically, EJB can now be viewed as part of what Spring offers, and I’m quite surprised why there is no EJB implementation in plain Spring (oops, wait ...), given its support from JPA 1.0 / 2.0, JSR-250 box , JSR-330, JAX-WS / RS and others. The spring framework today is perceived as slow, heavy and complex to support the framework, mainly because of the XML descriptors. In general, Spring in the confrontation of JEE-frameworks as a whipping boy.

I do not like politics and do not defend my favorite framework by writing long essays. Instead, I'll take a simple but not trivial Spring application and rewrite it without XML. Absolutely no XML, no single line.
For the article, I wrote a very simple Spring web application (the regular version in the xml branch, the final version in the master) with JDBC, JMS and JMX, just so that it would be impossible to simplify the task. Each change will be reflected in a separate commit to the repository, and, step by step, I will get rid of XML until it remains completely. Let's start.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:amq="http://activemq.apache.org/schema/core" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core-5.4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:mbean-export /> <bean id="fooService" class="com.blogspot.nurkiewicz.FooService"> <property name="jmsOperations" ref="jmsTemplate" /> </bean> <bean id="fooRequestProcessor" class="com.blogspot.nurkiewicz.FooRequestProcessor"> <property name="fooRepository" ref="fooRepository" /> </bean> <bean id="fooRepository" class="com.blogspot.nurkiewicz.FooRepository" init-method="init"> <property name="jdbcOperations" ref="jdbcTemplate" /> </bean> <!-- JDBC --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="org.h2.Driver" /> <property name="url" value="jdbc:h2:~/workspace/h2/spring-noxmal;DB_CLOSE_ON_EXIT=FALSE;TRACE_LEVEL_FILE=4;AUTO_SERVER=TRUE" /> <property name="username" value="sa" /> <property name="password" value="" /> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <constructor-arg ref="dataSource" /> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <constructor-arg ref="dataSource" /> </bean> <tx:annotation-driven /> <!-- JMS --> <bean id="jmsConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory"> <constructor-arg> <bean class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="tcp://localhost:61616" /> </bean> </constructor-arg> </bean> <amq:queue id="requestsQueue" physicalName="requests" /> <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"> <constructor-arg ref="jmsConnectionFactory" /> <property name="defaultDestination" ref="requestsQueue" /> </bean> <bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <property name="connectionFactory" ref="jmsConnectionFactory" /> <property name="destination" ref="requestsQueue" /> <property name="sessionTransacted" value="true"/> <property name="concurrentConsumers" value="5"/> <property name="messageListener"> <bean class="org.springframework.jms.listener.adapter.MessageListenerAdapter"> <constructor-arg ref="fooRequestProcessor" /> <property name="defaultListenerMethod" value="process"/> </bean> </property> </bean> </beans> 

A pair of user beans, JDBS with transactions and JMS, send and accept. The details are not particularly important - one of the bins via JMX sends a JMS message, the message is received and persisted in the database.

The most common and well-established approach to reducing the XML layer in Spring is to use the Service and Resource annotations along with <context: component-scan /> for user beans ( changes ):
 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:amq="http://activemq.apache.org/schema/core" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core-5.4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:mbean-export /> <context:component-scan base-package="com.blogspot.nurkiewicz"/> <!-- JDBC --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="org.h2.Driver" /> <property name="url" value="jdbc:h2:~/workspace/h2/spring-noxmal;DB_CLOSE_ON_EXIT=FALSE;TRACE_LEVEL_FILE=4;AUTO_SERVER=TRUE" /> <property name="username" value="sa" /> <property name="password" value="" /> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <constructor-arg ref="dataSource" /> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <constructor-arg ref="dataSource" /> </bean> <tx:annotation-driven /> <!-- JMS --> <bean id="jmsConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory"> <constructor-arg> <bean class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="tcp://localhost:61616" /> </bean> </constructor-arg> </bean> <amq:queue id="requestsQueue" physicalName="requests" /> <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"> <constructor-arg ref="jmsConnectionFactory" /> <property name="defaultDestination" ref="requestsQueue" /> </bean> <bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <property name="connectionFactory" ref="jmsConnectionFactory" /> <property name="destination" ref="requestsQueue" /> <property name="sessionTransacted" value="true"/> <property name="concurrentConsumers" value="5"/> <property name="messageListener"> <bean class="org.springframework.jms.listener.adapter.MessageListenerAdapter"> <constructor-arg ref="fooRequestProcessor" /> <property name="defaultListenerMethod" value="process"/> </bean> </property> </bean> </beans> 

Minus 10 lines of XML, nothing special ... And user bins?
 @Service public class FooRepository { @Resource private JdbcOperations jdbcOperations; @PostConstruct public void init() { log.info("Database server time is: {}", jdbcOperations.queryForObject("SELECT CURRENT_TIMESTAMP", Date.class)); } //... } 

Setters and init methods are replaced by annotations. So what's next? Most people — someone who likes — annotations stop there, but as you can see, a lot of XML remains. The question now is how to use annotations in the case of third-party things such as connection pools, etc.?
This is where the fun begins. First we need to get rid of data source XML and replace it all with ... ( changes )
 import javax.sql.DataSource; import org.apache.commons.dbcp.BasicDataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class ContextConfiguration { @Bean public DataSource dataSource() { final BasicDataSource ds = new BasicDataSource(); ds.setDriverClassName("org.h2.Driver"); ds.setUrl("jdbc:h2:~/workspace/h2/spring-noxmal;DB_CLOSE_ON_EXIT=FALSE;TRACE_LEVEL_FILE=4;AUTO_SERVER=TRUE"); ds.setUsername("sa"); return ds; } } 

@Configuration, Bean , dataSource (), what is it ...?!? But it works just as you thought: Spring finds the ContextConfiguration class and all methods annotated as Bean . Each such method is equivalent to the XML <bean ...> (here even Scope , @DependsOn and Lazy ), so we can remove the XML declaration of the dataSource bean. Although, in general, you can get rid of the JdbcTemplate and transaction manager ( changes ):
 @Bean public JdbcOperations jdbcOperations() { return new JdbcTemplate(dataSource()); } @Bean public PlatformTransactionManager transactionManager() { return new DataSourceTransactionManager(dataSource()); } 

Pay attention to how easy it is to inject the data source bin into other bins. You have a method that creates a data source, and you have two methods that need this data source (JdbcTemplate and transaction manager). It doesn't get any easier, and maybe your girlfriend would have implemented dependency injection (um, Guice? )
')
One thing should bother you now ... If you call the dataSource () twice, does this mean that two independent DataSource instances are being created? This is not at all what we would like. It puzzled me, but once again Spring turned out to be a smart animal. Not finding the Scope annotations, he begins to assume that the data source should be singleton and, using a bit of CGLIB proxying magic on the dataSource () method, limits the number of calls to one. Well, or more specifically, you can call it many times, but return the same bean. Cool!
As a result, the XML configuration is reduced to:
 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:amq="http://activemq.apache.org/schema/core" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core-5.4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:mbean-export /> <context:component-scan base-package="com.blogspot.nurkiewicz"/> <!-- JDBC --> <tx:annotation-driven /> <!-- JMS --> <bean id="jmsConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory"> <constructor-arg> <bean class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="tcp://localhost:61616" /> </bean> </constructor-arg> </bean> <amq:queue id="requestsQueue" physicalName="requests" /> <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"> <constructor-arg ref="jmsConnectionFactory" /> <property name="defaultDestination" ref="requestsQueue" /> </bean> <bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <property name="connectionFactory" ref="jmsConnectionFactory" /> <property name="destination" ref="requestsQueue" /> <property name="sessionTransacted" value="true"/> <property name="concurrentConsumers" value="5"/> <property name="messageListener"> <bean class="org.springframework.jms.listener.adapter.MessageListenerAdapter"> <constructor-arg ref="fooRequestProcessor" /> <property name="defaultListenerMethod" value="process"/> </bean> </property> </bean> </beans> 

Now you can stop and think about how to rewrite the remaining bins in the configuration. It's not worth it, everything is transparent ( changes ).
 @Bean public ConnectionFactory jmsConnectionFactory() { final ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(); factory.setBrokerURL("tcp://localhost:61616"); return new PooledConnectionFactory(factory); } @Bean public Queue requestsQueue() { return new ActiveMQQueue("requests"); } @Bean public JmsOperations jmsOperations() { final JmsTemplate jmsTemplate = new JmsTemplate(jmsConnectionFactory()); jmsTemplate.setDefaultDestination(requestsQueue()); return jmsTemplate; } 

The DefaultMessageListenerContainer declaration contains anonymous inner bins that are used once inside the parent bean. Private-method in order ( changes ):
 @Bean public AbstractJmsListeningContainer jmsContainer() { final DefaultMessageListenerContainer container = new DefaultMessageListenerContainer(); container.setConnectionFactory(jmsConnectionFactory()); container.setDestination(requestsQueue()); container.setSessionTransacted(true); container.setConcurrentConsumers(5); container.setMessageListener(messageListenerAdapter()); return container; } private MessageListenerAdapter messageListenerAdapter() { final MessageListenerAdapter adapter = new MessageListenerAdapter(fooRequestProcessor); adapter.setDefaultListenerMethod("process"); return adapter; } 


There is nothing to comment on because plain Java configuration in Spring is trivial and unpretentious - the code speaks for itself. In case you are confused, we have come to this:

<? xml version = "1.0" encoding = "UTF-8"?>
<beans xmlns = " www.springframework.org/schema/beans "
xmlns: xsi = " www.w3.org/2001/XMLSchema-instance "
xmlns: tx = " www.springframework.org/schema/tx "
xmlns: context = " www.springframework.org/schema/context "
xsi: schemaLocation = " www.springframework.org/schema/beans www.springframework.org/schema/beans/spring-beans-3.0.xsd
www.springframework.org/schema/tx www.springframework.org/schema/tx/spring-tx-2.5.xsd
www.springframework.org/schema/context www.springframework.org/schema/context/spring-context.xsd »>

<context: mbean-export />

<context: component-scan base-package = "com.blogspot.nurkiewicz" />

<tx: annotation-driven />



Honestly, it wasn't all that hard, but it was really hard to get rid of the last few lines of XML. Believe me - you do not want to do what I did to cope with these beautiful little namespace-powered ads. But after a couple of minutes, a couple of unsuccessful experiences and a heap of scanned Spring-code, I finally removed the JMX declarations ( changes ) and transactions ( changes ). It looks innocent and I am pleased that you didn’t have to dig into the Spring code to get to it yourself:
 @Bean public AnnotationMBeanExporter annotationMBeanExporter() { return new AnnotationMBeanExporter(); } @Bean public TransactionAttributeSource annotationTransactionAttributeSource() { return new AnnotationTransactionAttributeSource(); } @Bean public TransactionInterceptor transactionInterceptor() { return new TransactionInterceptor(transactionManager(), annotationTransactionAttributeSource()); } 

Like this. It remains to remove the XML instructions for Spring regarding the location of the beans and the web.xml snippet:
 <context:component-scan base-package="com.blogspot.nurkiewicz"/> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app> 

There is no other way to launch Spring normally in a web environment, you must tell the web container that you need Spring. I know, I know, I promised that there would be no XML at all, well, I lied, and that now ... sorry, OK? Business ... Yeah, just kidding.  Let's get rid of it. A couple of seconds! Well ... although it depends on how quickly you download the newest Tomcat 7 or other JSR 315 (known as Servlet 3.0) web container ...

Web fragments are a technology that allows you to delicately integrate various web frameworks into servlet containers. If you have experience with frameworks, then you know that they all need a description of a special servlet, filter, or listener in web.xml. In most cases, this is the only time that servlet dependency occurs and Spring is no exception. The essence of web fragments is that they are trying to free developers from this. Servlet 3.0 containers should scan all JARs in / WEB-INF / lib and, if any of the JARs contain web-fragment.xml in / META-INF, it will be registered in web.xml.

Understand where I lead? What if we create a small JAR with a small web fragment just to run Spring without XML? WAR structure:
 . |-- META-INF `-- WEB-INF |-- classes | |-- com | | `-- blogspot | | `-- nurkiewicz | | |-- ContextConfiguration.class | | |-- FooRepository.class | | |-- FooRequestProcessor.class | | |-- FooService$1.class | | `-- FooService.class | `-- logback.xml |-- lib | |-- spring-web-3.0.5.RELEASE.jar | |-- spring-web-fragment-0.0.1-SNAPSHOT.jar | | `-- META-INF | | |-- MANIFEST.MF | | |-- web-fragment-context.xml | | `-- web-fragment.xml | `-- spring-beans-3.0.5.RELEASE.jar `-- web.xml 

The sole purpose of the spring-web-fragment is *. Jar in providing the web-fragment.xml container:
 <web-fragment xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd" version="3.0"> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:/META-INF/web-fragment-context.xml</param-value> </context-param> </web-fragment> 

One new element is the web fragment-context.xml Spring context file. We cannot use the default (/WEB-INF/applicationContext.xml), because this file is no longer (!). But our little JAR looks as if he were the best place for it:
 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <context:component-scan base-package="." /> </beans> 

Package declaration in the form of "." causes alertness. This is very sad, but I tried to bypass the requirement in the declaration of at least one package. This requirement may have been justified (I think the CLASSPATH scan takes some time), but I couldn’t just specify my package, otherwise I would have to change this ad for each new project. But this goes against the main advantage of the web-fragments approach — when creating a JAR with two small XML files, it can be used for all projects. All that is needed is to include it in your WAR's library list and start annotating the POJO as a Service (and / or use @Configuration).

If it is available amongst what Spring offers out of the box (if you like the idea, vote ), then newbies will be able to enjoy the journey to Spring as soon as they add Spring to pom.xml. In fact, pom.xml can be written in various languages , like logback.xml . Hey, no xml! Are you sure? Do you like XML or Java? Or maybe Groovy? Please be silent. Spring gives you the opportunity to be as simple and straightforward to use as you want. Without being so simplified that the functionality will be trimmed.

Source: https://habr.com/ru/post/112488/


All Articles