Spring 프레임 워크는 무엇을 하는가? 사용해야합니까? 그 이유는 무엇?


237

그래서 Java로 새로운 프로젝트를 시작하고 있으며 Spring 사용을 고려하고 있습니다. 왜 봄을 고려하고 있습니까? 많은 사람들이 Spring을 사용해야한다고 말합니다! 진심으로, 사람들이 Spring이 무엇인지 정확히 무엇인지 설명하려고 할 때마다, 그들은 결코 대답하지 않습니다. SpringSource 사이트에서 인트로를 확인했으며 실제로 복잡하거나 튜토리얼에 중점을두고 있으며 왜 사용 해야하는지 또는 어떻게 인생을 더 편하게 할 수 있는지에 대한 좋은 아이디어를 제공하지 않습니다. 때때로 사람들은 "종속성 주입 (dependency injection)"이라는 용어를 던지는데, 이는 그 용어의 의미에 대해 다른 이해가 있다고 생각하기 때문에 나를 더욱 혼란스럽게 만듭니다.

어쨌든, 여기 내 배경과 내 응용 프로그램에 대해 조금 있습니다.

백엔드 웹 개발을 수행하면서 한동안 Java로 개발했습니다. 예, 많은 단위 테스트를 수행합니다. 이를 용이하게하기 위해 일반적으로 두 가지 버전의 메소드를 작성합니다. 하나는 인스턴스 변수를 사용하고 다른 하나는 메소드에 전달 된 변수 만 사용합니다. 인스턴스 변수를 사용하는 인스턴스는 다른 하나를 호출하여 인스턴스 변수를 제공합니다. 단위 테스트 시간이되면 Mockito를 사용하여 객체를 조롱 한 다음 인스턴스 변수를 사용하지 않는 메소드를 호출합니다. 이것이 제가 항상 "종속성 주입"을 이해 한 것입니다.

내 앱은 CS 관점에서 매우 간단합니다. 소규모 프로젝트, 1-2 명의 개발자부터 시작합니다. 대부분의 검색이 포함 된 CRUD 유형 작업. 기본적으로 많은 RESTful 웹 서비스와 웹 프론트 엔드 및 일부 모바일 클라이언트. HTML / CSS / JS / JQuery에서 프런트 엔드를 수행하려고 생각하고 있으므로 JSP를 사용할 실제 계획은 없습니다. 웹 서비스를 구현하기 위해 Hibernate를 ORM으로 사용하고 Jersey.

이미 코딩을 시작했으며 실제로 쇼핑을하고 누군가가 투자를 원하는지 확인할 수있는 데모를 열망하고 있습니다. 분명히 시간은 본질입니다. Spring에는 꽤 많은 학습 곡선이 있으며 XML 구성을 필요로하는 것처럼 보이며 일반적으로 전염병처럼 피하려고합니다. 그러나 내 인생을 더 쉽게 만들 수 있고 (특히) 개발과 테스트를 더 빠르게 할 수 있다면 총알을 물고 스프링을 배우려고합니다.

그러니 제발 저를 교육 시키십시오. Spring을 사용해야합니까? 그 이유는 무엇?


10
나는 당신이 그것을 좋아하고 당신의 프로젝트에 적합한 지 스스로 확인하기 위해 잠시 동안 그것을 시도해야한다고 생각합니다. 개인적으로 나는 그것을 싫어한다.
Richard

1
XML이나 주석을 사용할 수는 있지만; Spring은 구성 사고 방식에 대한 협약을 취합니다. 반드시 해결 해야 할 항목의 체크리스트는 아닙니다 .
Aaron McIver

14
이 질문이 다소 광범위하다는 것은 사실이지만, 계속 공개해야한다고 생각합니다. 나는 "Spring이 중간 규모 프로젝트에 어떤 이점을 제공 하는가?"라고 읽었으며 이는 좋은 질문입니다.
sleske

1
크레이그 월즈 (Craig Walls)의 Spring in Action, Third Edition 등 내가 좋아하는 기술 책을 읽는 것이 좋습니다. 잘 읽었으며 프로그래밍 방식을 바꿀 것입니다.
alfredaday

4
Enterprise Java를 고려하면 Spring이하지 않는 것에 대답하는 것이 더 쉽다 ...
m3th0dman

