EJBs originally had a particular set of structures and came in several flavors. The primary types were session EJBs (logic) and Entity EJBs (Object Relational Model). To which was added the Message-Driven Bean, although it really is somewhat foreign to the original framework. There's also a Management EJB in many webapp servers.
EJBs were intended to be plug-sharable components registered to a server. However, that mode of operating has a lot of overhead, so various iterations of the standard allowed for lighter options - specifically local-use beans that weren't shared between JVMs at runtime and therefore didn't require a Remote Interface.
The most recent version of the EJB spec (Version 3) allows EJBs to be defined as POJOs. Earlier generations did not, resulting in a rather cumbersome construct known as a Data Transfer Object, which was nothing but a POJO usable by applications that could be loaded/stored with values from a corresponding EJB. DTOs are totally obsolete now.
Although originally EJBs could only operate within the context of an EJB server, Version 3 allowed a subset of the EJB functions to be used outside of the EJB server framework. This subset is known as the Java Persistence Architecture (JPA) and is implemented in several non-EJB persistence systems such as Apache OpenJPA and Hibernate JPA.
EJBs are instantiated and located by the EJB server. In contrast to this, Spring Beans are instantiated and located by a Spring Bean Factory. The Bean Factory protocol is a java Interface, thus allowing arbitrarily-complex manufacturing and management facilities to back it up. A common one uses an XML file to read bean definitions from, but the factory can mix-and-match from a multitude of sources presenting them as a unified front. Thus, for example, you can use both XML and Java class annotations to register beans.
Spring Beans are POJOs - or at least POJO-like for the most part. Unlike the old EJBs, Spring beans are not required to implement any specific interface(s) or extend from a specific base class (well, allowing for the fact that
everything[/b] extends java.lang.Object ).
Spring also has acquired many specialized capabilities, such as Spring Security, Spring Persistence, and so forth. Some people prefer the Spring Persistence mechanisms over EJBs, as they consider them to have more fine control over transaction management. In my case, I do a lot of my apps on Tomcat, which doesn't support EJBs, so it allows my Tomcat webapps to have EJB-like capabilities using JPA.
Spring also controls the Inversion of Control (IoC) paradigm. IoC became very popular because it allows a "Tinker Toy"-like approach to applications. You take independent components, wire them together, and [i]violá! The JavaServer Faces framework also uses IoC and there's a bridge module that allows Spring Beans and
JSF beans to exist in the same Expression Language (EL) namespace. IoC facilitates productivity (in theory) because it supports the Holy Grail of reusable components. How well that works is debatable, but it also supports swapping out components, which can be invaluable when
testing. Especially when what you're testing would do something annoying like send out hundreds of emails. Just swap the standard email component out for a dummy mailer!