SSM-Spring(六)-纯注解开发

实现AOP

  1. 与之前的注解实现AOP进行比对
  2. @EnableAspectJAutoProxy相当于<aop:aspectj-autoproxy/>
  3. 删除applicationContext.xml
  4. 新建入口类

     //com.zh.cfg.AppConfig
     @Configuration
     //相当于<aop:aspectj-autoproxy/>
     @EnableAspectJAutoProxy
     @ComponentScan("com.zh")
     public class AppConfig {
     }
    
  5. 测试入口

     ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
     UserService service = ctx.getBean("userService", UserService.class);
     service.list();
    
  6. 其余不变

实现整合MyBatis

  1. 之前示例代码,使用xml整合如下

     <!-- 1. 创建数据源 -->
     <!-- 加载配置文件 -->
     <context:property-placeholder location="db.properties"/>
    
     <!-- 数据源(Druid) -->
     <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
         <property name="driverClassName" value="${jdbc.driverClass}"/>
         <property name="url" value="${jdbc.url}"/>
         <property name="username" value="${jdbc.username}"/>
         <property name="password" value="${jdbc.password}"/>
     </bean>
    
     <!--2.创建SqlSessionFactoryBean,获取sqlsession
     设置数据源
     设置别名
     设置映射文件位置
      -->
     <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
         <!-- 数据源 -->
         <property name="dataSource" ref="dataSource"/>
         <!-- 这个包底下的类会自动设置别名(一般是领域模型)
             这个包下面所有的别名就是类名
             比如com.zh.domain.Skill;别名是Skill
          -->
         <property name="typeAliasesPackage" value="com.zh.domain"/>
         <!-- 映射文件的位置 -->
         <property name="mapperLocations">
             <array>
                 <value>mappers/*.xml</value>
             </array>
         </property>
     </bean>
    
     <!-- 3. 扫描dao
     设置sqlSessionFactory名字
     告知到哪里去扫描dao
      -->
     <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
         <!-- 设置SqlSessionFactoryBean的id -->
         <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
         <!-- 设置dao的包 -->
         <property name="basePackage" value="com.zh.dao"/>
     </bean>
    
  2. 创建入口类MyBatisConfig

     @Configuration
     //加载配置文件
     @PropertySource("db.properties")
     //MapperScan相当于MapperScannerConfigurer
     @MapperScan("${mybatis.mapperScan}")
     public class MyBatisConfig {
         //纯注解注入基本数据类型
         @Value("${jdbc.driverClassName}")
         private String driverClassName;
         @Value("${jdbc.url}")
         private String url;
         @Value("${jdbc.username}")
         private String username;
         @Value("${jdbc.password}")
         private String password;
         @Value("${mybatis.typeAliasesPackage}")
         private String typeAliasesPackage;
         @Value("${mybatis.mapperLocations}")
         private String mapperLocations;
        
         //创建数据源,因为DruidDataSource类是第三方的,所以通过@Bean方法
         @Bean
         public DataSource dataSource() {
             DruidDataSource ds = new DruidDataSource();
             ds.setDriverClassName(driverClassName);
             ds.setUrl(url);
             ds.setUsername(username);
             ds.setPassword(password);
             return ds;
         }
         //创建SqlSessionFactoryBean,获取sqlsession
         @Bean
         public SqlSessionFactoryBean sqlSessionFactory() throws Exception {
             SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
             bean.setDataSource(dataSource());
             //设置别名
             bean.setTypeAliasesPackage(typeAliasesPackage);
             //映射文件的位置
             PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
             bean.setMapperLocations(resolver.getResources(mapperLocations));
             return bean;
         }
        
         //扫描dao
         //可以直接使用MapperScan来代替
     //    @Bean
     //    public MapperScannerConfigurer configurer() {
     //        MapperScannerConfigurer configurer = new MapperScannerConfigurer();
               //不用手动注入,可以直接通过参数类型自动注入
     //        configurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
               //设置要扫描的dao
     //        configurer.setBasePackage("com.zh.dao");
     //        return configurer;
     //    }
     }
    
  3. 配置文件内容如下:db.properties

     jdbc.driverClassName=com.mysql.jdbc.Driver
     jdbc.url=jdbc:mysql://localhost:3306/test-mybatis
     jdbc.username=root
     jdbc.password=root
        
     mybatis.typeAliasesPackage=com.zh.domain
     mybatis.mapperLocations=mappers/*.xml
     mybatis.mapperScan=com.zh.dao
    
  4. SkillTest

     @Before
     public void before() {
         ctx = new AnnotationConfigApplicationContext("com.zh");
         skillDao = ctx.getBean("skillDao", SkillDao.class);
     }
    
     @Test
     public void list() {
         List<Skill> skills = skillDao.list();
         System.out.println(skills);
     }
    

实现事务管理

  1. 之前xml与半注解实现事务管理如下

     <!-- 上面是整合MyBatis,略 -->
     <!-- 事务管理器 -->
     <bean id="txMgr"
           class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
         <property name="dataSource" ref="dataSource"/>
     </bean>
     <!--设置注解驱动
     取代之前的<tx:advice>、`<aop:config>
      -->
     <tx:annotation-driven transaction-manager="txMgr"/>
     <!--让spring扫描这个包下所有的类,用来解析注解-->
     <context:component-scan base-package="com.zh"/>
    
     <!-- 附加代码:事务管理代码 -->
     <!--    <tx:advice id="txAdvice" transaction-manager="txMgr">-->
     <!--        <tx:attributes>-->
     <!--            <tx:method name="*"/>-->
     <!--            <tx:method name="list*" propagation="SUPPORTS"/>-->
     <!--        </tx:attributes>-->
     <!--    </tx:advice>-->
    
     <!-- 切面 -->
     <!--    <aop:config>-->
     <!--        <aop:pointcut id="pc" expression="within(com.zh.service..*)"/>-->
     <!--        <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>-->
     <!--    </aop:config>-->
    
  2. 现在就是如何使用注解替换上面的xml
  3. 创建一个单独的类实现:TxConfig

     @Configuration
     //EnableTransactionManagement相当于<tx:annotation-driven />
     //类型自动注入功能,不需要将事务管理器手动注入
     @EnableTransactionManagement
     public class TxConfig {
         @Bean
         //创建事务管理器
         //dataSource注入:由参数类型自动注入,容器中在mybatis中已经生成该对象
         public DataSourceTransactionManager mgr(DataSource dataSource) {
             DataSourceTransactionManager mgr = new DataSourceTransactionManager();
             mgr.setDataSource(dataSource);
             return mgr;
         }
     }
    
  4. 测试入口

     private ApplicationContext ctx;
     private SkillService skillService;
     @Before
     public void before() {
         ctx = new AnnotationConfigApplicationContext("com.zh");
         skillService = ctx.getBean("skillService", SkillService.class);
     }
     @Test
     public void save() throws Exception {
         skillService.test(null);
     }
    

JSR注解

  1. JSR是Java Specification Requests的缩写, 译为Java规范提案
    1. 是指向JCP(Java Community Process) 提出新增一个标准化技术规范的正式请求
    2. 任何人都可以提交JSR,以向Java平台增添新的API和服务
    3. 一旦某个JSR通过了JCP的审核,它就变成了Java技术栈的一部分,可以安全地用于生产环境
    4. JSR的审核过程确保了只有可靠稳定的技术才能变成Java的一部分,避免过度臃肿和膨胀
    5. JSR就是定义的向java官方平台提出新增规范的建议的一套规范
  2. Spring也支持JSR规范中定义的一些注解
    1. JSR 250:@Resource
    2. JSR 330:@Inject
    3. JSR 520:@PostConstruct、@PreDestroy
    4. 上面是JSR规范的一些版本,Spring识别这些注解
  3. 导入依赖

     <!-- JSR注解 -->
     <!-- @Resource、@PostConstruct、@PreDestroy -->
     <dependency>
         <groupId>javax.annotation</groupId>
         <artifactId>javax.annotation-api</artifactId>
         <version>1.3.2</version>
     </dependency>
     <!-- @Inject -->
     <dependency>
         <groupId>javax.inject</groupId>
         <artifactId>javax.inject</artifactId>
         <version>1</version>
     </dependency>
    

@Resource、@Inject

  1. @Resource:根据名称(id)进行注入,可以用在成员变量、setter上
    1. @Resource(name="dog")等价于@Autowired、@Qualifier("dog2")
     //dog的名称(id)
     @Component("dog2")
     public class Dog {}
        
     @Component
     public class Person {
         //可以根据名称(id)进行注入
         @Resource(name = "dog2")
         private Dog dog;
         //也可以修饰set方法
         //@Resource(name = "dog2")
         //public void setDog(Dog dog) {
         //    this.dog = dog;
         //}
     }
     //测试有dog属性值
     System.out.println(ctx.getBean("person"));
    
  2. @Inject:默认根据类型进行注入, 可以用在成员变量、setter、构造方法上
    1. 可以配合使用@Qualifier@Name:设置需要注入的bean的id

       //dog的名称(id)
       @Component("dog2")
       public class Dog {}
              
       @Component
       public class Person {
           //修饰成员:可以根据类型进行注入,不管名称是什么
           //@Inject
           private Dog dog;
           private String name;
           //修饰set方法
           //@Inject
           public void setDog(Dog dog) {
               this.dog = dog;
           }
           //默认会调用这个构造方法
           public Person() {
               System.out.println("Person-------------");
           }
           /*
           修饰构造方法:有两个功能
           1. 一旦使用@Inject修饰,就会首先调用这个构造方法
           2.默认根据类型自动注入:会到容器中查找Dog类型的bean对象,name为非bean对象,直接传值,然后调用
           * */
       //    @Inject
       //    public Person(Dog dog, @Value("jack") String name) {
       //        this.dog = dog;
       //        this.name = name;
       //        System.out.println("Person(Dog, String)-------------");
       //    }
              
           /*
           * 可以配合@Named,指定以名称(id)的方式注入
           * 用@Qualifier也同样
           * */
           @Inject
           //@Named("dog2") :要按照名称(id)自动注入,不要按照类型自动注入
           public Person(@Named("dog2") Dog dog, @Nullable String name) {
               this.dog = dog;
               this.name = name;
       //        System.out.println("Person(Dog, String)-------------");
           }
              
           //@Autowired也能用在构造方法中
       //    @Autowired
       //    public Person(@Named("dog2") Dog dog, @Nullable String name) {
       //        this.dog = dog;
       //        this.name = name;
       //    }
       }
       //测试有dog属性值
       System.out.println(ctx.getBean("person"));
      

