Spring基于注解编程详解

Spring基于注解编程详解

1. 注解编程概述

注解编程简单来讲指的是在类或方法上加上特定的注解,完成指定功能的开发。

为什么要使用注解进行编程?

  1. 注解开发简洁并且灵活性高
  2. Spring的潮流趋势

注解有什么作用?

  1. 替换XML文件配置形式,简化配置

  2. 替换接口,实现调用双方的契约性

    通过注解的⽅式,在功能调⽤者和功能提供者之间达成约定,进⽽进⾏功能的调⽤。因为
    注解应⽤更为⽅便灵活,所以在现在的开发中,更推荐通过注解的形式完成开发
    

    在这里插入图片描述

Spring注解开发中的一个问题:

Spring基于注解进行开发之后会不会导致耦合呢?

答:在Spring框架应用注解时,如果对注解的内容不满意,可以通过配置文件进行覆盖。

2. Spring基础注解

搭建开发环境:

<context:component-scan base-package="com.xxx"/>
作⽤:让Spring框架在设置包及其子包中扫描对应的注解,使其生效

2.1 对象创建相关注解

@Component

作用:相当于在配置文件中使用bean标签创建对象

注意: 创建出来的bean的id属性使用@Component注解提供了默认的设置方式,首单词字母小写(小驼峰命名)。

@Component
public class User {
}
||
|| 作用等同于
||
\/
<bean id="user" class="com.xxx.bean.User"/>

细节:

  1. 如何指定spring工厂创建出的bean的id值:

    @Component注解中有一个value属性,通过设置其值,来自定义id值
    如:
    @Component("u")
    
  2. Spring配置文件会覆盖掉注解的配置:

    <bean id="u" class="com.baizhiedu.bean.User"/>
    id值 class的值要和注解中的设置保持一致
    

@Component的衍生注解:

  • @Repository —> XXXDAO

    @Repository
    public class UserDAO{
    }
    
  • @Service

    @Service
    public class UserService{
    }
    
  • @Controller

    @Controller
    public class UserController{
    }
    
  • 注意

    • 本质上这些衍生注解就是@Component 作⽤ <bean 细节 @Service(“s”) 目的:更加准确的表达⼀个类型的作用
    • Spring整合Mybatis开发过程中一般不会使用@Repository @Component
@Scope

作用:控制对象创建的次数。

注意:Spring默认控制对象是单例的。

  • singleton:单例
  • prototype:可以创建多个对象

使用:

@Scope
public class User {
  
}
@Lazy

开起懒加载模式,只有要获取bean时才创建对象。

生命周期相关注解
1. 初始化相关⽅法 @PostConstruct
				InitializingBean
<bean init-method=""/>

2. 销毁⽅法 @PreDestroy
DisposableBean
<bean destory-method=""/>

注意: 1. 上述的2个注解并不是Spring提供的, JSR(JavaEE规范)520
2. 再⼀次的验证,通过注解实现了接⼝的契约性

2.2 注入相关注解

2.2.1 用户自定义类型 @Autowired

作用:为成员变量注入值。

使用举例
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDAO userDAO;

    public UserDAO getUserDAO() {
        return userDAO;
    }

    // @Autowired
    public void setUserDAO(UserDAO userDAO) {
        this.userDAO = userDAO;
    }
}
细节分析
  • Autowired注解基于类型进行注入(推荐使用)

    • 基于类型的注入:注入对象的类型,必须与目标成员变量类型相同或者是其子类(实现类)
  • Autowired + Qualifier 可以基于名字进行注入

    • 基于名字的注入:注入对象的id值,必须与Qualifier注解中设置的名字相同
    @Autowired
    @Qualifier("user")
    public class User {
    }
    
  • Autowired注解放置位置

    • 放置在对应成员变量的set方法上
    • 直接把这个注解放置在成员变量之上, Spring通过反射直接对成员变量进行注入(赋值) [推荐]
  • JavaEE规范中类似功能的注解

    • @Resource注解

      JSR250 @Resouce(name="userDAOImpl") 基于名字进行注⼊
      @Autowired()
      @Qualifier("userDAOImpl")
      注意:如果在应⽤Resource注解时,名字没有配对成功,那么他会继续按照类型进⾏注入。
      
    • @Inject注解(了解)

      JSR330 @Inject 作⽤ @Autowired完全⼀致 基于类型进⾏注⼊ ---》EJB3.0
      <dependency>
          <groupId>javax.inject</groupId>
          <artifactId>javax.inject</artifactId>
          <version>1</version>
      </dependency>
      