답변:


108

Spring 프레임 워크는 무엇을 하는가? 사용해야합니까? 그 이유는 무엇?

Spring은 다른 컴포넌트를 함께 "연결"하는 데 도움이되는 프레임 워크입니다. 구성 요소가 많고 구성 요소를 다른 방식으로 결합하기로 결정하거나 다른 설정 또는 환경에 따라 한 구성 요소를 다른 구성 요소로 쉽게 교체하려는 경우에 가장 유용합니다.

이것이 제가 항상 "종속성 주입"을 이해 한 것입니다.

다른 정의를 제안합니다.

" 사람들이 일상적인 작업을 시작하기 전에 이러한 종속성 이 항상 주입 될 것으로 기대하면서, 필요한 것을 공급하기 위해 외력에 의존하도록 물건을 설계하십시오 ."

"각 객체는 외출하고 모든 것이 시작될 때 필요한 모든 것을 찾는 책임이 있습니다."

전체 XML 구성이 필요한 것처럼 보입니다.

글쎄, 대부분의 XML (또는 주석 기반)은 Spring에 다음과 같이 말하고 있습니다.

  • 누군가 "HammerStore"를 요청하면 인스턴스를 생성 example.HammerStore하고 반환하기 를 원합니다 . 하나의 저장소 만 있으면되므로 다음에 인스턴스를 캐시하십시오.
  • 누군가 "SomeHammer"를 요청할 때 "HammerStore"를 요청하고 상점의 makeHammer()메소드 결과를 리턴하기를 원합니다 . 마십시오 하지 이 결과를 캐시합니다.
  • 누군가 "SomeWrench"를 요청할 때의 인스턴스를 만들고 싶습니다 example.WrenchImpl. 구성 설정을 사용 gaugeAmount하여 인스턴스의 setWrenchSize()속성에 넣습니다 . 결과를 캐시하지 마십시오.
  • 누군가 "LocalPlumber"를 요청할 때의 인스턴스를 만들고 싶습니다 example.PlumberImpl. "Pedro"라는 문자열을 setName()메소드에 넣고 "SomeHammer"를 setHammer()메소드에 넣고 "SomeWrench"를 setWrench()메소드에 넣습니다 . 배관공이 하나만 필요하므로 결과를 반환하고 나중에 결과를 캐시하십시오.

이러한 방식으로 Spring은 연결 컴포넌트를 연결하고 레이블을 지정하며 라이프 사이클 / 캐싱을 제어하며 구성에 따라 동작을 변경합니다.

[테스트]를 용이하게하기 위해 나는 일반적으로 두 가지 버전의 메소드를 작성한다. 하나는 인스턴스 변수를 사용하고 다른 하나는 메소드에 전달 된 변수 만 사용한다.

그것은 나에게 많은 이익이되지 않기 때문에 많은 오버 헤드처럼 들립니다. 대신 인스턴스 변수에 protected패키지 표시 또는 가시성 을 부여하고 동일한 com.mycompany.whatever패키지 내에서 단위 테스트를 찾으십시오 . 이렇게하면 테스트 중에 원하는 때마다 인스턴스 변수를 검사하고 변경할 수 있습니다.


65

첫째, 의존성 주입이란 무엇입니까?

단순한. 클래스가 있고 개인 필드가 있고 (널로 설정 됨) 해당 필드의 값을 제공하는 공용 세터를 선언합니다. 즉, 클래스 (필드)의 종속성은 외부 클래스 (세터를 통해)에 의해 주입됩니다. 그게 다야. 마법 같은 것은 없습니다.

둘째, Spring은 XML없이 사용할 수 있습니다 (또는 아주 적음)

Spring 3.0.5.GA 이상으로 다이빙하는 경우 JDK6 +의 의존성 주입 지원을 사용할 수 있습니다. 이는 @Component@Resource주석을 사용하여 종속성을 연결할 수 있음을 의미합니다 .

왜 스프링을 사용 하는가?

분명히, 의존성 주입은 모든 클래스가 중요한 의존성에 대한 세터를 가지고 있기 때문에 필요한 동작을 제공하기 위해 좋아하는 조롱 프레임 워크를 사용하여 쉽게 조롱 할 수 있으므로 매우 쉬운 단위 테스트를 촉진합니다.