@PostContruct、@PreDestroy

  1. @PostContruct:在InitializingBean的afterPropertiesSet方法之前调用
  2. @PreDestroy:在DisposableBean的destroy方法之前调用
  3. 代码举例

     //监听生命周期
     @Component
     public class Processor implements BeanPostProcessor {
         @Override
         public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
             if (!(bean instanceof Student)) return bean;
             System.out.println("BeanPostProcessor - before");
             return bean;
         }
        
         @Override
         public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
             if (!(bean instanceof Student)) return bean;
             System.out.println("BeanPostProcessor - after");
             return bean;
         }
     }
        
     //Student生命周期
     public class Student implements InitializingBean, DisposableBean, BeanNameAware, ApplicationContextAware {
     private String name;
    
     public void setName(String name) {
         this.name = name;
         System.out.println("setName");
     }
    
     public void run() {
         System.out.println("run");
     }
    
     public Student() {
         System.out.println("Student");
     }
    
     public void init() {
         System.out.println("init");
     }
    
     public void dealloc() {
         System.out.println("dealloc");
     }
        
     //在InitializingBean的afterPropertiesSet方法之前调用
     @PostConstruct
     public void postConstruct() {
         System.out.println("postConstruct");
     }
        
     //在DisposableBean的destroy方法之前调用
     @PreDestroy
     public void preDestroy() {
         System.out.println("preDestroy");
     }
    
     @Override
     public void setBeanName(String name) {
         System.out.println("setBeanName");
     }
    
     @Override
     public void destroy() throws Exception {
         System.out.println("DisposableBean - destroy");
     }
    
     @Override
     public void afterPropertiesSet() throws Exception {
         System.out.println("InitializingBean - afterPropertiesSet");
     }
    
     @Override
     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
         System.out.println("setApplicationContext");
     }
    
  4. 测试

     AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
     Student student = ctx.getBean("student", Student.class);
     student.run();
     ctx.close();
    
  5. 打印Student生命周期调用顺序结果:

     tudent
     setName
     setBeanName
     setApplicationContext
     BeanPostProcessor - before
     postConstruct
     InitializingBean - afterPropertiesSet
     init
     BeanPostProcessor - after
     run
     preDestroy
     DisposableBean - destroy
     dealloc
    