2.2.2 JDK类型 @Value

@Value注解可以从配置文件中读取配置进行注入。

使用步骤
  1. 编写properties配置文件

  2. 配置Spring工厂读取这个配置文件

    <context:property-placeholder location=""/>
    
  3. 使用

    在属性上标记
    @Value("${key}")
        
    @Value("${username}")
    private String username;    
    
@PropertySource标签
  1. 作用:

    ⽤于替换Spring配置⽂件中的<context:property-placeholder location=""/>标签
    
  2. 使用步骤:

    1. 设置xxx.properties
    2. 应⽤@PropertySource("classpath:/xxx.properties")
    3. 代码
    属性 @Value("${key}")
    
@Value注解使用细节
  • @Value注解不可以静态变量上(如果应用就会赋值失败)
  • @Value注解+Properties这种方式,不能注入集合类型
    • Spring提供新的配置形式 YAML YML (SpringBoot)

2.3 注解扫描详解

当前包 及其 子包

<context:component-scan base-package="com.xxx"/>
2.3.1 排除方式
<context:component-scan base-package="com.baizhiedu">
    <context:exclude-filter type="" expression=""/>
                        type:assignable:排除特定的类型 不进⾏扫描
                        annotation:排除特定的注解 不进⾏扫描
                        aspectj:切⼊点表达式
                        包切⼊点: com.baizhiedu.bean..*
                        类切⼊点: *..User
                        regex:正则表达式
                        custom:⾃定义排除策略框架底层开发
</context:component-scan>	
2.3.2 包含方式
<context:component-scan base-package="com.baizhiedu" use-defaultfilters="false">
	<context:include-filter type="" expression=""/>
</context:component-scan>

注意要设置为false:use-default-filters="false"

作⽤:让Spring默认的注解扫描⽅式 失效。
2. <context:include-filter type="" expression=""/>
作⽤:指定扫描那些注解
                        type:assignable:排除特定的类型 不进⾏扫描
                        annotation:排除特定的注解 不进⾏扫描
                        aspectj:切⼊点表达式
                                包切⼊点: com.baizhiedu.bean..*
                                类切⼊点: *..User
                        regex:正则表达式
                        custom:⾃定义排除策略框架底层开发

包含的⽅式⽀持叠加
<context:component-scan base-package="com.baizhiedu" use-defaultfilters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
    <context:include-filter type="annotation"
    expression="org.springframework.stereotype.Service"/>
</context:component-scan>

2.4 对于注解开发的思考

  • 注解与配置文件互通

    Spring注解配置 配置⽂件的配置 互通
    @Repository
    public class UserDAOImpl{
    }
    
    public class UserServiceImpl{
    	private UserDAO userDAO;
    	// set get
    } 
    
    在配置文件中可以使用注解注入的对象
    <bean id="userService" class="com.xxx.UserServiceImpl">
    	<property name="userDAO" ref="userDAOImpl"/>
    </bean>
    
  • 什么情况下使用注解?什么情况下使用配置文件?

    @Component 替换 <bean
    
    基础注解(@Component @Autowired @Value) 程序员开发类型的配置
    
    1. 在程序员开发的类型上 可以加⼊对应注解 进⾏对象的创建
    	User UserService UserDAO UserAction
    
    2. 应⽤其他⾮程序员开发的类型时,还是需要使⽤<bean 进⾏配置的
    	SqlSessionFactoryBean MapperScannerConfigure
    

3. Spring高级注解

