SSM-Spring(五)-注解的使用

注解实现bean标签(注解实现IoC)

  1. 常见的注解

     1. @Component
         相当于applicationContext.xml中的<bean>
         默认会将类名的首字母小写形式作为bean的id,也可以通过value属性设置bean的id,
     2. @Controller用于标识控制器层,@Service用于标识业务层,@Repository用于标识Dao层
     3. @Scope:设置singleton、prototype
     4. @Lazy:scope为singleton时延迟加载
     5. 可以通过<context:component-scan> 设置需要扫描的包,可以扫描到所有的@Component注解。
         1. @ComponentScan:相当于<context:component-scan>标签
    
  2. 举例使用

    1. 在applicationContext.xml文件中设置需要扫描那些包含注解的文件

       <!--扫描指定包下面的所有注解-->
       <!--注意:只能扫描到domain下的文件,不能扫描dao、service、servlet层的文件,但是可以通过注解@ComponentScan来附加扫描其他文件-->
       <context:component-scan base-package="com.zh.domain"/>
      
    2. domain下的person类注解如下:

       //等价于bean标签,默认id为类名小写person
       @Component
       //指定bean的id为personx
       //@Component("personx")
       //在容器中是否唯一
       @Scope("singleton")
       //等价于标签的lazy-init = true
       @Lazy
       //让入口xml文件,附加扫描其它的包
       @ComponentScans({
               @ComponentScan("com.zh.service"),
               @ComponentScan("com.zh.dao"),
               @ComponentScan("com.zh.servlet")
       })
       public class Person {
              
       }
      
    3. MyTest测试代码

       ApplicationContext ctx = new ClassPathXmlApplicationContext(
           "applicationContext.xml");
       System.out.println(ctx.getBean("person"));
       System.out.println(ctx.getBean("user"));
       System.out.println(ctx.getBean("userService"));
      

注解实现注入

  1. @Autowired:默认按照类型注入bean实例
    1. 可以写在成员变量(不会调用setter)、setter、构造方法上
    2. 可以配合使用@Qualifier@Named:设置需要注入的bean的id
    3. required设置为false:找不到对应的bean时不会抛出异常
  2. @Value
    1. 用于注入String、基本类型、BigDecimal等类型
    2. 可以配合配置文件(比如properties)使用${}读取配置文件的内容
  3. @PropertySources、@PropertySource
    1. 相当于<context:property-placeholder>
  4. 指定构造方法
    1. 可以通过@Autowired指定创建bean时调用的构造方法

       public Student (){};
       //创建bean时会调用这个构造方法
       @Autowired
       public Student(@Value("zh") String name,
                      @Qualifier("dog") Dog dog){
           this.name = name;
           this.dog = dog;
       }
          
       //@Nullable会注入null值
       @Autowired
       public Student(@Nullable String name,
                      @Qualifier("dog") Dog dog){
           this.name = name;
           this.dog = dog;
       }
      
  5. 举例使用:

     //注解User类
     @Component
     //导入配置文件,相当于<context:property-placeholder>,有了这个下面就可以使用配置文件中的动态引用了 @Value("${name}")
     @PropertySource("user.properties")
     public class User {
         //基本数据类型注入一个动态的值
         @Value("${name}")
         private String name;
        
         //也可以直接放到成员变量上,不用放到set方法上,甚至不用写set方法
         //@Autowired
         private Dog dog;
        
         //基本数据类型注入一个确定的值
         //@Value("jack")
         //也可以放在set方法前面
         //@Value("${name}")
         public void setName(String name) {
             this.name = name;
         }
        
         //自动注入
         //@Autowired
         //required = false:找不到bean对象不会报错
         @Autowired(required = false)
         //指定id值为dog的bean对象进行注入
         @Qualifier("dog")
         public void setDog(Dog dog) {
             this.dog = dog;
         }
     }
        
     //user.properties文件内容
     name=Rose
    

注解实现AOP

  1. @Aspect:用来标识一个切面类(切面类对象需要被放入到IoC容器中)
  2. @Around:用来设置切入点(被注解的方法要有返回值、接收一个ProceedingJoinPoint参数)
  3. 还需要加上<aop:aspectj-autoproxy>标签,替代之前的<aop:config>
    1. 同样有proxy-target-class属性设置AOP的底层实现方案(强制使用CGLib、按照默认做法)
     <!--替代之前的<aop:config>-->
     <aop:aspectj-autoproxy proxy-target-class="true"/>
    

