zoukankan      html  css  js  c++  java
  • Spring 简介

    Spring 简介

    Spring 基于 IOC(Inversion Of Control) 和 DI(Dependency Inject)。Spring 出现以前,类对象是用户手动创建的(例如使用 new)与组装的,而使用 Spring 框架后类对象将由 Spring 框架生成并由 Spring 控制对象的生命周期

    graph TB A[Spring] B[上下文] C[4类Scope] D[Component<br/>ComponentScan] E[Bean<br/>Primary<br/>Autowired] F[Profile<br/>Conditional] G[Value<br/>SpEL] A --> B B --> C A --> D A --> E A --> F A --> G

    Spring 组成

    Spring 上下文

    在基于 Spring 的应用中,对象生存于 Spring 容器(container)中,Spring 容器负责创建、装配、配置并管理对象的整个生命周期,从创建到死亡

    Spring 容器归为两大类型:Bean 工厂和应用上下文,常用的是 Spring 上下文。Spring 自带了多种类型的应用上下文:

    • AnnotationConfigApplicationContext
      • 从 Java 配置类中加载 Spring 应用上下文
      • AnnotationConfigApplicationContext(com.springinaction.knights.config.KnightConfig.class);
    • AnnotationConfigWebApplicationContext
      • 与上同,用于加载 web 配置
    • ClassPathXmlApplicationContext
      • 默认从所有已知路径下查找并载入 xml 配置文件
      • 给出文件名即可: ClassPathXmlApplicationContext("knight.xml");
    • FileSystemXmlapplicationcontext
      • 从文件系统中的 xml 配置文件中加载上下文
      • 需明确指明文件位置: FileSystemXmlApplicationContext("c:/knight.xml");
    • XmlWebApplicationContext

    Bean 作用域

    默认情况下,Spring 应用上下文中的所有 bean 都以单例(singleton) 模式创建。有时候,你所使用的类是易变的(mutable),它们会保持一些状态,因此重用是不安全的。Spring 提供了几种作用域,可以按照需求来创建 bean

    • 单例(Singleton),默认作用域,只创建一个 bean 实例
    • 原型(Prototype),每次注入或者通过 Spring 上下文获取时会创建一个新的 bean 实例
    • 会话(Session),web 应用中,为每一个会话创建一个 bean
    • 请求(Rquest),web 应用中,为每一个请求创建一个 bean

    @Scope

    使用 @Scope 注解可以设置 bean 的作用域:

    @Component // @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    // @Scope("prototype"),使用上面那行的方式更安全一些
    public class Notepad { ... }
    

    xml 配置形式如下:

    <bean id="notepad" class="com.myapp.Notepad" scope="prototype" />
    

    Spring 依赖注入

    Spring 可以使用配置文件或者注解来实现依赖注入,下面以配置文件形式和注解形式来简单解释 IOC 与 DI

    配置文件形式(XML)

    需要 Spring 进行管理的对象:

    public class Category {
        private int id;
        private String name;
        
        // getter & setter func...
    }
    
    public class Product {
        private int id;
        private String name;
        private Category category;
        
        // getter & setter func...
    }
    

    Spring 配置文件(IOC & DI):

    <?xml version=...
        <!-- 同时指定 name 和 id,此时 id 为标识符,而 name 为 Bean 的别名,两者都可以找到目标Bean -->
    	<bean id="category" name="c" class="xyz.yearn.Category">
            <property name="name" value="category 1" />
        </bean>
        <bean name="p" class="xyz.yearn.Product" primary="true">
            <property name="name" value="product1" />
            <property name="category" ref="c" />
        </bean>
    </beans>
    

    位于 <bean> 标签内的配置用于声明 bean

    从 Spring 上下文获取对象

    public class TestSpring {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext(
                    new String[] { "applicationContext.xml" });
     		// 对象 p 中的 category 由 name 为 c 的 bean 自动进行填充:ref="c",这是 DI
            Product p = (Product) context.getBean("p"); // IOC,xml 配置文件中创建了一个 name 为 p 的对象
            System.out.println(p.getName());
            System.out.println(p.getCategory().getName());
        }
    }
    

    注解形式

    @Component & @Bean

    使用 @Component 告知 Spring 在进行组件扫描(@ComponentScan)时需要为哪些类创建 bean

    // Spring 将为使用 Component 注解的类创建对象(bean)
    // 每一个 Bean 都有一个 ID,默认 ID 为类首字母小写,可以指定,如下 p
    @Component("p") // 大部分场景下可以使用 @Named 替换 Component
    @Primary // 有歧义时,标识首选bean
    public class Product {
        ...
    }
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration// 表名这是一个配置类,包含创建 bean 的细节
    @ComponentScan(basePackages={"soundsystem", "video"}) // 组件扫描默认是不开的,需要使用这个注释打开
    // @ComponentScan(basePackageClasses={CDPlayer.class, DVDPlayer.class})
    // @Import(CDPlayerConfig.class)
    // @ImportResource("classpath:cd-config.xml")
    public class CDPlayerConfig {
        // 默认情况下, bean的ID与带有@Bean注解的方法名是一样的
        // 因为 Bean 注解的存在,Spring 会拦截所有下面函数的调用,返回相同的对象实例
        @Bean(name="lonelyHeartsClubBand") // 告知 Spring 当前函数返回的对象需要注册为 bean
        @Primary
    	public CompactDisc sgtPeppers() {
    		return new SgtPeppers();
    	}
    }
    

    自动装配(DI)

    // 使用 Autowired 注解,Spring 自动将合适的 bean 绑定到变量
    @Autowired // 变量注解,可以使用 @Inject 替换 Autowired
    @Qualifier("cc1") // 只有 id 为 cc1 的 bean 才会注入 category 中
    private Category category;
    
    @Autowired // 函数注解,Autowired 按照类型进行装配
    // @Resource,按照 name 属性进行装配
    // @Autowired(required=false),自动装配失败不抛异常
    public void setCategory(Category category) {
        this.category = category;
    }
    

    条件装配

    Spring 可以在运行时根据环境变量选择合适的 Beans,这样在测试或上线时就不需要重新构建程序

    @Profile

    Spring 中可以使用 Profile 注解指定 bean 属于哪个 profile,@Profile 注解可以同时置于方法(Spring 版本需要大于 3.2)和类

    @Configuration
    @Profile("dev")// 控制配置类生效环境
    public class DevelopmentProfileConfig {
    	@Bean(destroyMethod="shutdown")
    	public DataSource dataSource() {
            ...
    
    XML 形式
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    ...
    profile="dev">
        
    	<jdbc:embedded-database id="dataSource">
    	...
    	</jdbc:embedded-database>
    </beans>
    

    可以在 <beans> 元素中嵌套 <beans>,并指定不同的 profile

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    ...>
        <beans profile="dev">
    		<jdbc:embedded-database id="dataSource">
    		...
    		</jdbc:embedded-database>
        </beans>
        
        <beans profile="prod">
    		<jee:jndi-lookup id="dataSource">
    		...
        </beans>
    </beans>
    

    激活 profile

    Spring 会依次查找 spring.profiles.activespring.profiles.default 这两个变量来确定哪个 profile 处于激活状态,如果二者都未定义,则 Spring 只会创建那些没有定义在 profile 中的 bean。

    可以同时激活多个不同的 profile

    有很多种方式配置上面两个属性,例如在 web.xml 中

    <?xml ...>
    <web-app version="2.5" ...>
    ...
    <context-param>
    	<param-name>spring.profiles.default</param-name>
    	<param-value>dev</param-value>
    </context-param>
    ...
    

    在生产环境可以通过配置 spring.profiles.active 环境变量实现环境切换

    @ActiveProfiles

    测试环境可直接使用注解激活对应的 profile

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes={PersistenceTestConfig.class})
    @ActiveProfiles("dev") // 直接激活指定 profile
    public class PersistenceTest {
    	...
    }
    

    @Conditional

    @Conditional 注解,它可以用到带有 @Bean 注解的方法上。如果给定的条件计算结果为 true,就会创建这个 bean,否则的话, 这个 bean 会被忽略

    歧义消除

    • @Primary,标识首选 bean
    • @Qualifier("iceCream") ,这类方法比较灵活,具体使用时还需查询文档

    运行时注入

    Spring 提供了两种在运行时求值的方式

    • 属性占位符(Property placeholder)
    • Spring 表达式语言(SpEL)

    @PropertySource & Env

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.PropertySource;
    import org.springframework.core.env.Environment;
    
    @Configuration
    @PropertySource("classpath:/xyz/yearn/app.properties")
    public class Test{
        @Autowired
        Environment env;
        
        @Bean
        public BlankDisc disc()
        {
            // getProperty 有多种变种,用时请查 API
            return new BlankDisc(env.getProperty("disc.title"), env.getProperty("disc.artist"));
        }
    }
    
    // app.properties 的内容如下
    // disc.title=Sgt.Peppers Lonely Hearts Club Band
    // disc.artist=The Beatles
    

    属性占位符 @Value

    占位符使用 ${...} 包装属性名称,使用属性占位符需要先配置 PropertySourcesPlaceholderConfigurer

    <context:property-placeholder />
    
    <bean id="sgtPeppers" class="soundsystem.BlankDisc" c:_title="${disc.title}" c:_artist="${disc.artist}"/>
    
    @Value
    @Bean
    public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
    	return new PropertySourcesPlaceholderConfigurer();
    }
    
    public BlankDisc(@Value("${disc.title}") String title,
    				 @Value("${disc.artist}") String artist) 
    {
    	this.title = title;
    	this.artist = artist;
    }
    

    SpEL

    SpEL 是 Spring 提供的内嵌脚本语言,SpEL 需要放置到 #{...} 之中,示例

    • #{sgtPeppers.artist}
      • SpEL 可以引用 bean 属性
    • #{T(System).currentTimeMillis()} & T(java.lang.Math).random()
      • T() 表达式会将 Java.lang.System 视为 Java 中对应的类型,因此可以调用其 static 方法
    • #{systemProperties['disc.title']}
    • 通过 systemProperties 对象引用系统属性
    • #{2 * T(java.lang.Math).PI * circle.radius}

    和常见脚本语言一样,SpEL 可以计算十分复杂的计算,这部分内容请参考其他文档

    面向切面编程(AOP)

    AOP (Aspect Oriented Program)即面向切面编程。使用 AOP 可以剥离核心业务与辅助功能代码,减少业务程序员的心智负担:

    • 核心业务,比如登录,数据库操作等
    • 辅助功能,比如性能统计、日志、事务管理等。AOP 中的切面即为周边业务(或者辅助功能)

    下面举个例子

    需要添加切面的业务对象

    public class ProductService {
        public void doSomeService(){
            System.out.println("doSomeService");
        }
    }
    

    切面对象

    public class LoggerAspect {
        public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("start log:" + joinPoint.getSignature().getName());
            Object object = joinPoint.proceed();
            System.out.println("end log:" + joinPoint.getSignature().getName());
            return object;
        }
    }
    

    关联业务与切面(配置文件)

    <aop:config>
       <!-- 定义切面位置 -->
       <aop:pointcut id="loggerCutpoint" expression="execution(* com.how2java.service.ProductService.*(..)) "/>
        <!-- 关联切面和切面对象 -->
       <aop:aspect id="logAspect" ref="loggerAspect">
       		<aop:around pointcut-ref="loggerCutpoint" method="log"/>
       </aop:aspect>
    </aop:config>  
    

    根据 AOP 配置中的 execution,ProductService 中所有方法的调用都将触发 LoggerAspect 切面

    注解形式

    @Aspect // 这是一个切面对象
    @Component
    public class LoggerAspect {
        // 指明切面的执行位置
        @Around(value = "execution(* com.how2java.service.ProductService.*(..))")
        public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("start log:" + joinPoint.getSignature().getName());
            Object object = joinPoint.proceed();
            System.out.println("end log:" + joinPoint.getSignature().getName());
            return object;
        }
    }
    
  • 相关阅读:
    September 29th 2017 Week 39th Friday
    September 28th 2017 Week 39th Thursday
    September 27th 2017 Week 39th Wednesday
    September 26th 2017 Week 39th Tuesday
    September 25th 2017 Week 39th Monday
    September 24th 2017 Week 39th Sunday
    angular2 学习笔记 ( Form 表单 )
    angular2 学习笔记 ( Component 组件)
    angular2 学习笔记 ( Http 请求)
    angular2 学习笔记 ( Router 路由 )
  • 原文地址:https://www.cnblogs.com/jiahu-Blog/p/11849811.html
Copyright © 2011-2022 走看看