文章目录
注解开发的好处
- 注解开发方便
代码简洁,开发速度大大提高。 - Spring开发潮流
Spring2.x引入注解,Spring3.x完善注解,SpringBoot普及、推广注解。
注解的作用
- 替换XML这种配置形式,简化配置。
- 替换接口,实现调用双方的契约性。
通过注解的方式,在功能调用者和功能提供者之间达成约定,进而进行功能的调用。因为注解应用更为方便灵活,所以现在的开发中,更推荐使用注解。
Spring注解的发展历程
- Spring2.x:开始支持注解编程 @Component、@Service、@Scope、…
目的:提供的这些注解只是为了在某些情况下简化XML的配置,作为XML开发的有益补充。 - Spring3.x:@Configuration、@Bean、…
目的:彻底替换XML,基于纯注解编程。 - Spring4.x:SpringBoot
提倡使用注解开发。
Spring注解开发的一个问题
Spring基于注解进行配置后,还能否解耦呢?
在Spring框架应用注解时,如果对注解的配置内容不满意,可以通过Spring配置文件进行覆盖,不需要去修改代码。
Spring的基础注解(Spring2.x)
这个阶段的注解仅仅简化了XML的配置,并不能完全代替XML。
对象创建的相关注解
搭建开发环境
<context:component-scan base-package="com.angenin"/>
作用:让Spring框架扫描指定包及其子包中的注解,让注解生效。
1. @Component
作用:替换原有Spring配置文件中的bean标签。
注意:
- id属性:由component注解提供默认的设置方式——首单词首字母小写
- class属性:通过反射获得class的全限定类名。
@Component
public class User implements Serializable {
private Integer id;
private String name;
private String password;
//get set
}
细节:
- 指定工厂创建对象的id值:在component注解中直接加入id值即可
@Component("u") public class User implements Serializable {...}
- Spring配置文件覆盖注解的配置内容
<!--bean标签中的id和class都需要和要修改的相同,只有相同才能进行覆盖,不相同的话会创建新的对象--> <bean id="u" class="com.angenin.bean.User"/>
@Component的衍生注解:
本质上衍生注解就是@Component注解,作用、细节、用法都完全一致。
目的:更加准确的表达一个类型的作用。
- @Repository --> DAO类
- @Service --> Service类
- @Controller --> Controller类
注意:Spring整合MyBatis开发过程中,DAO类不使用@Repository、@Component,因为DAO类不需要我们去创建。
2. @Scope
作用:控制简单对象的创建次数。
注意:不添加@Scope,默认为singleton(单例)。
@Component
@Scope("prototype") // 多例
public class Customer {}
单例(Singleton):在整个应用中,只创建一个bean。
原型(Prototype):每次注入或者获取,都会创建一个新的bean。
会话(Session):在Web应用中,为每个会话创建一个bean。
请求(Request):在Web应用中,为每个请求创建一个bean。
3. @Lasy
作用:延迟创建单实例对象。(懒加载)
注意:使用@Lasy注解后,Spring会在使用到这个对象的时候才创建对象。
@Component
@Lazy
public class Account {
public Account() {
System.out.println("创建account对象");
}
}
4. 生命周期方法的相关注解
之前:
- 初始化相关方法:
实现Initializing接口
或<bean init-method="" />
- 销毁相关方法:实现
DisposableBean接口
或<bean destory-method=""/>
注意:
- 下面两个注解并不是Spring提供的,而是JSR(JavaEE规范)520,Spring很好的兼容了JSR。
- 再一次的验证了,通过注解实现了接口的契约性。
@PostConstruct
初始化方法
@Component
public class Product {
@PostConstruct
public void myInit() {
System.out.println("Product...myInit...");
}
...
}
@PreDestroy
销毁方法
@Component
public class Product {
@PreDestroy
public void myDestroy() {
System.out.println("Product...myDestroy...");
}
...
}
注入的相关注解
@Autowired、@Qualifier与@Resouce
用户自定义类型注入
UserDAO
public interface UserDAO {
void save();
}
UserDAOImpl
@Repository
public class UserDAOImpl implements UserDAO {
@Override
public void save() {
System.out.println("save...");
}
}
UserService
public interface UserService {
void register();
}
UserServiceImpl
@Service
public class UserServiceImpl implements UserService {
private UserDAO userDAO;
public UserDAO getUserDAO() {
return userDAO;
}
@Autowired // 后面会讲,不要急
public void setUserDAO(UserDAO userDAO) {
this.userDAO = userDAO;
}
@Override
public void register() {
userDAO.save();
}
}
测试:
@Test
public void test01() {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// id为首单词首字母小写
UserService userService = (UserService) ctx.getBean("userServiceImpl");
userService.register();
}
细节:
-
基于类型注入:注入对象的类型,必须与目标成员变量类型相同或者是其子类(或实现类)。【推荐】
-
基于名字注入:需要配合@Qualifier注解使用。【了解】
输入的id对应的bean必须存在,否则都不能通过IDEA的检查。
-
@Autowired注解放置的位置
- 放置在对应成员变量的set方法上。(set注入)
- 直接放置在成员变量之上,Spring通过反射直接对成员变量进行注入(赋值),也不需要写set方法,@Qualifier注解也可以直接加到成员变量之上。【更加推荐】
-
JavaEE规范中类似功能的注解
JSR250:@Resouce(name = “注入的bean的id”)注解,基于名字进行注入,等同于@Autowired和@Qualifier二合一,如果不指定name,默认基于类型进行注入,也推荐使用。
JSR330:@Inject:作用于@Autowired完全一致,基于类型进行注入,使用时需要引入对应的jar包,在Spring开发中应用的比较少,主要应用在EJB3.0中,了解即可。
<dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> </dependency>
@Value
JDK类型注入
开发步骤:
- 设置xxx.properties配置文件,用于保存值(格式:key = value)。
init.propertiesid = 1 name = angenin
- 读取配置文件
applicationContext.xml<!--读取配置文件--> <context:property-placeholder location="classpath:init.properties"/>
- 注入,@Value("${key}")
Category@Component public class Category { @Value("${id}") private Integer id; @Value("${name}") private String name; //get set }
细节:
- @Value注解不能应用在静态成员变量上,如果应用,赋值(注入)失败。
- @Value注解 + properties配置文件这种方式,不能注入集合类型,解决办法是使用yaml(yml)配置文件(yml配置文件会在最后讲解)。
@PropertySource
作用:用于替换Spring配置文件中的
<context:property-placeholder location=""/>
标签。
开发步骤:
- 设置xxx.properties配置文件
- 应用@PropertySource注解,加在要注入的类上。
- 注入
@Component @PropertySource("classpath:/init.properties") public class Category { @Value("${id}") private Integer id; @Value("${name}") private String name; ... }
注解扫描详解
<context:component-scan base-package="com.angenin"/>
扫描指定包及其子包,当想要更为细致的指定哪些包想要扫描,哪些包不需要扫描,需要以下两种方式。
1. 排除方式
由context:exclude-filter标签指定排除。
<context:component-scan base-package="com.angenin">
<context:exclude-filter type="" expression=""/>
</context:component-scan>
type共有五种:
- assignable:排除特定的类型,不进行扫描。【推荐】
<context:component-scan base-package="com.angenin"> <!--排除User类,不扫描--> <context:exclude-filter type="assignable" expression="com.angenin.bean.User"/> </context:component-scan>
- annotation:排除特定的注解,不进行扫描。【推荐】
<context:component-scan base-package="com.angenin"> <!--排除@Service注解,不扫描--> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/> </context:component-scan>
- aspectj:包、类切入点表达式【更加推荐】
<context:component-scan base-package="com.angenin"> <!--包切入点表达式:排除bean包及其子包下的所有类,不扫描--> <context:exclude-filter type="aspectj" expression="com.angenin.bean..*"/> <!--类切入点表达式:排除所有User类,不扫描--> <context:exclude-filter type="aspectj" expression="*..User"/> </context:component-scan>
- regex:正则表达式【了解即可】
- custom:自定义排除策略(框架底层用得多)【了解即可】
排除策略可以叠加使用,即使用多个context:exclude-filter标签。
<context:component-scan base-package="com.angenin">
<!--排除User类,不扫描-->
<context:exclude-filter type="assignable" expression="com.angenin.bean.User"/>
<!--包切入点表达式:排除bean包及其子包下的所有类,不扫描-->
<context:exclude-filter type="aspectj" expression="com.angenin.bean..*"/>
</context:component-scan>
2. 包含方式
<context:component-scan base-package="com.angenin" use-default-filters="false">
<context:include-filter type="" expression=""/>
</context:component-scan>
包含方式与排除方式的不同点:
use-default-filters="false"
作用:让Spring默认的注解扫描方式失效。<context:include-filter type="" expression=""/>
作用:指定扫描哪些注解。
type和上面排除方式的type使用方式一样,但意思相反,被指定的是需要扫描的。
<context:component-scan base-package="com.angenin" use-default-filters="false">
<!--只扫描@Service和@Repository注解-->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
包含策略也是可以叠加使用,即使用多个context:include-filter标签。
对于注解开发的思考
-
配置互通
Spring的注解配置是与配置文件的配置互通的。 -
什么情况下使用注解,上面情况下使用配置文件?
- 在程序员开发的类型上(自己写的类上),我们可以加入对应的注解,进行对象的创建,如User、UserDAO、UserService。
- 应用其他非程序员开发的类型时(框架提供的类),还是需要使用bean标签进行配置,因为我们并不能直接源码添加注解,如SqlSessionFactoryBean、MapperScannerConfigure。
Spring低版本是这样的,不过高版本已经解决这个问题了。
Spring的高级注解(Spring3.x及以上)
1. @Configuration
配置Bean
Spring在3.x提供的新的注解,用于替换XML配置文件。
@Configuration // 加上这个注解后,表明这个类为一个配置类,用于替换XML的配置文件
public class AppConfig{
...
}
- 配置Bean在应用的过程中替换了XML具体什么内容呢
- AnnotationConfigApplicationContext
1. 创建工厂代码 ApplicationContext ctx = new AnnotationConfigApplicationContext(..); 2. 指定配置文件 1. 指定配置Bean的Class ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); 2. 指定配置Bean所在的路径(会扫描包下及其子包下加了@Configuration注解的配置类) ApplicationContext ctx = new AnnotationConfigApplicationContext("com.angenin");
配置Bean开发的细节分析
-
基于注解开发使用日志,不能集成log4j,集成logback
-
引入jar包
<dependency> <groupId>org.logback-extensions</groupId> <artifactId>logback-ext-spring</artifactId> <version>0.1.4</version> </dependency>
-
引入logback配置文件(logback.xml)
<?xml version="1.0" encoding="UTF-8" ?> <configuration> <!--控制台输出--> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度,%msg:日志消息,%n:换行符--> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> </appender> <!--日志级别:debug--> <root level="DEBUG"> <appender-ref ref="STDOUT"/> </root> </configuration>
-
-
@Configuration注解的本质
本质:也是@Component注解的衍生注解。
可以应用context:component-scan标签进行扫描,不过注解版就不会用标签了,改为使用对应的注解进行扫描。
2. @Bean
@Bean注解需要在配置Bean(配置类)中使用,等同于XML配置文件中的bean标签。
@Bean注解的基本使用
对象创建
加上@Bean的方法,如同bean标签,该方法必须是public,其返回值为创建的对象类型,方法名为创建的bean的id。
- 简单对象
User、UserService、UserDAO、… - 复杂对象
不能通过new的方式直接创建的对象。
Connection、SqlSessionFactory、…
User类
public class User {
}
AppConfig(配置类)
@Configuration
public class AppConfig {
// 简单对象:可以直接new的对象
@Bean
public User user() {
// 后面会解决直接new对象的问题
return new User();
}
// 复杂对象:不能直接new的对象
@Bean
public Connection conn() {
Connection conn = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/book?useSSL=false&serverTimezone=Asia/Shanghai",
"root", "123456");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
}
-
@Bean注解创建复杂对象的注意事项
ConnectionFactoryBean(自定义的connection工厂)
public class ConnectionFactoryBean implements FactoryBean<Connection> { @Override public Connection getObject() throws Exception { Class.forName("com.mysql.cj.jdbc.Driver"); Connection conn = DriverManager.getConnection( "jdbc:mysql://localhost:3306/book?useSSL=false&serverTimezone=Asia/Shanghai", "root", "123456"); return conn; } @Override public Class<?> getObjectType() { return Connection.class; } @Override public boolean isSingleton() { // 返回false,多例 return false; } }
AppConfig
// 主要用于遗留系统整合 @Bean public Connection conn1() { Connection conn = null; try { ConnectionFactoryBean factoryBean = new ConnectionFactoryBean(); conn = factoryBean.getObject(); } catch (Exception e) { e.printStackTrace(); } return conn; }
自定义id值
// 在括号里直接指定即可
@Bean("id")
控制对象创建次数
@Bean
@Scope("prototype") // 多例,默认singleton单例
@Bean注解的注入
1. 用户自定义类型注入
UserDAO、UserDAOImpl、UserService、UserServiceImpl和之前创建的一样,这里就不再赘述了。
@Configuration
public class AppConfig {
@Bean
public UserDAO userDAO() {
return new UserDAOImpl();
}
// 定义形参
@Bean
public UserService userService(UserDAO userDAO) {
UserServiceImpl userService = new UserServiceImpl();
// 调用set方法进行赋值
userService.setUserDAO(userDAO);
return userService;
}
}
简化写法(不需要形参):
@Configuration
public class AppConfig {
// userDAO不变
@Bean
public UserDAO userDAO() {
return new UserDAOImpl();
}
@Bean
public UserService userService() {
UserServiceImpl userService = new UserServiceImpl();
// 注入时,直接调用上面的方法即可
userService.setUserDAO(userDAO());
return userService;
}
}
2. JDK类型注入
AppConfig配置类
@Bean
public User user() {
User user = new User();
user.setId(11);
user.setName("angenin");
return user;
}
如果直接在代码中进行set方法的调用,会存在耦合问题。
注入时代码耦合的解决办法:
-
创建init.properties配置文件,用于存储键值对。
id = 11 name = angenin11
-
在配置类上加上
@PropertySource("classpath:/init.properties")
,扫描配置文件 -
定义成员变量,通过@Value注解赋值后,注入。
@Configuration @PropertySource("classpath:/init.properties") public class AppConfig { @Value("${id}") private Integer id; @Value("${name}") private String name; @Bean public User user() { User user = new User(); user.setId(id); user.setName(name); return user; } }
3. @ComponentScan
@ComponentScan注解在配置Bean中进行使用,等同于 XML配置文件中的context:component-scan标签。
作用:进行相关注解的扫描,如@Component、@Value、@Autowried、…
-
基本使用:
-
排除
// 可以用多个不同类型的@ComponentScan.Filter,并且每个@ComponentScan.Filter也可以排除多个,所以都是用{},多个用逗号,分隔。 // 排除 @ComponentScan(basePackages = "com.angenin.scan", excludeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Service.class, Component.class}), @ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = {"com.angenin..*", "*..User1"}) }) @Configuration public class AppConfig {...} /* type = FilterType.ASSIGNABLE_TYPE --> value .ANNOTATION --> value .ASPECTJ --> pattern .REGEX --> pattern .CUSTOM --> value */
五种type:
- assignable:排除特定的类型,不进行扫描。【推荐】
- annotation:排除特定的注解,不进行扫描。【推荐】
- aspectj:包、类切入点表达式【更加推荐】
- regex:正则表达式【了解即可】
- custom:自定义排除策略(框架底层用得多)【了解即可】
-
包含
// 包含 @ComponentScan(basePackages = "com.angenin.scan", // 关闭Spring的默认扫描方式 useDefaultFilters = false, includeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Service.class, Component.class}), @ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = {"com.angenin..*", "*..User1"}) })
Spring工厂创建对象的多种配置方式
-
多种配置方式的应用场景
-
配置优先级
优先级(从低到高):@component及其衍生注解 < @Bean < 配置文件bean标签。
优先级高的配置可以覆盖优先级低的配置。@Component public class User {} // ↑覆盖 @Bean public User user() { return new User(); } // ↑覆盖 <bean id="user" class="com.angenin.User"/>
覆盖时,需要保证id是相同的,否则就不是覆盖了,而是创建新的对象。
解决基于注解进行配置的耦合问题
--------配置Bean:AppConfig4--------- @Configuration public class AppConfig4 { @Bean public UserDAO userDAO() { return new UserDAOImpl(); } } --------applicationContext.xml--------- 覆盖userDAO,使用新的实现类 <bean id="userDAO" class="com.angenin.dao.UserDAOImplNew"/> --------不修改AppConfig4,需要创建一个新的配置Bean:AppConfig5-------- @Configuration @ImportResource("applicationContext.xml") public class AppConfig5 { } ------创建的工厂-------- // 可以同时引用多个配置Bean // ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig4.class, AppConfig5.class); // 也可以直接指向一个包 ApplicationContext ctx = new AnnotationConfigApplicationContext("com.angenin.appconfig");
整合多个配置信息
拆分多个配置Bean的开发,是一种模块化开发的形式,也体现了面向对象各司其职的设计思想。
多配置信息整合的方式
- 多个配置Bean的整合
- 配置Bean与@Component相关注解的整合
- 配置Bean与SpringXML配置文件的整合
整合多种配置需要关注的要点
- 如何使多个配置的信息汇总成一个整体
- 如何实现跨配置的注入
1. 多个配置Bean的整合
-
多配置的信息汇总
- base-package进行多个配置Bean的整合
- @Import
可以用于创建对象,也可以用于多配置Bean的整合。
- 在工厂创建时,指定多个配置Bean的Class对象【了解即可】
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig1.class, AppConfig2.class);
- base-package进行多个配置Bean的整合
-
跨配置进行注入
在应用配置Bean的过程中,不管使用哪种方式进行配置信息的汇总,其操作方式都是通过成员变量加入@Autowired注解完成的。@Configuration @Import(AppConfig2.class) public class AppConfig1 { @Autowired private UserDAO userDAO; @Bean public UserService userService() { UserServiceImpl userService = new UserServiceImpl(); userService.setUserDAO(userDAO); return userService; } } @Configuration public class AppConfig2 { @Bean public UserDAO userDAO() { return new UserDAOImpl(); } }
2. 配置Bean与@Component相关注解的整合
@Repository
public class UserDAOImpl implements UserDAO {
@Override
public void save() {
System.out.println("UserDAOImpl..save..");
}
}
@Configuration
@ComponentScan(basePackages = "com.angenin.dao") // 扫描添加到容器中
public class AppConfig3 {
@Autowired
private UserDAO userDAO;
@Bean
public UserService userService() {
UserServiceImpl userService = new UserServiceImpl();
userService.setUserDAO(userDAO);
return userService;
}
}
ApplicationContext ctx = new AnnotationConfigApplicationContext(com.angenin.config.AppConfig3.class);
3. 配置Bean与SpringXML配置文件的整合
<bean id="userDAO" class="com.angenin.dao.UserDAOImpl"/>
@Configuration
@ImportResource("applicationContext.xml")
public class AppConfig4 {
@Autowired
private UserDAO userDAO;
@Bean
public UserService userService() {
UserServiceImpl userService = new UserServiceImpl();
userService.setUserDAO(userDAO);
return userService;
}
}
ApplicationContext ctx = new AnnotationConfigApplicationContext(com.angenin.config.AppConfig4.class);
配置Bean底层实现原理
Spring在配置Bean中加入了@Configuration注解后,底层就会通过Cglib的代理方式,来进行对象相关的配置和处理。
四维一体的开发思想
四维一体:Spring开发一个功能的4种形式,虽然开发方式不同,但是最终效果是一样的。
- 基于schema
- 基于特定功能注解【推荐】
- 基于原始bean标签
- 基于@Bean注解【推荐】
四维一体的开发案例
- 基于schema
<bean id="user" class="com.angenin.bean.User"/> <context:component-scan base-package="com.angenin.bean"/> <context:property-placeholder location="classpath:init.properties"/> public class User { @Value("${id}") private Integer id; @Value("${name}") private String name; //get set }
- 基于特定功能注解【推荐】
<bean id="user" class="com.angenin.bean.User"/> @Component @PropertySource("classpath:init.properties") public class User { @Value("${id}") private Integer id; @Value("${name}") private String name; //get set }
- 基于原始bean标签
<context:component-scan base-package="com.angenin.bean"/> <!--PropertySourcesPlaceholderConfigurer否则读取properties文件信息--> <bean id="propertyholder" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"> <property name="location" value="classpath:init.properties"/> </bean> @Component public class User { @Value("${id}") private Integer id; @Value("${name}") private String name; // get set }
- 基于@Bean注解【推荐】
@Configuration @ComponentScan(basePackages = "com.angenin.bean") public class AppConfig { // PropertySourcesPlaceholderConfigurer否则读取properties文件信息 @Bean public PropertySourcesPlaceholderConfigurer configurer() { PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer(); configurer.setLocation(new ClassPathResource("init.properties")); return configurer; } } @Component public class User { @Value("${id}") private Integer id; @Value("${name}") private String name; // get set }
纯注解版AOP开发
搭建环境
应用配置Bean、注解扫描
@Configuration
@ComponentScan("com.angenin.aop")
public class AppConfig {
}
开发步骤
- 原始对象
public interface UserService { void register(); void login(); } @Service // 原始类 public class UserServiceImpl implements UserService { @Override public void register() { System.out.println("UserServiceImpl.register"); } @Override public void login() { System.out.println("UserServiceImpl.login"); } }
- 创建切面类(额外功能 + 切入点 + 组装切面)
@Component @Aspect // 切面类 public class MyAspect { // 切入点 @Pointcut("execution(* com.angenin.aop..*.*(..))") public void pointCut(){} @Around("pointCut()") // 组装切面 public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable { // 额外功能 System.out.println("-----log----"); Object ret = joinPoint.proceed(); return ret; } }
- 在配置Bean中启用AOP
@Configuration @ComponentScan("com.angenin.aop") @EnableAspectJAutoProxy // 开启AOP public class AppConfig { }
注解AOP细节分析
-
代理创建方式的切换(JDK、Cglib)
切换为Cglib动态代理(默认JDK动态代理) <aop:aspectj-autoproxy proxy-target-class=true/> @EnableAspectjAutoProxy(proxyTargetClass = true)
-
SpringBoot的AOP开发方式。
@EnableAspectJAutoProxy已经自动设置好了,我们只需要做前两步,创建原始对象和创建切面类即可。Spring的AOP代理默认为JDK动态代理,而SpringBoot的AOP代理默认的是Cglib动态代理。
纯注解版Spring+MyBatis整合
基础配置(配置Bean)
- 连接池
- SqlSessionFactoryBean
- MapperScannerConfigure
@Configuration
@ComponentScan(basePackages = "com.angenin.mybatis")
@MapperScan(basePackages = "com.angenin.mybatis") // 3. MapperScannerConfigure
public class MyBatisAutoConfiguration {
// 1. DataSource
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/angenin?useSSL=false&serverTimezone=Asia/Shanghai");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
}
// 2. SqlSessionFactoryBean
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setTypeAliasesPackage("com.angenin.mybatis");
// 设置mapper文件的路径(只设置一个,看下面通配的写法)
sqlSessionFactoryBean.setMapperLocations(new ClassPathResource("UserMapper.xml"));
return sqlSessionFactoryBean;
}
}
编码
- 实体
public class User { private Integer id; private String name; private String password; // get set }
- 表
use angenin; create table t_users( `id` int primary key auto_increment, `name` varchar(12), `password` varchar(12) ); desc t_users;
- DAO接口
public interface UserDAO { void save(User user); }
- Mapper文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.angenin.mybatis.UserDAO"> <insert id="save" parameterType="User"> insert into t_users(name,password)values(#{name},#{password}); </insert> </mapper>
MapperLocations编码时通配的写法
开发时更多用通配的写法。
// 设置mapper文件的路径(只设置一个,看下面通配的写法)
// sqlSessionFactoryBean.setMapperLocations(new ClassPathResource("UserMapper.xml"));
// 设置多个mapper文件的路径(通配写法)
try {
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// 注意:mapper文件是在resources目录下的,resources目录下是用/代表多级目录,所以这里com.angenin.mapper只是一个目录,不是多级目录
Resource[] resources = resolver.getResources("com.angenin.mapper/*Mapper.xml");
sqlSessionFactoryBean.setMapperLocations(resources);
} catch (IOException e) {
e.printStackTrace();
}
配置Bean数据耦合问题
提取配置信息到小配置文件中,方便以后的修改维护。
-
创建mybatis.properties小配置文件
mybatis.driverClassName = com.mysql.cj.jdbc.Driver mybatis.url = jdbc:mysql://localhost:3306/angenin?useSSL=false&serverTimezone=Asia/Shanghai mybatis.username = root mybatis.password = 123456 mybatis.typeAliasesPackages = com.angenin.mybatis mybatis.mapperLocations = com.angenin.mapper/*Mapper.xml
-
封装成一个mybatis配置信息类MyBatisProperties
// 封装成一个mybatis配置信息类 @Component @PropertySource("classpath:mybatis.properties") public class MyBatisProperties { @Value("${mybatis.driverClassName}") private String driverClassName; @Value("${mybatis.url}") private String url; @Value("${mybatis.username}") private String username; @Value("${mybatis.password}") private String password; @Value("${mybatis.typeAliasesPackages}") private String typeAliasesPackages; @Value("${mybatis.mapperLocations}") private String mapperLocations; // get set }
-
修改配置Bean
@Configuration @ComponentScan(basePackages = "com.angenin.mybatis") @MapperScan(basePackages = "com.angenin.mybatis") // 3. MapperScannerConfigure public class MyBatisAutoConfiguration { // 引入mybatis配置信息类 @Autowired private MyBatisProperties myBatisProperties; // 1. DataSource @Bean public DataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(myBatisProperties.getDriverClassName()); dataSource.setUrl(myBatisProperties.getUrl()); dataSource.setUsername(myBatisProperties.getUsername()); dataSource.setPassword(myBatisProperties.getPassword()); return dataSource; } // 2. SqlSessionFactoryBean @Bean public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSource); sqlSessionFactoryBean.setTypeAliasesPackage(myBatisProperties.getTypeAliasesPackages()); // 设置mapper文件的路径 // sqlSessionFactoryBean.setMapperLocations(new ClassPathResource("UserMapper.xml")); // 设置多个mapper文件的路径(通配写法) try { ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource[] resources = resolver.getResources(myBatisProperties.getMapperLocations()); sqlSessionFactoryBean.setMapperLocations(resources); } catch (IOException e) { e.printStackTrace(); } return sqlSessionFactoryBean; } }
纯注解版事务开发
- 原始对象
- 额外功能
- 因为原始对象添加事务属性注解
- 配置Bean,开启事务
@Service
@Transactional // 事务属性
public class UserServiceImpl implements UserService {
@Autowired
private UserDAO userDAO;
public UserDAO getUserDAO() {
return userDAO;
}
public void setUserDAO(UserDAO userDAO) {
this.userDAO = userDAO;
}
@Override
public void register(User user) {
userDAO.save(user);
}
}
@Configuration
// 因为在MyBatisAutoConfiguration中已经进行了包扫描,所以这里就不需要了
@EnableTransactionManagement // 开启事务
public class TransactionAutoConfiguration {
@Autowired
private DataSource dataSource;
// 额外功能
@Bean
public DataSourceTransactionManager dataSourceTransactionManager() {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
细节
- 多配置Bean汇总(包扫描)
也是SpringBoot的实现思想。ApplicationContext ctx = new AnnotationConfigApplicationContext("com.angenin.mybatis");
对于Spring与SpringMVC的整合,孙老师会在SpringMVC视频中讲解,等孙老师把SpringMVC出了会把笔记补上。
Spring框架中YML的使用
YML(YAML)是一种新形式的配置文件,比XML更简单,比Properties更强大。
用Properties配置存在的问题
- Properties表达过于繁琐,无法表达数据的内在联系。
- Properties无法表达对象和集合类型。
YML语法简介
1. 定义yml文件
xxx.yml 或 xxx.yaml
2. 最基本的语法
1. 基本语法(冒号后必须空格)
name: angenin
password: 123456
2. 对象概念(属性一般每次缩进两个空格)
user:
id: 1
name: angenin
3. 定义集合(用于缩进两个空格,并且-后要空格)
service:
- 111
- 222
Spring与YML集成思路的分析
-
创建yml配置文件
init.yml
name: angenin
password: 123456 -
读取yml并转换成properties
YamlPropertiesFactoryBean.setResources(yml配置文件的路径),路径不是简单的一个字符串,需要new一个ClassPathResource对象存放yml文件的路径。
通过YamlPropertiesFactoryBean的getObject方法获取转换后的properties。 -
应用PropertySourcesPlaceholderConfigurer
PropertySourcesPlaceholderConfigurer.setProperties(…); -
类中使用@Value注解进行注入
Spring与YML继承编码
环境搭建
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.23</version>
</dependency>
<!--最低版本不要低于1.18-->
编码
- 创建init.yml文件
# 基本语法 # name: angenin # password: 123456 # 对象类型 account: name: angenin password: 123456
- 配置Bean中,完成YML的读取与PropertySourcesPlaceholderConfigurer的创建
@Configuration @ComponentScan(basePackages = "com.angenin.yml") public class YmlAutoConfiguration { @Bean public PropertySourcesPlaceholderConfigurer configurer() { YamlPropertiesFactoryBean yamlPropertiesFactoryBean = new YamlPropertiesFactoryBean(); yamlPropertiesFactoryBean.setResources(new ClassPathResource("init.yml")); Properties properties = yamlPropertiesFactoryBean.getObject(); PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer(); configurer.setProperties(properties); return configurer; }
- 使用@Value注解
@Component public class Account { // @Value("${name}") @Value("${account.name}") private String name; // @Value("${password}") @Value("${account.password}") private String password; // get set }
Spring与YML集成的问题
-
集合处理的问题
list: - 1111 - 2222
@Value("${list}") private List<String> list;
无法进行赋值,需要使用EL表达式。
# 在yml配置文件中也不能用集合的语法,需要写成下面的格式,然后通过EL表达式通过逗号进行分割 list: 111,222
@Value("#{'${list}'.split(',')}") private List<String> list;
-
对象类型的YML进行配置时,过于繁琐。
@Value("${account.name}")
在SpringBoot提供@ConfigurationProperties注解,解决了上面两个问题。
学习视频(p151-p210):https://www.bilibili.com/video/BV185411477k?p=151