Spring注解开发学习总结

185 阅读6分钟

环境准备:
spring是从4.x开始支持纯注解开发,代替以往的xml方式,导入核心jar包context

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.12.RELEASE</version>
</dependency>
  1. @ComponentScan("com.yefeng.bean") 作用于类,接口;扫描带有spring组件组件的bean,例如@Component,@Controller,@Service等

2.@Import 导入组件,一般是一个普通的java pojo对象,bean name默认是组件的全类名; Import组件一般可以配合 ImportSelector,ImportBeanDefinitionRegistrar接口使用

2.1 ImportSelector: 自定义逻辑返回需要导入的组件 image.png image.png 上图中的配置类MyConfig除了导入了Red,Color,Person,还导入了MyImportSelecor这个类,而这个类又导入了Blue和Yellow这两个pojo对象。

2.2 ImportBeanDefinitionRegistrar: 根据bean的定义去注册bean
比如判断容器是否存在Blue这个bean对象,存在就向容器注册一个名为rainBow的bean,且通过RootBeanDefinition这个对象给bean设置一些属性,例如是否懒加载,作用域等一些bean的信息,同样的这个MyImportBeanDefinitionRegistrar也需要配合@Import注解一起使用才生效

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
   /**
    * AnnotationMetadata:当前类的注解信息
    * BeanDefinitionRegistry:BeanDefinition注册类;
    * 把所有需要添加到容器中的bean;调用
    * BeanDefinitionRegistry.registerBeanDefinition手工注册进来
    */
   @Override
   public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

       boolean definition = registry.containsBeanDefinition("com.yefeng.bean.Blue");
       // registry.isAlias("xxx");
       //如果配置文件有这个bean的定义就注册一个rainBow bean
       if (definition) {
           //指定Bean定义信息;(Bean的类型,Bean。。。)
           RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
           beanDefinition.setPrimary(false);
           beanDefinition.setScope(ConfigurableBeanFactory.SCOPE_SINGLETON);
           //注册一个Bean,指定bean名
           registry.registerBeanDefinition("rainBow", beanDefinition);
       }
   }
}
  1. Conditional: 按条件注入bean 编写两个类分别实现Condition接口,实现matches方法去做条件判断
//判断是否linux系统
public class LinuxCondition implements Condition {

   /**
    * ConditionContext:判断条件能使用的上下文(环境)
    * AnnotatedTypeMetadata:注释信息
    */
   @Override
   public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
      // TODO是否linux系统
      //1、能获取到ioc使用的beanfactory
      ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
      //2、获取类加载器
      ClassLoader classLoader = context.getClassLoader();
      //3、获取当前环境信息
      Environment environment = context.getEnvironment();
      //4、获取到bean定义的注册类
      BeanDefinitionRegistry registry = context.getRegistry();
      //获取当前服务器的os 名称
      String property = environment.getProperty("os.name");
      
      //当前os是linux[false]且有bean定义注册表有Color这个bean[true]
      boolean definition = registry.containsBeanDefinition("com.yefeng.bean.Color");
      if(property.contains("linux") &&  definition){
         return true;
      }
      return false;
   }
}
//判断是否windows系统
public class WindowsCondition implements Condition {

   @Override
   public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
      Environment environment = context.getEnvironment();
      //获取当前服务器的os 名称
      String property = environment.getProperty("os.name");
      if(property.contains("Windows")){
         return true;
      }
      return false;
   }
}

编写一个配置类用于注入满足条件的bean,在下面2个方法上使用@Conditional(WindowsCondition.class)和@Conditional(LinuxCondition.class),很明显根据当前系统运行环境而言,满足条件的bean是bill。 @Conditional(WindowsCondition.class)也可以作用在类上面,效果也是一样