그 외에도 Spring은 JEE 표준 기술을 사용하기가 쉬운 기본 클래스 역할을하는 많은 템플릿을 제공합니다. 예를 들어 JdbcTemplate은 JDBC와 잘 작동하고 JpaTemplate은 JPA와 잘 작동하며 JmsTemplate은 JMS를 매우 간단하게 만듭니다. RestTemplate은 단순함이 뛰어납니다. 예를 들면 다음과 같습니다.

RestTemplate restTemplate = new RestTemplate();
MyJaxbObject o = restTemplate.getForObject("https://secure.example.org/results/{param1}?param2={param2}",MyJaxbObject.class,"1","2");

그리고 당신은 끝났습니다. 매개 변수가 주입되며 MyJaxbObject에 대한 JAXB 주석 만 제공하면됩니다. Maven JAXB 플러그인을 사용하여 XSD에서 자동 생성 한 경우 시간이 전혀 걸리지 않습니다. 거기에는 캐스팅이 없었고 마샬 러를 선언 할 필요도 없었습니다. 모두 당신을 위해 완료되었습니다.

Spring의 경이로움에 대해 영원히 깨달을 수는 있지만 가장 좋은 방법은 RESTful 웹 서비스를 연결하여 트랜잭션을 지원하는 주입 된 DAO에서 데이터를 펌핑하는 간단한 코드 스파이크를 시도하는 것입니다.


4
네 RestTemplate은 정말 대단합니다. 버려서 2-3 줄로 바꿀 수있는 100 줄의 코드가 있습니다.
Kevin

11
nitpick : Dependency Injection에는 생성자 기반 접근 방식도 포함됩니다. 세터가 반드시 필요한 것은 아닙니다.
Darien

28

우선, 의존성 주입에 대한 당신의 이해는 근본적으로 잘못되지는 않지만 대부분의 사람들이 그 용어를 사용할 때 의미하는 것과는 상당히 다릅니다. 설명하는 것은 테스트 가능성을 달성하는 다소 이상하고 비 전통적인 방법입니다. 다른 개발자들은 그런 종류의 코드에 당황 할 것이므로 다른 곳으로 이동하지 않는 것이 좋습니다.

일반적으로 이해되고 스프링에 의해 구현되는 종속성 주입은 클래스가 갖는 종속성 (예 : JDBC 데이터 소스)이 클래스 자체에 의해 페치되지 않고 인스턴스가 작성 될 때 컨테이너에 의해 "주입"됨을 의미합니다. 따라서 데이터 소스를 사용하는 모든 메소드의 두 가지 버전이 없습니다. 대신, "실제"데이터 소스가 주입되고 모의가 주입되는 의존성 주입 구성이 있습니다. 또는 생성자 또는 getter를 통해 주입이 발생하면 테스트 코드가 주입을 명시 적으로 수행 할 수 있습니다.

둘째, Spring은 단지 의존성 주입이 아니라 핵심 기능입니다. 또한 선언적 트랜잭션, 작업 예약, 인증 및 필요한 기타 기능 (완전한 MVC 웹 프레임 워크 포함)을 제공합니다. 동일한 기능을 제공하는 다른 프레임 워크가 있지만 Spring 이외의 다른 프레임 워크에는 Java EE 만 통합되어 있습니다.


OP는 DI를 완벽하게 이해합니다
Basilevs

19

왜 Spring을 사용하고 싶습니까? http://www.wrox.com/WileyCDA/Section/Why-Use-the-Spring-Framework-.id-130098.html 확인할 .