3.1 配置Bean(@Configuration)

  • Spring在3.x提供的新的注解,用于替换XML配置文件。

    @Configuration
    public class AppConfig {
        
    }
    
  • 使用@Configuration注解替换了在xml文件中配置bean标签的内容

  • 使用AnnotationConfigApplicationContext 读取配置类创建Spring工厂

  • 配置Bean开发需要注意的细节:

    • 基于注解开发使用日志

      不能集成Log4j
      集成logback
      
    • @Configuration注解的本质

      本质:也是@Component注解的衍生注解
      可以应⽤<context:component-scan进⾏扫描(但是几乎不扫描)
      

3.2 @Bean

@Bean在配置文件中进行使用,与xml文件中的bean标签作用一致。

注意:使用@Bean注解标记的方法的方法名默认就是对象的id值。

3.2.1 @Bean注解的使用
  • 创建简单对象

    @Configuration
    public class AppConfig {
        @Bean
        public User user() {
            return new User();
        }  
    }
    
  • 创建复制对象

    @Configuration
    public class AppConfig {
        @Bean
        public Connection conn() {
            Connection conn = null;
            try {
                Class.forName("com.mysql.jdbc.Driver");
                conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/springdb?username=root&password=123456&useSSL=false","root", "123456");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return conn;
        }
    }
    
  • 可以整合遗留系统的代码

    遗留系统是使用继承FactoryBean的方式来创建复杂对象的,那么就可以通过
    @Bean
    public Connection conn1() {
        Connection conn = null;
        try {
            ConnectionFactoryBean factoryBean = new ConnectionFactoryBean();
            conn = factoryBean.getObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return conn;
    }
    
  • 自定义id值

    @Bean("xxx")
    
  • 控制对象创建的次数

    @Bean
    @Scope("singleton|prototype") 默认值 singleton
    
3.2.2 @Bean注解的注入
  • 用户自定义类型

    // 方式一:通过设置方法参数进行注入
    @Bean
    public UserDAO userDAO() {
        return new UserDAOImpl();
    }
    
    @Bean
    public UserService userService(UserDAO userDAO) {
        UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDAO(userDAO);
        return userService;
    }
    
    // 方式二:直接通过调用方法的方式
    @Bean
    public UserDAO userDAO() {
        return new UserDAOImpl();
    }
    
    @Bean
    public UserService userService() {
        UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDAO(userDAO());
        return userService;
    }
    
  • JDK类型注入

     @Bean
     public User user() {
         User user = new User();
         user.setId(1);
         user.setUsername("Tom");
         user.setPassword("123456");
         return user;
     }
    
    • JDK类型注入如果直接写到代码中会造成耦合,所以建议在配置文件中指定要注入的属性的值

      @PropertySource("classpath:/test.properties")
      public class AppConfig {
          @Value("${name}")
          private String username;
          
          @Value("${password}")
          private String password;
      
          @Bean
          public Customer customer() {
              Customer customer = new Customer();
              customer.setName(username);
              customer.setPassword(password);
              return customer;
          }
      }
      

3.3 @ComponentScan

@ComponentScan注解在配置bean中进行使用,等同于XML配置文件中的<context:component-scan>标签。

用处是进行相关注解的扫描。

3.3.1 基本使用
@Configuration
@ComponentScan(basePackages = "com.xxx.xxx")
public class AppConfig {
} 
<context:component-scan base-package=""/>
3.3.2 排除、包含使用
  • 排除使用

    原来使用标签形式:
    <context:component-scan base-package="com.baizhiedu">
        <context:exclude-filter type="assignable" expression="com.baizhiedu.bean.User"/>
    </context:component-scan>
    
    注解使用:
    @ComponentScan(basePackages = "com.jc.service",
            excludeFilters = {@ComponentScan.Filter(type =
                    FilterType.ANNOTATION, value = {Service.class}),
                    @ComponentScan.Filter(type =
                            FilterType.ASPECTJ, pattern = "*..User1")})
    
    type = FilterType.ANNOTATION 	  value
    			     .ASSIGNABLE_TYPE value
                     .ASPECTJ    	  pattern
                     .REGEX 		  pattern
                     .CUSTOM 	      value
    
  • 包含使用

    原来使用:
    <context:component-scan base-package="com.xxx" use-defaultfilters="false">
    <context:include-filter type="" expression=""/>
    </context:component-scan>
        
    现在注解使用:
    和排除形式一样,但是需要将默认过滤方式设为false(useDefaultFilters = false@ComponentScan(basePackages = "com.jc.service",
            useDefaultFilters = false,
            includeFilters = {@ComponentScan.Filter(type=
                    FilterType.ANNOTATION,value={Service.class})})    
    type = FilterType.ANNOTATION 	  value
    			     .ASSIGNABLE_TYPE value
                     .ASPECTJ    	  pattern
                     .REGEX 		  pattern
                     .CUSTOM 	      value
    

4. Spring工厂创建对象的多种配置方式

4.1 多种配置方式的应用场景

  • @Component注解:通常用在程序员开发时自定义的类(如UserServiceImpl等)。

  • @Bean注解:通常是框架提供的类型,或者其他人开发的类型(没有源码)时使用。

  • bean标签:纯注解开发时一般不使用,主要用于整合旧系统的开发时使用。

  • @Import注解:Spring框架底层使用,多配置bean整合。

    @Import(AppConfig1.class)
    

4.2 多种配置的优先级

xml配置文件中bean标签 > @Bean注解标记的配置类 > @Component注解

优先级高的配置会覆盖优先级低的配置:

@Component
public class User{
}

@Bean
public User user(){
	return new User();
}

<bean id="user" class="xxx.User"/>
配置覆盖: id值保持⼀致

解决基于注解进行配置的耦合问题:

使用@ImportResource 注解引入配置文件,在配置文件中进行配置覆盖。

假设要用UserServiceImplNew替换UserServiceImpl

@Configuration
@ImportResource("applicationContext.xml")
public class AppConfig{
    @Bean
	public UserDAO userDAO() {
		return new UserDAOImpl();
	}
}

applicationContext.xml:
<bean id="userDAO" class="com.baizhiedu.injection.UserDAOImplNew"/>

5. 整合多个配置信息

为什么要有多个配置信息?

拆分多个配置bean的开发,是⼀种模块化开发的形式,也体现了面向对象各司其职的设计思想。

例如:配置数据库的配置放到数据库相关配置类中,配置Redis的配置放到Redis相关配置类中。

多配置信息的整合方式:

  • 多个配置Bean的整合
  • 配置Bean与@Component相关注解的整合
  • 配置Bean与SpringXML配置⽂件的整合

整合多种配置需要关注那些要点?

  • 如何使多配置的信息汇总成⼀个整体
  • 如何实现跨配置的注入

5.1 多个配置Bean的整合

  • 有多个配置Bean,创建工厂时通过包扫描:

    ApplicationContext ctx = new AnnotationConfigApplicationContext("com.xxx.config");
    
  • 使用@Import注解,只读取一个配置Bean,另一个在这个配置Bean中导入

  • 跨配置域进行注入(使用@Autowired

    • 有两个配置Bean,其中一个配置Bean需要另一个配置Bean中创建的对象
    // 配置BeanAppConfig1
    @Configuration
    @Import(AppConfig2.class)
    public class AppConfig1 {
        @Autowired
        UserDAO userDAO;
    
        @Bean
        public UserService userService() {
            UserServiceImpl userService = new UserServiceImpl();
            userService.setUserDAO(userDAO);
            return userService;
        }
    }
    
    // 配置BeanAppConfig2
    @Configuration
    public class AppConfig2 {
        @Bean
        public UserDAO userDAO() {
            return new UserDAOImpl();
        }
    }
    

5.2 配置Bean与@Component相关注解的整合

配置Bean中与需要通过@Component注解创建出来的对象。

可以通过@Autowired注解进行注入:

@Component
public class UserDAOImpl implements UserDAO{
}

@Configuration
@ComponentScan("xxx")
public class AppConfig3 {
    @Autowired
    private UserDAO userDAO;
	@Bean
	public UserService userService() {
    	UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDAO(userDAO);
        return userService;
    }
}

5.3 配置Bean与配置文件整合

使用@ImportResource注解。

代码:

@Configuration
@ImportResource("classpath:/applicationContext.xml")
public class AppConfig1 {

    // 配置文件中创建的bean也可以通过@Autowired注解进行注入
    @Autowired
    UserDAO userDAO;

    @Bean
    public UserService userService() {
        UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDAO(userDAO);
        return userService;
    }

}

<bean id="userDAO" class="com.jc.dao.impl.UserDAOImpl"/>

6. 配置Bean的底层实现原理

首先思考一个问题,我们在配置Bean中写了如下内容:

@Bean
public User user() {
	return new User();
}

获取的时候:

ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig1.class);
User user1 = (User) ctx.getBean("user");
System.out.println("user1 = " + user1);
User user2 = (User) ctx.getBean("user");
System.out.println("user2 = " + user2);

获取User对象使用的是user()方法,那么获取第二次时不也是调用user()方法吗?在user()方法中我们直接new User()并返回,那么为什么在默认的控制对象创建次数为单例的情况下获取的是同一个对象呢?

这是因为在Spring创建出的配置Bean的对象是一个代理对象,采用动态代理的方式来增加额外功能,而动态代理的方式是CGlib。

retun new User()处打上断点进行调试中我们可以看到:

在这里插入图片描述

这就证明了Spring工厂创建出的配置Bean是CGlib方式生成的代理对象。

7. 纯注解版的AOP开发

7.1 开发步骤

回顾AOP开发的步骤:

  1. 原始类
  2. 额外功能
  3. 配置切入点
  4. 组装切面

在纯注解开发中也是一样的。

但是大致步骤为:

  1. 原始类
  2. 创建配置Bean
  3. 组装切面(创建切面类)

代码演示:

  1. 创建原始类

    public interface UserService {
        boolean login(String username, String password);
        boolean register(User user);
    }
    
    @Component
    public class UserServiceImpl implements UserService {
    
        @Override
        public boolean login(String username, String password) {
            System.out.println("UserServiceImpl.login");
            return false;
        }
    
        @Override
        public boolean register(User user) {
            System.out.println("UserServiceImpl.register");
            return false;
        }
    }
    
  2. 创建配置Bean

    @Configuration
    @EnableAspectJAutoProxy
    public class AppConfig {
    }
    
  3. 组装切面(创建切面类)

    @Aspect
    @Component
    public class MyAspect {
    
        @Pointcut("execution(* login(..))")
        public void pointcut(){}
    
        @Around("pointcut()")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("---额外功能---");
            Object ret = joinPoint.proceed();
            return ret;
        }
    
    }
    

7.2 细节分析

  1. 代理创建方式的切换 JDK | Cglib
    • <aop:aspectj-autoproxy proxy-target-class=true|false />
    • @EnableAspectjAutoProxy(proxyTargetClass)
  2. SpringBoot AOP的开发方式
    • @EnableAspectjAutoProxy 已经设置好了

8. 纯注解版事务编程

  1. 原始对象 XXXService

    <bean id="userService" class="com.xxx.service.UserServiceImpl">
      <property name="userDAO" ref="userDAO"/>
    </bean>
        
        ||
        ||
        ||
        \/
    
    @Service
    public class UserServiceImpl implements UserService{
          @Autowired
          private UserDAO userDAO;
    }
    
  2. 额外功能

    <!--DataSourceTransactionManager-->
     <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
       <property name="dataSource" ref="dataSource"/>
     </bean>
         
    	 ||
         ||
         ||
         \/
         
     @Bean
     public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource){
           DataSourceTransactionManager dstm = new DataSourceTransactionManager();
           dstm.setDataSource(dataSource);
           return dstm;
     }
    
  3. 事务属性

    @Transactional
    @Service
    public class UserServiceImpl implements UserService {
        @Autowired
        private UserDAO userDAO;
    }
    
  4. 基于Schema的事务配置

    <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
    
    	||
    	|| 改为使用注解形式在配置Bean上标记
    	||
    	\/
    
    @EnableTransactionManager ---> 配置Bean
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神烦狗闯入了你的博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值