Spring基于注解编程详解
文章目录
1. 注解编程概述
注解编程简单来讲指的是在类或方法上加上特定的注解,完成指定功能的开发。
为什么要使用注解进行编程?
- 注解开发简洁并且灵活性高
- Spring的潮流趋势
注解有什么作用?
-
替换XML文件配置形式,简化配置
-
替换接口,实现调用双方的契约性
通过注解的⽅式,在功能调⽤者和功能提供者之间达成约定,进⽽进⾏功能的调⽤。因为 注解应⽤更为⽅便灵活,所以在现在的开发中,更推荐通过注解的形式完成开发
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"/>
细节:
-
如何指定spring工厂创建出的bean的id值:
@Component注解中有一个value属性,通过设置其值,来自定义id值 如: @Component("u")
-
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
注解可以从配置文件中读取配置进行注入。
使用步骤
-
编写properties配置文件
-
配置Spring工厂读取这个配置文件
<context:property-placeholder location=""/>
-
使用
在属性上标记 @Value("${key}") @Value("${username}") private String username;
@PropertySource标签
-
作用:
⽤于替换Spring配置⽂件中的<context:property-placeholder location=""/>标签
-
使用步骤:
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开发的步骤:
- 原始类
- 额外功能
- 配置切入点
- 组装切面
在纯注解开发中也是一样的。
但是大致步骤为:
- 原始类
- 创建配置Bean
- 组装切面(创建切面类)
代码演示:
-
创建原始类
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; } }
-
创建配置Bean
@Configuration @EnableAspectJAutoProxy public class AppConfig { }
-
组装切面(创建切面类)
@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 细节分析
- 代理创建方式的切换 JDK | Cglib
<aop:aspectj-autoproxy proxy-target-class=true|false />
@EnableAspectjAutoProxy(proxyTargetClass)
- SpringBoot AOP的开发方式
@EnableAspectjAutoProxy
已经设置好了
8. 纯注解版事务编程
-
原始对象 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; }
-
额外功能
<!--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; }
-
事务属性
@Transactional @Service public class UserServiceImpl implements UserService { @Autowired private UserDAO userDAO; }
-
基于Schema的事务配置
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/> || || 改为使用注解形式在配置Bean上标记 || \/ @EnableTransactionManager ---> 配置Bean