요약하자면 :

  • J2EE 응용 프로그램에는 과도한 양의 "배관"코드가 포함되는 경향이 있습니다. 많은 코드 검토에서 JNDI 조회 코드, 객체 전송, 시도 / 캐치 블록을 사용하여 JDBC 리소스를 획득 및 해제하는 작업을 수행하지 않는 많은 비율의 코드가 반복적으로 나타납니다. . . . 이러한 배관 코드를 작성하고 유지 관리하면 응용 프로그램의 비즈니스 영역에 중점을 두어야하는 리소스가 크게 소모됩니다.

  • 많은 J2EE 응용 프로그램은 이것이 부적절한 분산 오브젝트 모델을 사용합니다. 이는 과도한 코드 및 코드 복제의 주요 원인 중 하나입니다. 또한 많은 경우 개념적으로 잘못되었습니다. 내부적으로 분산 된 응용 프로그램은 함께 배치 된 응용 프로그램보다 복잡하고 성능이 훨씬 떨어집니다. 물론 비즈니스 요구 사항에 따라 분산 아키텍처가 필요한 경우 분산 아키텍처를 구현하고 발생하는 타협을 수용해야합니다 (Spring은 이러한 시나리오에서 도움이되는 기능을 제공합니다). 그러나 당신은 설득력있는 이유없이 그렇게해서는 안됩니다.

  • EJB 컴포넌트 모델은 지나치게 복잡합니다. EJB는 J2EE 애플리케이션에서 비즈니스 로직을 구현할 때 복잡성을 줄이는 방법으로 고안되었습니다. 실제로이 목표에서 성공하지 못했습니다.

  • EJB가 과도하게 사용되었습니다. EJB는 기본적으로 내부적으로 분산 된 트랜잭션 응용 프로그램을 위해 설계되었습니다. 거의 모든 사소한 응용 프로그램은 트랜잭션 방식이지만 배포는 기본 구성 요소 모델에 내장되어서는 안됩니다.

  • 많은 "J2EE 디자인 패턴"은 실제로 디자인 패턴이 아니라 기술 제한에 대한 해결 방법입니다. 배포의 남용과 EJB와 같은 복잡한 API의 사용은 많은 의심스러운 디자인 패턴을 생성했습니다. 이를 비판적으로 조사하고 더 단순하고 생산적인 접근 방식을 찾는 것이 중요합니다.

  • J2EE 응용 프로그램은 단위 테스트가 어렵습니다. J2EE API, 특히 EJB 구성 요소 모델은 민첩한 움직임이 시작되기 전에 정의되었습니다. 따라서 그들의 디자인은 단위 테스트의 용이성을 고려하지 않습니다. API와 암시 적 계약을 통해 EJB 및 응용 프로그램 서버 외부의 다른 많은 J2EE API를 기반으로 응용 프로그램을 테스트하는 것은 놀랍습니다. 그러나 응용 프로그램 서버 외부의 단위 테스트는 높은 테스트 적용 범위를 달성하고 데이터베이스 연결 손실과 같은 많은 오류 시나리오를 재현하는 데 필수적입니다. 또한 개발 또는 유지 관리 프로세스 중에 테스트를 신속하게 실행하여 재배치를 기다리는 비생산적인 시간을 최소화하는 것이 중요합니다.

  • 특정 J2EE 기술은 단순히 실패했습니다. 여기서 주범은 엔티티 빈 (Entity Bean)으로 생산성과 개체 방향에 대한 제약이 거의없는 것으로 입증되었습니다.


13

이전에는 핵심 Java, 서블릿 및 JSP, html 및 xml, JDBC API를 사용하여 간단하고 효율적이며 빠른 응용 프로그램 및 웹 서비스를 작성했습니다. 충분했다; JUnit은 테스트하기에 좋은 도구였습니다. 우리는 코드가 작동하기 쉬웠다.

Hibernate는 SQL을 단순화하고 Java 객체와 데이터베이스 테이블의 진정한 매핑을 가능하게하여 계층 적 관계가 호출 될 때 ORM (Object Relations Mapping)에 반영되도록했다. 나는 그것을 좋아했다. 특히 ResultSet을 Java 객체 또는 데이터 유형으로 다시 매핑 할 필요가 없었습니다.

Struts는 Model View Controller 패턴을 웹 앱에 추가하기 위해 나왔습니다.

EJB는 엄청난 오버 헤드와 고통이었고 주석은 코드를 치킨 스크래치처럼 보이게 만들었고 이제 Spring은 우리에게 무고한 사람들을 낳았습니다. 그냥 나에게 지나친 것 같습니다.

예를 들어, 간단한 jdbc url, user를 먼저 패키지 한 다음 jdbc.properties에 먼저 전달한 다음 최대 절전 모드 속성에 두번째로 전달한 다음 Spring bean에 세 번째로 전달합니다!