举例使用

  1. service层如下:

     //接口
     public interface UserService {
         void list();
     }
        
     //实现
     @Service("userService")
     public class UserServiceImpl implements UserService {
         public void list() {
             System.out.println("UserServiceImpl - list");
         }
     }
    
  2. 添加切面类

     //代表一个切面类
     @Aspect
     //放入到spring容器中去
     @Component
     public class DefaultAspect {
         //可以使用@Pointcut复用切入点,这里设置好切入点,其他地方引用。
         //1. 设置要切入的类,给当前类的所有方法都切入
         @Pointcut("within(com.zh.service.impl.UserServiceImpl)")
         //2. 用一个空方法来代表切入点
         public void pc() {}
        
         //设置切入点,也可以直接使用@Around("within(com.zh.service.impl.UserServiceImpl)")
         @Around("pc()")
         //附加代码:必须要接收一个ProceedingJoinPoint类型参数,用来拿到目标对象,执行目标方法;而且必须要有返回值
         public Object log(ProceedingJoinPoint point) throws Throwable {
             System.out.println("log-----------------------1");
             // 调用目标方法
             Object ret = point.proceed();
             System.out.println("log-----------------------2");
             return ret;
         }
        
         //设置切入点
         @Around("pc()")
         public Object watch(ProceedingJoinPoint point) throws Throwable {
             System.out.println("watch-----------------------1");
             // 调用目标方法
             Object ret = point.proceed();
             System.out.println("watch-----------------------2");
             return ret;
         }
     }
    
  3. applicationContext.xml

     <!--让spring扫描这个包下所有的类,用来解析注解-->
     <context:component-scan base-package="com.zh"/>
    
     <!--替代之前的<aop:config>-->
     <aop:aspectj-autoproxy proxy-target-class="false"/>
    

如何将自定义的附加代码与注解混合使用

  1. 自定义附加代码

     public class LogInterceptor implements MethodInterceptor {
         @Override
         public Object invoke(MethodInvocation invocation) throws Throwable {
             System.out.println("LogInterceptor-----------------------1");
        
             // 调用目标方法
             Object ret = invocation.proceed();
        
             System.out.println("LogInterceptor-----------------------2");
             return ret;
         }
     }
    
  2. 通过applicationContext.xml配置,将附加代码也切入到注解类设置的切入点(com.zh.service.impl.UserServiceImpl)中

     <!--xml与注解混合使用-->
     <bean id="logInterceptor" class="com.zh.aop.LogInterceptor"/>
    
     <aop:config>
         <!--直接使用注解中的切入点
         将自定义的附加代码,切入到注解的切面类设置的切入点中
         -->
         <aop:pointcut id="pc" expression="com.zh.aop.DefaultAspect.pc()"/>
         <aop:advisor advice-ref="logInterceptor" pointcut-ref="pc"/>
         <!--替代上面的两句-->
         <!--        <aop:advisor advice-ref="logInterceptor" pointcut="com.zh.aop.DefaultAspect.pc()"/>-->
     </aop:config>
    

测试结果

```
ApplicationContext ctx = new ClassPathXmlApplicationContext(
            "applicationContext.xml");
UserService service = ctx.getBean("userService", UserService.class);
service.list();

//打印
LogInterceptor-----------------------1
log-----------------------1
watch-----------------------1
UserServiceImpl - list
watch-----------------------2
log-----------------------2
LogInterceptor-----------------------2
```

注解实现事务管理

  1. @Transactional
    1. 可以在需要进行事务管理的类(比如Service) 上使用这个注解, 代表所有方法都会自动管理事务
    2. 可以在需要进行事务管理的方法上使用这个注解(可以覆盖类中@Transactional的配置)
    3. 可以设置isolation、propagation、imeout、rollback For、noRollbackFor、readOnly等属性
  2. 使用<tx:annotation-driven>取代之前的<tx:advice><aop:config>
    1. 同样有proxy-target-class属性设置AOP的底层实现方案