读取配置文件的底层实现

  1. 读取配置文件的方法:
    1. 方法一:
      1. xml配置

         <context:property-placeholder location="db.properties"/>
        
      2. 类中直接使用

         @Component
         public class UserServiceImpl {
             @Value("${jdbc.username}")
             private String name;
         }
        
    2. 方法二:注解

       @Component
       @PropertySource("db.properties")
       public class UserServiceImpl {
           @Value("${jdbc.username}")
           private String name;
       }
      
  2. property-placeholder的底层
    1. 它的底层使用了PropertySourcesPlaceholderConfigurer类
    2. 可以使用这个类取代<context:property-placeholder/>、@PropertySources、@PropertySource
    3. 使用方式一:用PropertySourcesPlaceholderConfigurer类的xml形式,相当于使用<context:property-placeholder/>

       <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
           <property name="location" value="db.properties"/>
       </bean>
      
    4. 使用方法二:直接使用PropertySourcesPlaceholderConfigurer类的代码形式加载

       @Configuration
       public class PropConfig {
           @Bean
           public PropertySourcesPlaceholderConfigurer configurer() {
               PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
               configurer.setLocation(new ClassPathResource("db.properties"));
               return configurer;
           }
       }
      
    5. 使用

       @Component
       public class UserServiceImpl {
           @Value("${jdbc.username}")
           private String name;
       }
      