이 모든 것에 비해, 필요한 곳에 연결하는 것이 순수 자바에서 아래에 표시된 것처럼 간단하다는 것을 고려하십시오. 이것은 Spring으로 모든 뜨거운 공기를 통과 한 후에 실제로하는 것입니다.

Connection connection = DriverManager.getConnection(url, user, pass);

그것은 그 자체로 그 자체로 설명이 필요합니다. 큰 둥근 주위를 감싸고 둥근 것을 감싸는 것은 실제로 다른 큰 이점없이 간단한 일을 빠르고 쉽게 할 수 있다는 것입니다. 그것은 당신이 정말로 보관하는 작은 멋진 선물 주위에 톤과 선물 종이를 포장하는 것과 같습니다.

다른 예는 일괄 업데이트입니다. Spring에서는 JdbcTemplate을 사용하여 일괄 업데이트를 수행하기 전에 인터페이스와 클래스가 상당히 복잡합니다. 일반 jdbc를 사용하면 간단합니다.

Statement statement = connection.createStatement();
statement.addBatch(sqlquery);
statement.executeBatch();

그것보다 간단하거나 빠를 수 없습니다.

이 프레임 워크를 지원하지 않습니다. 죄송합니다. 세상에서 누가 필요할 때마다 주사를 원합니까?


6
이 코드는 간단하지만, 전이 처리는 어디에 정의되어 있으며 어떻게 테스트합니까? 봄에는 두 가지가 더 단순 해졌습니다. 또한 연결 URL과 암호를 외부에 저장하지 않으면 Java 코드가 DB 연결에 직접 연결됩니다.
NimChimpsky

이 답변에 +1, 위의 의견을 해결하기 위해 사용자는 자신의 경량 디자인 패턴 (GOF), 연결 풀링을위한 싱글 톤, 데이터베이스 객체에 SQL (문자열) 및 값 (배열)을 제공하는 프록시 클래스를 사용합니다 (http 메소드에 따라 다름), dbobject는 연결 풀링, 트랜잭션 등을 실행하여 쿼리를 실행 한 다음 릴리스를 처리합니다. 모든 모듈에서 하나의 객체를 사용하므로 과도한 보일러 플레이트 코드가 필요하지 않습니다. 프록시 클래스 및 dbobject에서 단위 테스트를 설정하는 보너스 포인트.
user2727195

Spring-Data-JPA를 확인하십시오! 엔티티에 pojo에 주석을 달고 인터페이스를 구현하고 findHammerByWeight ()와 같은 적절한 이름을 사용하여 메소드 서명을 정의하고 spring은 메소드를 구현하여 다른 비즈니스 서비스 또는 컨트롤러 클래스 모두에서 사용할 수있는 주사 가능한 저장소를 제공합니다. .
mancini0

13

Spring 프레임 워크는 무엇을 하는가?

봄은 오늘날처럼 단순한 프레임 워크로 알려진 것이 아니라 완전한 생태계입니다.

봄 생태계에서 다루는 주제 :

  • 스프링 프레임 워크 (예 : 의존성 주입, AOP ...)

  • 봄 구름

  • 스프링 데이터

  • 봄 보안

  • 스프링 배치

  • 봄 사회

생태계에 대한 전체 내용은 여기 를 참조 하십시오 . 프로젝트를 신중하게 선택 하여 DI와 같은 Google Guice 와 Spring Security를 ​​사용하여 보안 관련 사항을 처리 할 수 있습니다 . 당신은 전체 생태계를 구매할 필요가 없습니다.

스프링 프레임 워크 자체는 오늘날 주로 다루고 있습니다.

  • 의존성 주입

  • Spring의 선언적 트랜잭션 관리를 포함한 Aspect-Oriented Programming

  • Spring MVC 웹 애플리케이션 및 RESTful 웹 서비스 프레임 워크

  • JDBC, JPA, JMS에 대한 기본 지원

소스 spring.io

일반적으로 Spring은 코드에서 구현 된 패턴 및 연습 모음으로, 애플리케이션 개발주기를 개선하거나 가속화하는 데 도움이 될 수 있습니다.