代码举例

  1. 之前讲解的事务管理如下:

     <!-- 事务管理器 -->
     <bean id="txMgr"
           class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
         <property name="dataSource" ref="dataSource"/>
     </bean>
     <!-- 附加代码:事务管理代码 -->
     <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. 在service层设置事务管理注解

     @Service("skillService")
     //代表所有方法都需要添加事务管理
     @Transactional
     //可以设置事务的属性
     //@Transactional(isolation = )
     public class SkillServiceImpl implements SkillService {
         private SkillDao dao;
        
         @Autowired
         public void setDao(SkillDao dao) {
             this.dao = dao;
         }
        
         @Override
         //针对某个特定的方法,设置特别的属性值
         @Transactional(propagation = Propagation.SUPPORTS)
         public List<Skill> list() {
             return dao.list();
         }
        
         @Override
         public boolean save(Skill skill) {
             return dao.save(skill);
         }
        
         @Override
         public boolean update(Skill skill) {
             return dao.update(skill);
         }
        
         public void test(Skill skill) throws Exception {
        
             dao.save(skill);
        
             throw new RuntimeException();
         }
     }
    
  3. 在applicationContext.xml配置如下

     <!-- 事务管理器 -->
     <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"/>
    

@Configuration、@Bean

  1. @Configuration:使用此注解的类,可以取代applicationContext.xml
    1. 它也是一个@Component, 所以可以通过component-scan扫描
  2. 可以在@Configuration类中,使用@Bean修饰方法,进行bean对象的创建
    1. 默认情况下,方法名就是bean的id。也可以通过name、value属性设置bean的id
    2. 可以配合@Scope设置bean的创建次数
    3. 默认情况下,当IoC容器创建的时候,会调用所有@Bean修饰方法,生成对象放入到容器中
     //com.zh.cfg.BeanConfig
     //使用此注解的类,可以取代applicationContext.xml
     @Configuration
     public class BeanConfig {
         //使用@Bean修饰方法,进行bean对象的创建
         // 默认情况下,方法名就是bean的id。也可以通过name、value属性设置bean的id
         @Bean
         //通过name属性设置id
         //@Bean(name = "skillx")
         //可以配合@Scope设置bean的创建次数
         //@Scope("prototype")
         //@Lazy
         public Skill skill() {
             Skill skill = new Skill();
             return skill;
         }
     }
    
  3. 下面讲解:一个对象注入为bean对象时、基本数据类型时,如何使用注解
    1. 相当于之前xml文件中bean标签的类,注入属性为对象、基本数据类型,使用注解如何实现
    2. 注意下面讲的所有方法都在BeanConfig中