component-scan详解

  1. exclude-filter设置不需要扫描的类型

     <context:component-scan base-package="com.zh">
         <!-- 排除Person类不扫描 -->
         <context:exclude-filter type="assignable" expression="com.zh.domain.Person"/>
         <!-- 排除com.zh.service这个包下面的所有类 -->
         <context:exclude-filter type="aspectj" expression="com.zh.service..*"/>
         <!-- 排除通过正则表达式匹配的所有类 -->
         <context:exclude-filter type="regex" expression=".*My.*"/>
         <!-- 排除带Service注解的所有类 -->
         <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
         <!-- 自定义过滤类,通过类match方法返回的值来过滤,返回true就过滤 -->
         <context:exclude-filter type="custom" expression="com.zh.filter.MyTypeFilter"/>
     </context:component-scan>
        
     //自定义过滤器类
     public class MyTypeFilter implements TypeFilter {
         @Override
         public boolean match(MetadataReader metadataReader,
                              MetadataReaderFactory metadataReaderFactory)
                 throws IOException {
             String name = metadataReader.getClassMetadata().getClassName();
             //返回true则过滤掉,反之。。。
             return name.matches(".*My.*");
         }
     }
    
  2. include-filter设置需要扫描的类型

     <context:component-scan base-package="com.zh" use-default-filters="false">
         <!-- 只扫描某个类-->
         <context:include-filter type="assignable" expression="com.zh.domain.Person"/>
     <!-- 自定义过滤类,通过类match方法返回的值来包含,返回true就包含-->
         <context:include-filter type="custom" expression="com.zh.filter.MyTypeFilter"/>
     </context:component-scan>
    
  3. 注意: 被include-filter包含的类型,就算没有@Component、<bean>、@Bean修饰,也会放到IoC容器中
  4. 关于exclude-filter、include-filter的type属性值,决定了expression属性可以设置什么值
    1. ssignable:设置具体类型
    2. annotation:设置注解类型
    3. aspectj:设置切入点表达式within中的内容
    4. regex:设置正则表达式,比如.*service.*表示全类名中包含service的
    5. custom设置自定义类型过滤器 (实现org.springframework.core.type.filter.Type Filter接口)
  5. @ComponentScan使用纯注解实现

     @Component
     //@ComponentScans()
     @ComponentScan(basePackages = "com.zh", excludeFilters = {
             @Filter(type = FilterType.ASSIGNABLE_TYPE, value = {Person.class, Dog.class}),
             @Filter(type = FilterType.ANNOTATION, classes = Service.class),
             @Filter(type = FilterType.CUSTOM, classes = MyTypeFilter.class),
             @Filter(type = FilterType.REGEX, pattern = ".*My.*"),
             @Filter(type = FilterType.ASPECTJ, pattern = "com.mj.service..*")
     })
     public class MyCat {}
    
Table of Contents