그것 (핵심 프레임 워크)이 가장 잘 알려진 것은 의존성 주입 분야의 기능입니다 . 스프링 자체에는 컨트롤 컨테이너의 반전 또는 짧은 IoC 컨테이너 또는 더 짧은 컨테이너가 있습니다. ( "스프링"이 때때로 동의어로 사용됨)가 있습니다.

의존성 주입이란 무엇입니까?

종속성 주입은 개체가 외부화 된 메커니즘을 통해 다른 개체에 대한 모든 종속성을 받는다는 것을 의미합니다.

예를 들어, 자동차가 있으며 일반적인 방법은 다음과 같습니다.

public class Car {

    Engine e;

    public Car() { 
        e = new Engine(); 
    }

}

자동차 개체는 엔진에 따라 다릅니다. 엔진은 자동차의 멤버로 구현되므로 테스트 엔진 등으로 교체 할 수 없습니다.

이제 의존성 주입 이 작동합니다.

public class Car {

    Engine e;

    public Car(Engine e) { 
        this.e = e; 
    }

}

그 후에는 엔진을 전환 할 수 있습니다. 위에서 본 것을 생성자 주입 이라고 합니다. setter -injection 또는 method -injection 과 같은 다른 유형이 있습니다 . Spring이 어떻게 도와 주나요? Spring은 주석이 주입 된 구성 요소를 표시 할 수있게하고 주입 된 @Autowired오브젝트의 배선을 자동으로 수행합니다. 주입하려는 구성 요소 자체에 종속성이있을 가능성이 높습니다. 즉, 주사 가능 물질은 다음을 통해 표시됩니다.@Component

public class Car {

    Engine e;

    @Autowired
    public Car(Engine e) { 
        this.e = e; 
    }

}

그러나 이것은 Spring이 제공해야하는 많은 기능 중 하나 일뿐입니다.

Spring을 사용해야합니까? 그 이유는 무엇?

스프링은 그다지 방해가되지 않으며 스프링 사용을 고려해야하는 많은 보조 도구를 제공합니다. 특히 새 프로젝트의 경우 Spring Boot 가 매우 매력적입니다. start.spring.io는 쉬운 사용합니다 point'n'click 시작하는 프로젝트 템플릿을 생성 할 수 -interface을. curl템플릿을 검색하는 데 사용할 수도 있습니다 .

curl start.spring.io

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

:: Spring Initializr ::  https://start.spring.io

This service generates quickstart projects that can be easily customized.
Possible customizations include a project's dependencies, Java version, and
build system or build structure. See below for further details.

The services uses a HAL based hypermedia format to expose a set of resources
to interact with. If you access this root resource requesting application/json
as media type the response will contain the following links:
+-----------------+-----------------------------------------+
| Rel             | Description                             |
+-----------------+-----------------------------------------+
| gradle-build    | Generate a Gradle build file            |
| gradle-project  | Generate a Gradle based project archive |
| maven-build     | Generate a Maven pom.xml                |
| maven-project * | Generate a Maven based project archive  |
+-----------------+-----------------------------------------+

...

반면에 spark 또는 dropwizard 와 같은 프레임 워크 는 빠른 웹앱 생성을위한 좋은 출발점을 제공합니다.


1
매우 유익한 답변!
GOXR3PLUS

동의, 아주 좋은 대답. OP, 나는 당신에게 동정합니다. 나는 또한 몇몇 사람들에게 XML 파일과 간접적으로 2 개의 레이어를 추가하여 코드를 "단순화"하는 스프링 데모를 보여 주었으므로 클래스에서 생성자를 호출 할 수 있습니다. 프리젠 테이션 정말 좋은 장점과 단점이 어디에 결국은 분명히 될 것입니다
아담 휴즈

4

웹 응용 프로그램을 기능적으로 만들기 위해 많은 것들이 포함 된 Java로 작성된 프레임 워크입니다 (예 : 국제화 지원). 또한 응용 프로그램을 계층으로 구성하는 좋은 방법을 제공합니다. 그것을 사용하면 장기적으로 많은 시간을 절약 할 수 있습니다.

Spring에 대해 배우기에 좋은 책은 Expert Spring MVC와 Web Flow입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.