说到spring,那么什么是spring?一句话概括,就是一个Java的一个开源框架。目的是:简化Java开发。核心是DI和AOP(依赖注入、面向切面编程)。在spring中,用bean 来表示应用组件。
什么是DI(依赖注入)?:在传统的Java方法。A类的A1方法调用B类的B1方法,那个B对应的Java类需要实例化变成一个对象来调用(这里排除static声明的类)。但是有了DI之后,不用实例化了,spring容器自动给你实例化了。你需要注入该对象,然后直接使用。
spring的DI(依赖注入)简单理解了,那么你一定很好奇,什么东西可以管理这些bean ,以至于你都不用实例化对象?答案就是spring容器。当然spring有很多容器,他的作用就是管理这些个时而被需要,时而被抛弃的bean。现在基本上会用应用上下文容器比较多一些,bean工厂会少一些,因为他比较简单。具体容器是怎么做到的,这里不展开,因为spring已经封好了,我们就直接用吧。
还有一点你可能会有疑问,容器只是负责管理这些bean,那么什么样的对象可以被创建为bean。如果没有这些对象那么就无法创建bean。他也没有办法去管理啊。在spring中,用bean 来表示应用组件,那么引出配置一个组件。这里大致可以分为3种方式来配置bean。自动化装配bean,通过Java代码来配置bean。通过配置XML方式来配置bean。当然也可以混合配置。
- 自动换装配bean。这种方式真棒,自动化,自然需要配置的东西就越来越少。怎么配置呢?这里引用《spring实战》的例子。
package soundsystem; public interface CompactDisc { void play(); }
import org.springframework.stereotype.Component; /** *@Component: *1.表明该类会作为组件类, *2.并告诉spring要为这个类创建bean,这个bean的ID默认是类名(该类名的第一个字母小写),若改名,则@Component("xxx")或者 * @Named("xxxx") * * */ @Component public class SgtPeppers implements CompactDisc { private String title = "Sgt.Pepper's Lonely Hearts Club Band"; private String artist = "The Beatles"; @Override public void play() { System.out.println("Playing "+title+" by "+artist); } }
package soundsystem; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; /** * @ComponentScan: * 1.默认扫描和配置类相同的包以及该包的子包 * 2.指定扫描的包@ComponentScan("soundsystem"); * 3.@ComponentScan(basePackages="soundsystem"); * 4.@ComponentScan(basePackages={"soundsystem","soundsystem2"}); * 注意:2,3,4中的方法设置基础包是String类型的,安全性角度来说是不安全的,建议是采用组件的方式; * @ComponentScan(basePackagesClasses={soundsystem.calss,soundsystem2.class}); */ @Configuration//通过Java代码定义了spring的装配规则, @ComponentScan//在spring中启动组件扫描 public class CDPlayerConfig { }
这里,用到了@Component注解,@Configuration注解,@ComponentScance注解。那么具体让我们看看这个DI到底是怎么做到不用实例化对象的呢。
代码介绍:SgtPeppers 类实现CompactDisc接口,来打印一条信息。就是简单一个需求。那么需要怎么操作呢?看下面的JUnit测试方法。
package soundsystem; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes=CDPlayerConfig.class) public class CDPlayerTest { /** * @Autowired:声明自动装配,让spring自动满足bean依赖的一种方法 * 换句话来说,就是通过@Autowired来实例化并且会传入一个一可设置的bean * 为了避免没有找到该bean,一般写明是否必要 * @Autowired(required=false),spring在实现自动装配的时候,若没有匹配的bean,spring会让这个bean处于未匹配状态 * 若有多个bean可匹配的请求,spring会抛出异常 * @Autowired 同@Inject */ @Autowired private CompactDisc cd; @Autowired private CompactDisc player; // @Test // public void cdShouldNotBeNull(){ // assertNotNull(cd); // } @Test public void play(){ player.play(); } }
@Autowired这里又用到了这个注解。看到JUnit测试类,这里我们没有实例化对象。这个play方法可以测试通过。会打印出来那么一句话。。。。(你懂的)。具体他是怎么做到的?
@Component:带该注解,那么就是告诉容器,我这个类需要bean。那么容器就给他一个首字母小写的一个bean。这里就是sgtPeppers。那么容器去哪里找这个bean呢?看到@ComponentScan,他去扫描组件。去哪里扫描呢,默认是本包(包括子包)。
@ComponentScan扫描本包(包括子包)找到带@Component的类,然后来创建bean。有了bean,当需要这个bean的时候,容器自动创建他,不需要时则被回收。这里需要调用play方法,使用@Autowired方法自动装配完成,直接调用,无需实例化对象。
@ComponentScan使用的前提是,必须要有@Configuration。
- 第二种,java代码来配置bean。
Java代码,感觉应该也还好,至少会在配置第三方组件的时候会用。这里我去掉了@ComponentScan注解,用代码来创建了Bean ,而不是通过@Component组件了。
package soundsystem2; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration//通过Java代码定义了spring的装配规则, public class CDPlayerConfig { /** * 声明简单的Bean *@Bean:告诉spring这个方法返回一个对象,该对象要注册为spring应用上下文中的bean。bean默认ID是方法名即 sgtPeppers * 更改bean id @Bean(name="xxxx") */ @Bean public CompactDisc sgtPeppers(){ return new SgtPeppers(); } }
这里用到@Bean注解。JUnit测试结果是一样的。就是这么简单。
- 第三种,xml方式配置bean。当然需要先创建一个Spring Bean Definition file(spring的配置文件)。这里需要bean 类是soundsystem2.SgtPeppers,所以把他配置bea。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" 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.xsd"> <bean id="sgtPeppers" class="soundsystem2.SgtPeppers"></bean> </beans>
JUnit测试通过。
package soundsystem2; import static org.junit.Assert.assertNotNull; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) //@ContextConfiguration(classes=CDPlayerConfig.class) @ContextConfiguration(locations="classpath:soundsystem2/springxml.xml") public class CDPlayerTest { /** * @Autowired:声明自动装配,让spring自动满足bean依赖的一种方法 * 换句话来说,就是通过@Autowired来实例化并且会传入一个一可设置的bean * 为了避免没有找到该bean,一般写明是否必要 * @Autowired(required=false),spring在实现自动装配的时候,若没有匹配的bean,spring会让这个bean处于未匹配状态 * 若有多个bean可匹配的请求,spring会抛出异常 * @Autowired 同@Inject */ // @Autowired // private CompactDisc cd; @Autowired private SgtPeppers player; // @Autowired // private CDPlayer cdPlayer; // @Test // public void cdShouldNotBeNull(){ // assertNotNull(cd); // } @Test public void play(){ player.play(); } }
就是这么简单。当应该程序比较复杂的时候,可想而知,通过这种方式配置bean可能会有点小崩溃。
下面理解一下,spring中的aop。什么是AOP(面前切面编程)?其实就是AOP作用就是:把分散在各个地方的功能分离出来形成组件。(这里总是出现组件这个词,那么组件是什么?简单理解他就是一个Java类。)
spring侧重于提供一种AOP实现和Spring IoC容器之间的整合,用于帮助解决在企业级开发中的常见问题。spring具体实现AOP原理这里不展开。从spring层面来应用AOP。通过我们还是讲注解方式。具体应用,想看下面的代码。
package concert; public interface Performance { public void perform(); }
package concert; import org.springframework.stereotype.Component; @Component public class Performance2 implements Performance { @Override public void perform() { System.out.println("joy "); } }
需求很简单,就是去看一个演出。perform()方法就是一个演出。那么在演出之前。我们也要去找座位。找完座位后,关闭手机等待演出开始。然后再是开始演出。如果演出没有意外,那么就鼓掌。否则退票。演出方法就有了。也就是 先执行“找座位方法 关闭手机等待演出” ;接下来执行perform()方法;然后执行 “鼓掌” or "退票方法";
那么我们可以通过AOP这么做:(这里需要两个第三方jar:aopalliance-.jar和 aspectj-weaver.jar)。这里需要解释一下SpringAop与AspectJ(一个基于Java语言的AOP框架)的联系与区别。这里我们所讲的是Spring集成AspectJ的注解模式。
1.@Aspect表示一个切面。先创建一个切面。
2.创建切点@Pointcut("execution(** concert.Performance.perform(..))")设置 concert.Performance.perform方法(任意参数的该方法)为切点。
3.创建通知。@Around("performance()") performance()指向的是设置切点的方法。当执行测试类的时候控制台打印3行文字,也就实现了我们这个需求。具体是什么,请你试一下。
注:通知可以分为很多种。切点的设置也有很多种,这里为了单纯的理解AOP,不做展开。附上spring 的文档地址,供读者查看(https://docs.spring.io/spring/docs/4.3.12.RELEASE/spring-framework-reference/htmlsingle/)。
附上:
package concert; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Aspect @Component public class Audience { // //设置切点 @Pointcut("execution(** concert.Performance.perform(..))") public void performance(){ } /** *设置环绕通知 */ @Around("performance()") public void watchPerformance(ProceedingJoinPoint jp){ System.out.println("Silencing cell phones"); try { jp.proceed(); System.out.println("clap"); } catch (Throwable e) { System.out.println("Demanding a refund"); } } }
配置文件:
package concert; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration @EnableAspectJAutoProxy @ComponentScan public class ConcertConfig { }
如果你选择 xml 配置方式 一下配置供参考 :
<aop:aspectj-autoproxy proxy-target-class="true" /> <bean id="audience" class="concert.Audience"></bean>
测试类:
package concert; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes=ConcertConfig.class) public class Test { @Autowired private Performance performance; @org.junit.Test public void test() { performance.perform(); } }
简单实现功能后,你会不会有这么疑问?既然配置都是通过方法来配置,是不是可以传递参数。当然是可以的。
package concert; public interface Performance { public void perform(); public void perform(int a); }
package concert; import org.springframework.stereotype.Component; @Component public class Performance2 implements Performance { @Override public void perform() { System.out.println("joy "); } @Override public void perform(int a) { System.out.println("joy 2...."); } }
//设置切点 @Pointcut("execution(** concert.Performance.perform(..))") public void performance(){ } /** *设置环绕通知 *效果同上是一样的 */ @Around("performance()") public void watchPerformance(ProceedingJoinPoint jp){ System.out.println("Silencing cell phones"); try { jp.proceed(); System.out.println("clap"); } catch (Throwable e) { System.out.println("Demanding a refund"); } } /** *设置切点 * concert.Performance.perform(int),该方法的int参数也会传递到通知中去 * args(a):指定参数a */ @Pointcut("execution(* perform*(..))&& args(a)") public void performance2(int a){ } /** *设置环绕通知 *效果同上是一样的 */ @Around("performance2(a)") public void watchPerformance2(ProceedingJoinPoint jp,int a){ System.out.println("Silencing cell phones and param:"+a); try { jp.proceed(); System.out.println("clap and param:"+a); } catch (Throwable e) { System.out.println("Demanding a refund"); } }
package concert; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes=ConcertConfig.class) public class Test { @Autowired private Performance performance; @org.junit.Test public void test() { performance.perform(); performance.perform(2); } }
测试结果中应该是带有参数的。
AOP还可以通过注解来引入新功能。比如我想引入这个功能:
package concert; import org.springframework.stereotype.Component; @Component public class OtherFunctionImpl implements OtherFunction{ @Override public void eat() { System.out.println("eat"); } }
在切面中增加该功能。
package concert; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.DeclareParents; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Aspect @Component public class Audience { //设置切点 @Pointcut("execution(** concert.Performance.perform(..))") public void performance(){ } /** *设置环绕通知 *效果同上是一样的 */ @Around("performance()") public void watchPerformance(ProceedingJoinPoint jp){ System.out.println("Silencing cell phones"); try { jp.proceed(); System.out.println("clap"); } catch (Throwable e) { System.out.println("Demanding a refund"); } } /** *设置切点 * concert.Performance.perform(int),该方法的int参数也会传递到通知中去 * args(a):指定参数a */ @Pointcut("execution(* perform*(..))&& args(a)") public void performance2(int a){ } /** *设置环绕通知 *效果同上是一样的 */ @Around("performance2(a)") public void watchPerformance2(ProceedingJoinPoint jp,int a){ System.out.println("Silencing cell phones and param:"+a); try { jp.proceed(); System.out.println("clap and param:"+a); } catch (Throwable e) { System.out.println("Demanding a refund"); } } /** * @DeclareParents 注解由3部分构成 * 1.value:指向了哪种类型的bean要引入该接,这里就是performance 接口(及其子接口) * 2.defaultImpl 引入实现类。 * 3.属性部分,引入接口 */ @DeclareParents(value="concert.Performance+",defaultImpl=OtherFunctionImpl.class) public static OtherFunction otherFunction; }
测试类:
package concert; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes=ConcertConfig.class) public class Test { @Autowired private Performance performance; @org.junit.Test public void test() { performance.perform(); performance.perform(2); OtherFunction e = (OtherFunction) performance; e.eat();; } }