@Configuration
@Import({Color.class, Red.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
@PropertySource(value={"classpath:/person.properties"})
public class MainConfigOfConditional {
   

   /**
    * @Conditional({Condition}) : 按照一定的条件进行判断,满足条件给容器中注册bean
    * 
    * 如果系统是windows,给容器中注册("bill")
    * 如果是linux系统,给容器中注册("linus")
    */

   @Conditional(WindowsCondition.class)
   @Bean("bill")
   public Person person01(){
      return new Person("Bill Gates",62);
   }

   @Conditional(LinuxCondition.class)
   @Bean("linus")
   public Person person02(){
      return new Person("Linus", 48);
   }
   

运行结果:
image.png 当然spring提供了非常多的条件注解以方便不同场景下的使用,例如很多以@ConditionalOnXXX命名的bean(xxx是条件)

  1. @PropertySource: 读取外部的classPath下 xxx.properties配置文件 读取外部配置文件中的k/v保存到运行的环境变量中;加载完外部的配置文件以后使用${}取出配置文件的值 image.png 配置文件中的内容为 person.nickName=feng
    运行结果: image.png

  2. @Profile: 可配置多数据源环境 编写一个多数据源的配置类,引入classPath下的dbconfig文件,声明3种数据源类型分别是测试,开发,生产这3种不同环境,区分bean的名称。这里用EmbeddedValueResolverAware去解析了dbconfig里的DriverClass属性,就当多了解一些组件的使用。

/**
 * Profile:
 * Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能;
 * 开发环境、测试环境、生产环境;
 * 数据源:(/A)(/B)(/C);
 *
 * @Profile:指定组件在哪个环境的情况下才能被注册到容器中,不指定,任何环境下都能注册这个组件 1)、加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中。默认是default环境
 * 2)、写在配置类上,只有是指定的环境的时候,整个配置类里面的所有配置才能开始生效
 * 3)、没有标注环境标识的bean在,任何环境下都是加载的;
 */
@PropertySource("classpath:/dbconfig.properties")
@Configuration
public class MainConfigOfProfile implements EmbeddedValueResolverAware {

    @Value("${db.user}")
    private String user;

    private StringValueResolver valueResolver;

    private String driverClass;

    @Bean
    public Yellow yellow() {
        return new Yellow();
    }

    @Profile("test")
    @Bean("testDataSource")
    public DataSource dataSourceTest(@Value("${db.password}") String pwd) throws Exception {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }


    @Profile("dev")
    @Bean("devDataSource")
    public DataSource dataSourceDev(@Value("${db.password}") String pwd) throws Exception {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/dev");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @Profile("prod")
    @Bean("prodDataSource")
    public DataSource dataSourceProd(@Value("${db.password}") String pwd) throws Exception {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/user_db");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.valueResolver = resolver;
        driverClass = valueResolver.resolveStringValue("${db.driverClass}");
    }

}

配置文件内容:

db.user=root
db.password=yefeng
db.driverClass=com.mysql.jdbc.Driver

编写测试类:

public class IOCTest_Profile {
   
   //1、使用命令行动态参数: 在虚拟机参数位置加载 -Dspring.profiles.active=test
   //2、代码的方式激活某种环境;
   @Test
   public void test01(){
      AnnotationConfigApplicationContext applicationContext = 
            new AnnotationConfigApplicationContext();
      //1、创建一个applicationContext
      //2、设置需要激活的环境
      //applicationContext.getEnvironment().setActiveProfiles("dev");
      applicationContext.getEnvironment().setActiveProfiles("test");
      //applicationContext.getEnvironment().setActiveProfiles("prod");
      //3、注册主配置类
      applicationContext.register(MainConfigOfProfile.class);
      //4、启动刷新容器
      applicationContext.refresh();

      String[] namesForType = applicationContext.getBeanNamesForType(DataSource.class);
      for (String string : namesForType) {
         System.out.println("数据源:"+string);
      }
      Yellow bean = applicationContext.getBean(Yellow.class);
      System.out.println(bean);
      applicationContext.close();
   }

}

测试结果: image.png

  1. 基于注解版的AOP 6.1 基于动态代理实现的,手写编写一个被代理的数学运算类MathCalculator
/**
 * 定义一个数学运算类
 */
public class MathCalculator {
   public double div(double i,double j){
      System.out.println("MathCalculator...div...执行了");
      return i/j;    
   }
}

6.2编写一个切面类,并且使用@Aspect声明该类是一个切面

/**
 * 切面类
 * @Aspect: 告诉Spring当前类是一个切面类
 */
@Aspect
public class LogAspects {
   
   //抽取公共的 切入点 表达式
   @Pointcut("execution(public double com.yefeng.test.aop.MathCalculator.*(..))")
   public void pointCut(){};
   
   //@Before在目标方法之前切入;切入点表达式(指定在哪个方法切入)
   @Before("pointCut()")
   public void logStart(JoinPoint joinPoint){
      Object[] args = joinPoint.getArgs();
      System.out.println(""+joinPoint.getSignature().getName()+"运行。。。@Before:参数列表是:{"+Arrays.asList(args)+"}");
   }
   
   @After("pointCut()")
   public void logEnd(JoinPoint joinPoint){
      System.out.println(""+joinPoint.getSignature().getName()+"结束。。。@After");
   }
   
   //JoinPoint一定要出现在参数表的第一位
   @AfterReturning(value="pointCut()",returning="result")
   public void logReturn(JoinPoint joinPoint,Object result){
      System.out.println(""+joinPoint.getSignature().getName()+"正常返回。。。@AfterReturning:运行结果:{"+result+"}");
   }
   
   @AfterThrowing(value="pointCut()",throwing="exception")
   public void logException(JoinPoint joinPoint,Exception exception){
      System.out.println(""+joinPoint.getSignature().getName()+"异常。。。异常信息:{"+exception+"}");
   }

}

@Pointcut("execution(public double com.yefeng.test.aop.MathCalculator.(..))") 表示给MathCalculator类做代理,:示该类的所有方法,..:表示方法的任意参数。该类就是一个切入点表达式,往下的方法就是前置通知,后置通知,返回通知,异常通知

6.3 编写一个AOP的配置类,并且使用@EnableAspectJAutoProxy开启基于注解的aop模式

/**
 * AOP:【动态代理】
 *        指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式;
 * 
 * 1、导入aop模块;Spring AOP:(spring-aspects)
 * 2、定义一个业务逻辑类(MathCalculator);在业务逻辑运行的时候将日志进行打印(方法之前、方法运行结束、方法出现异常,xxx)
 * 3、定义一个日志切面类(LogAspects):切面类里面的方法需要动态感知MathCalculator.div运行到哪里然后执行;
 *        通知方法:
 *           前置通知(@Before):logStart:在目标方法(div)运行之前运行
 *           后置通知(@After):logEnd:在目标方法(div)运行结束之后运行(无论方法正常结束还是异常结束)
 *           返回通知(@AfterReturning):logReturn:在目标方法(div)正常返回之后运行
 *           异常通知(@AfterThrowing):logException:在目标方法(div)出现异常以后运行
 *           环绕通知(@Around):动态代理,手动推进目标方法运行(joinPoint.procced())
 * 4、给切面类的目标方法标注何时何地运行(通知注解);
 * 5、将切面类和业务逻辑类(目标方法所在类)都加入到容器中;
 * 6、必须告诉Spring哪个类是切面类(给切面类上加一个注解:@Aspect)
 * [7]、给配置类中加 @EnableAspectJAutoProxy 【开启基于注解的aop模式】
 *        在Spring中有很多的 @EnableXXX;
 * 
 * 三步:
 *     1)、将业务逻辑组件和切面类都加入到容器中;告诉Spring哪个是切面类(@Aspect)
 *     2)、在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式)
 *  3)、开启基于注解的aop模式;@EnableAspectJAutoProxy
 */
@EnableAspectJAutoProxy  //开启基于注解的aop模式
@Configuration
public class MainConfigOfAOP {
    
   //业务逻辑类加入容器中
   @Bean
   public MathCalculator calculator(){
      return new MathCalculator();
   }

   //切面类加入到容器中
   @Bean
   public LogAspects logAspects(){
      return new LogAspects();
   }
}

6.4编写一个测试类,并测试看看aop的效果,可以看到执行结果的步骤符合前置通知,方法执行,后置通知,返回通知 image.png