@Configuration修饰类中注入bean对象的3种方式

  1. 方式一:如果bean属性本身有@Autowired,那么直接new原来对象即可(前提条件是IoC容器中必须有这个bean参数对象

     //Dog类
     //表明可以通过scan将该类创建对象并存放到IoC容器中,保证容器中有dog对象
     @Component
     public class Dog {
     }
     //bean属性本身有@Autowired,但是没有写@Component
     public class Person {
         private Dog dog;
         @Autowired
         public void setDog(Dog dog) {
             this.dog = dog;
         }
     }
        
     //直接new对象即可
     @Configuration
     public class BeanConfig {
         //尽管Person类没有用@Component修饰,但是这句话会将person对象放入容器
         @Bean
         public Person person(){
             return new Person();
         }
     }
    
  2. 方式二:Spring会利用@Autowired技术,自动注入bean给@Bean方法的参数(前提条件是IoC容器中必须有这个bean参数对象

     //保证容器中有dog对象
     @Bean
     public Dog dog() {
         return new Dog();
     }
     @Bean
     //参数自动注入
     public Person person(Dog dog)  {
         Person person = new Person();
         person.setDog(dog);
         return person;
     }
    
  3. 方式三:直接调用@Bean方法来注入

     @Bean
     public Dog dog() {
         return new Dog();
     }
     @Bean
     //不传参直接内部调用@Bean方法
     public Person person()  {
         Person person = new Person();
         //直接调用bean方法来注入
         person.setDog(dog());
         return person;
     }
     @Bean
     public Student student(){
         Student student = new Student();
         //这个dog与Person的dog是同一个对象
         student.setDog(dog());
         return student;
     }
    
    1. 这里有个细节:@Bean方法被直接调用多次,也能保证是singleton(同一个对象)
      1. 上面2个创建直接调用dog(),为什么是同一个对象?
      2. 因为@Configuration底层使用了CGLib动态代理,对@Bean方法进行了增强处理。
      3. 调用dog()时,本质不是BeanConfig对象调用的,而是他的继承子类(CGLib本质是继承)去调用的,比如BeanConfigSub,该子类对所有父类的bean方法做了过滤,如果当前容器中有对象,就不在调用父类的方法,直接返回

FactoryBean

  1. 这个是复杂对象的注入,请回看spring第一节“复杂对象的注入-FactoryBean”
  2. 创建一个DogFactoryBean类

     //DogFactoryBean对象会放入到容器中
     @Component
     public class DogFactoryBean implements FactoryBean<Dog> {
         //dog对象也会放到容器中
         @Override
         public Dog getObject() throws Exception {
             return new Dog();
         }
        
         @Override
         public Class<?> getObjectType() {
             return Dog.class;
         }
     }
    
  3. BeanConfig(方式一:)

     @Bean
     public DogFactoryBean dog() {
         return new DogFactoryBean();
     }
        
     //直接通过@Bean方法dog()拿到DogFactoryBean对象,然后拿到dog对象
     @Bean
     public Student student() throws Exception {
         Student student = new Student();
         student.setDog(dog().getObject());
         return student;
     }
    
  4. 测试1

     System.out.println(ctx.getBean("dog"));
    
    1. 表面上看,@Bean方法返回的是DogFactoryBean类型对象
    2. 由于@Configuration底层使用CGLib
    3. @Bean进行了增强处理
    4. 会调用DogFactoryBean的getObject方法
  5. 方式2:直接到容器中拿DogFactoryBean对象

     //直接不写dog()方法,这样容器中就没有id为dog的对象
        
     //根据容器中已经存储的bean对象,自动注入参数
     @Bean
     public Person person(DogFactoryBean bean) throws Exception {
         Person person = new Person();
         person.setDog(bean.getObject());
         return person;
     }
     @Bean
     public Student student(DogFactoryBean bean) throws Exception {
         Student student = new Student();
         student.setDog(bean.getObject());
         return student;
     }
    
  6. 测试2

     //此时打印的是dog对象,而不是dogFactoryBean对象,会自动调用DogFactoryBean类的getObject方法,拿到的是方法的返回值
     System.out.println(ctx.getBean("dogFactoryBean"));
     //返回的才是dogFactoryBean对象
     System.out.println(ctx.getBean("&dogFactoryBean"));
    

注入其他类型

  1. 配置文件

     //skill.properties
     name=Rose
     level=20
    
  2. BeanConfig

     @Configuration
     //获取配置文件数据
     @PropertySource("skill.properties")
     public class BeanConfig {
         //从配置文件中取值,然后注入给BeanConfig的成员变量
         @Value("${name}")
         private String name;
         @Value("${level}")
         private int level;
            
         @Bean
         public Skill skill() {
             Skill skill = new Skill();
             //直接写固定值
             //skill.setName("jack");
             //skill.setLevel(1);
             //从当前类的成员变量中注入
             skill.setName(name);
             skill.setLevel(level);
             return skill;
         }
     }
    

创建工厂的入口

  1. 如果创建工厂的入口是XML,一般就用ClassPathXmlApplicationContext
    1. 可以通过<context:component-scan>去扫描注解信息
    2. 可以通过<import>导入其他XML配置文件
    3. 这样就可以实现入口是xml,然后xml与注解混合使用的方式
     //1. 入口:通过代码加载xml,创建容器
     ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
     //2.applicationContext.xml文件
     //扫描所有注解的包下的类
     <context:component-scan base-package="com.zh"/>
     <!--    <import resource="其他xml文件"-->
    
  2. 如果创建工厂的入口是注解,一般就用AnnotationConfigApplicationContext
    1. 即入口可不从加载xml文件来作为入口,直接通过扫描注解
    2. 可以通过@Import、@ComponentScan扫描其他注解信息
      1. ComponentScan:只能扫描到带有@Component修饰的类;@Configuration也可以,打开源码可以看到它本质也是@Component
      2. 即只能扫描用@Component相关修饰的类
      3. 用AnnotationConfigApplicationContext作为入口直接加载的类,可以不用@Component修饰
    3. 可以通过@ImportSource导入其他XML配置文件
    4. 注意: 通过@Import导入创建的bean的id是全类名
     //可以直接加载这边包下的所有的类,不管类是否用@Component相关修饰
     ctx = new AnnotationConfigApplicationContext("com.zh");
     //单独加载一个类到容器中,不管这类是否用@Component相关修饰
     ctx = new AnnotationConfigApplicationContext(Person.class);
    

举例

  1. 通过代码设置入口类(可以传递多个),但容器中只会存在这几个类对应的对象,除非这几个类中使用@Import@ComponentScan注解,扫描其他类

     //类,没有用@Component修饰
     public class Dog {}
        
     //设置入口:只设置一个类
     ctx = new AnnotationConfigApplicationContext(Dog.class);
     //打印有值
     System.out.println(ctx.getBean("dog"));
     //打印无值,容器中没有person对象
     System.out.println(ctx.getBean("person"));
    
  2. 可以通过@Import扫描其他注解信息

     //类
     @Component
     //@Import注解,告诉Spring扫描其他的类
     @Import(Person.class)
     //@Import({Person.class, BeanConfig.class})
     public class Dog {}
        
     //设置入口:只设置一个类
     ctx = new AnnotationConfigApplicationContext(Dog.class);
     //打印有值
     System.out.println(ctx.getBean("dog"));
     //打印无值
     System.out.println(ctx.getBean("person"));
     //打印有值:通过Import导入的扫描出来的类,在容器中创建的对象的id不是类名的小写,而是全类名
     System.out.println(ctx.getBean("com.zh.domain.Person"));
    
  3. 可以通过@ComponentScan扫描其他注解信息

     //类
     @Component
     //只能扫描这个包下的所有用@Component相关修饰类的注解
     @ComponentScan("com.zh")
     public class Dog {}
        
     //没有用@Component相关修饰
     public class FixConfig {}
        
     //有用@Component相关修饰
     @Component
     public class Person {}
        
     //设置入口:只设置一个类
     ctx = new AnnotationConfigApplicationContext(Dog.class);
     //打印有值
     System.out.println(ctx.getBean("dog"));
     //打印有值
     System.out.println(ctx.getBean("person"));
     //打印无值
     System.out.println(ctx.getBean("fixConfig"));
    
  4. 可以通过@ImportSource导入其他XML配置文件(这样可以实现注解与xml混合编写代码)

     <!-- 纯注解类作为入口********** -->
     @Configuration
     //注解与xml混合开发
     @ImportResource("applicationContext.xml")
     public class FixConfig {}
        
     <!--XML开发**********-->
     //applicationContext.xml
     <bean id="userService" class="com.zh.service.impl.UserServiceImpl" p:name="zh666"/>
        
     //UserServiceImpl类
     public class UserServiceImpl {
         private String name;
         public void setName(String name) {
             this.name = name;
         }
     }
        
     <!-- 设置入口**********-->
     //设置入口:只设置一个类
     ctx = new AnnotationConfigApplicationContext(FixConfig.class);
     //有值
     System.out.println(ctx.getBean("userService"));
    
  5. 通常情况下,最常用的使用方式是通过一个核心类作为入口,再在这个核心类中关联其他类

     //1.入口加载一个核心类
     ctx = new AnnotationConfigApplicationContext(BeanConfig.class);
        
     //2.核心类BeanConfig
     @Configuration
     //扫描这个包下的所有用@Component相关修饰的类的注解
     @ComponentScan("com.zh")
     public class BeanConfig {}
        
     //3. 其他类
     @Configuration
     //注解与xml混合开发
     @ImportResource("applicationContext.xml")
     public class FixConfig {}
        
     //dog类
     @Component
     public class Dog {}
     ...
    

bean的创建方式总结

  1. 在Spring中,bean有3种常见的创建方式(将一个对象放入到IoC容器中)
    1. @Component:常用于源码可修改、创建过程比较简单(直直接调用构造方法即可)的类型
      1. 直接在一个类前面使用,然后通过xml标签或者注解扫描
      2. id值默认就是类名的首字母小写,也可以@Component("id值")指定id值
    2. @Bean:常用于源码不可修改的类型 (比如第三方库的类型)或者创建过程比较复杂的类型
      1. @Configuration修饰的类中使用
      2. id值就是方法的名称

         //创建过程比较复杂的类型
         @Bean
         public SqlSession session(){
             SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(null);
             return factory.openSession();
         }
        
    3. <bean>:适用于所有类型
      1. 就是在xml中使用
      2. id值就是主动设置的值
  2. 当上述三种方式bean的id相同时, 优先级如下
    1. <bean>大于@Bean大于@Component
    2. 因为<bean>可以用于覆盖以前@Bean@Component的内容,所以减少@Bean@Component代码的变动
      1. 通过<bean>全局设置,可以不用到某个具体类中去修改代码
      2. 假设一个项目使用纯注解开发,但是我不想通过去修改原来的代码来进行修改,那么就可以创建一个xml文件(applicationContext.xml),然后通过<bean>修改那个类的东西,然后再创建一个类,使用注解与xml混合来实现,将这个类与xml自动导入到IoC容器中

         @Configuration
         //注解与xml混合开发
         @ImportResource("applicationContext.xml")
         public class FixConfig {}
        
Table of Contents