文章目录
1.实现加载第三方的Bean
像controller、service,dao三层体系下编写的类,这些类都是我们在项目当中自己定义的类(自定义类)。当我们要声明这些bean,也非常简单,我们只需要在类上加上@Component以及它的这三个衍生注解(@Controller、@Service、@Repository),就可以来声明这个bean对象了。但是在我们项目开发当中,还有一种情况就是这个类它不是我们自己编写的,而是我们引入的第三方依赖当中提供的。
在pom.xml文件中,引入dom4j:
<!--Dom4j-->
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
dom4j就是第三方组织提供的。 dom4j中的SAXReader类就是第三方编写的。
当我们需要使用到SAXReader对象时,直接进行依赖注入是不是就可以了呢?
按照我们之前的做法,需要在SAXReader类上添加一个注解@Component(将当前类交给IOC容器管理)
结论:第三方提供的类是只读的。无法在第三方类上添加@Component注解或衍生注解。
那么我们应该怎样使用并定义第三方的bean呢?
- 如果要管理的bean对象来自于第三方(不是自定义的),是无法用@Component 及衍生注解声明bean的,就需要用到 @Bean 注解。
1.解决方案1:在启动类上添加@Bean标识的方法
//声明第三方bean
@Bean //将当前方法的返回值对象交给IOC容器管理, 成为IOC容器bean
public SAXReader saxReader(){
return new SAXReader();
}
xml文件
<?xml version="1.0" encoding="UTF-8"?>
<emp>
<name>Tom</name>
<age>18</age>
</emp>
测试类:
//第三方bean的管理
@Test
public void testThirdBean() throws Exception {
Document document = saxReader.read(this.getClass().getClassLoader().getResource("1.xml"));
Element rootElement = document.getRootElement();
String name = rootElement.element("name").getText();
String age = rootElement.element("age").getText();
System.out.println(name + " : " + age);
}
运行结果
说明:以上在启动类中声明第三方Bean的作法,不建议使用(项目中要保证启动类的纯粹性)
注意事项 :
通过@Bean注解的name或value属性可以声明bean的名称,如果不指定,默认bean的名称就是方法名。
如果第三方bean需要依赖其它bean对象,直接在bean定义方法中设置形参即可,容器会根据类型自动装配。
关于Bean只需要保持一个原则:
- 如果是在项目当中我们自己定义的类,想将这些类交给IOC容器管理,我们直接使用@Component以及它的衍生注解来声明就可以。
- 如果这个类它不是我们自己定义的,而是引入的第三方依赖当中提供的类,而且我们还想将这个类交给IOC容器管理。此时我们就需要在配置类中定义一个方法,在方法上加上一个@Bean注解,通过这种方式来声明第三方的bean对象。
2.@ComponentScan 组件扫描
当前工程中如果引用了另外一个工程中的坐标依赖,如果包体不在要运行的类所在包以及子类里面,是无法被正常IOC的。但是可以在@SpringBoot的主类中使用注解进行加载此包体。
@SpringBootApplication
@ComponentScan({"com.weijisheng","com.example"}) //指定要扫描的包
public class SpringbootWebConfig2Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebConfig2Application.class, args);
}
}
结论:SpringBoot中并没有采用以上这种方案。
3.@Import 导入(使用@Import导入的类会被Spring加载到IOC容器中)
@Import导入
- 导入形式主要有以下几种:
- 导入普通类
- 导入配置类
- 导入ImportSelector接口实现类
下面是import的类的源码
1). 使用@Import导入普通类:
@Import(TokenParser.class) //导入的类会被Spring加载到IOC容器中
@SpringBootApplication
public class SpringbootWebConfig2Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebConfig2Application.class, args);
}
}
2). 使用@Import导入配置类:
- 配置类
@Configuration
public class HeaderConfig {
@Bean
public HeaderParser headerParser(){
return new HeaderParser();
}
@Bean
public HeaderGenerator headerGenerator(){
return new HeaderGenerator();
}
}
@Import(HeaderConfig.class) //导入配置类
@SpringBootApplication
public class SpringbootWebConfig2Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebConfig2Application.class, args);
}
}
3). 使用@Import导入ImportSelector接口实现类:
- ImportSelector接口实现类
public class MyImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//返回值字符串数组(数组中封装了全限定名称的类)
return new String[]{"com.example.HeaderConfig"};
}
}
我们使用@Import注解通过这三种方式都可以导入第三方依赖中所提供的bean或者是配置类。
思考:如果基于以上方式完成自动配置,当要引入一个第三方依赖时,是不是还要知道第三方依赖中有哪些配置类和哪些Bean对象?
是的。 (对程序员来讲,很不友好,而且比较繁琐)
思考:当我们要使用第三方依赖,依赖中到底有哪些bean和配置类,谁最清楚?
第三方依赖自身最清楚。
结论:我们不用自己指定要导入哪些bean对象和配置类了,让第三方依赖它自己来指定。
怎么让第三方依赖自己指定bean对象和配置类?
- 比较常见的方案就是第三方依赖给我们提供一个注解,这个注解一般都以@EnableXxxx开头的注解,注解中封装的就是@Import注解
4). 使用第三方依赖提供的 @EnableXxxxx注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)//指定要导入哪些bean对象或配置类
public @interface EnableHeaderConfig {
}
在使用时只需在启动类上加上@EnableXxxxx注解即可
@EnableHeaderConfig //使用第三方依赖提供的Enable开头的注解
@SpringBootApplication
public class SpringbootWebConfig2Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebConfig2Application.class, args);
}
}
以上四种方式都可以完成导入操作,但是第4种方式会更方便更优雅,而这种方式也是SpringBoot当中所采用的方式。
2.SpringBoot自动配置原理
@ComponetScan 注解用于扫描某一个包下的所有的类,将类带有注解:@componet,@controller,@service@repository.默认的情况下,会自动的扫描,该注解的类所在的包以及子包。
@SpringBootConfiguration 作用就是修饰类标识该类就是一个配置类。
@EnableAutoConfiguration 启用自动配置
1.main方法跑起来 (tomcat跑起来)
2.初始化容器 找到@springbootapplication注解
3.找到开启自动配置的注解@enableautoconfiguration
4.注解导入了importSelector (作用就是从外部文件中获取对应的【配置类】的全路径,放到spring容器 就会根据条件自动的进行配置)
5.内部获取到自动配置的注解对应的属性值 作为 外部配置文件的key
6.从meta-info/spring.factories中获取 刚刚上边获取到的key 找配置文件中匹配这个key对应的所有的值
值就是所有需要用到的【自动配置类】
7.交给spring容器,自动的根据条件来执行自动配置 spring容器中就自动的有了某一些Bean
3.@EnableAutoConfiguration(重要–>自动配置原理)
4.@Conditional
我们在跟踪SpringBoot自动配置的源码的时候,在自动配置类声明bean的时候,除了在方法上加了一个@Bean注解以外,还会经常用到一个注解,就是以Conditional开头的这一类的注解。以Conditional开头的这些注解都是条件装配的注解。下面我们就来介绍下条件装配注解。
@Conditional注解:
- 作用:按照一定的条件进行判断,在满足给定条件后才会注册对应的bean对象到Spring的IOC容器中。
- 位置:方法、类
- @Conditional本身是一个父注解,派生出大量的子注解:
- @ConditionalOnClass:判断环境中有对应字节码文件,才注册bean到IOC容器。
- @ConditionalOnMissingBean:判断环境中没有对应的bean(类型或名称),才注册bean到IOC容器。
- @ConditionalOnProperty:判断配置文件中有对应属性和值,才注册bean到IOC容器。
5.SpringBoot的yml配置
yaml文件等价于properties文件,在使用过程中都是一样的效果。但是yml文件书写的方式和properties文件不一样。更加简洁,那么我们可以根据需要选择性的使用properties和yml文件。如果同时存在两个文件,那么优先级properties要高于yml。
语法特点如下:
- 大小写敏感
- 数据值前必须有空格,作为分隔符
- 缩进的空格数目不重要,只需要对齐即可
#
表示注释
书写格式如下要求如下:key和key之间需要换行以及空格两次。 简单key value之间需要冒号加空格。
key1:
key2:
key3: value
key4: value4
比如:
server:
port: 8081
注意:yml语法中,相同缩进代表同一个级别
# 基本格式 key: value
name: zhangsan
# 数组 - 用于区分
city:
- beijing
- tianjin
- shanghai
- chongqing
#集合中的元素是对象形式
students:
- name: zhangsan
age: 18
score: 100
- name: lisi
age: 28
score: 88
- name: wangwu
age: 38
score: 90
#map集合形式(了解)
maps: {"name":"zhangsan", "age": "15"}
#参数引用
person:
name: ${name} # 该值可以获取到上边的name定义的值
1读取配置文件中的值
获取配置文件中的值我们一般有几种方式:
- @value注解的方式 只能获取简单值
- Environment的方式
- @ConfigurationProperties
yml中配置
# 基本格式 key: value
name: zhangsan
# 数组 - 用于区分
city:
- beijing
- tianjin
- shanghai
- chongqing
#集合中的元素是对象形式
students:
- name: zhangsan
age: 18
score: 100
- name: lisi
age: 28
score: 88
- name: wangwu
age: 38
score: 90
#map集合形式
maps: {"name":"zhangsan", "age": "15"}
#参数引用
person:
name: ${name} # 该值可以获取到上边的name定义的值
age: 12
java代码
@RestController
public class Test2Controller {
@Value("${name}")
private String name;
@Value("${city[0]}")
private String city0;
@Value("${students[0].name}")
private String studentname;
@Value("${person.name}")
private String personName;
@Value("${maps.name}")//value注解只能获简单的值对象
private String name1;
@Autowired
private Student student;
@RequestMapping("/show")
public String showHello() {
System.out.println(name);
System.out.println(city0);
System.out.println(studentname);
System.out.println(personName);
System.out.println(">>>>"+student.getAge());
return "hello world";
}
}
pojo
@Component
@ConfigurationProperties(prefix = "person")
public class Student {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
6. profile
在开发的过程中,需要配置不同的环境,所以即使我们在application.yml中配置了相关的配置项,当时在测试是,需要修改数据源等端口路径的配置,测试完成之后,又上生产环境,这时配置又需要修改,修改起来很麻烦。
- yml配置方式
application.yml:
#通过active指定选用配置环境
spring:
profiles:
active: pro
application-dev.yml:
#开发环境
server:
port: 8081
application-test.yml:
#测试环境
server:
port: 8082
applicatioin-pro.yml
#生产环境
server:
port: 8083
还有一种是分隔符的方式(了解)
spring:
profiles:
active: dev
---
#开发环境
server:
port: 8081
spring:
profiles: dev
---
#测试环境
server:
port: 8082
spring:
profiles: test
---
#生产环境
server:
port: 8083
spring:
profiles: pro
激活profile的方式(了解)
- 配置文件的方式(上边已经说过)
- 运行是指定参数
java -jar xxx.jar --spring.profiles.active=test
- 上面的spring.profiles.active=test就是在测试环境下使用
- jvm虚拟机参数配置 -Dspring.profiles.active=dev
7.springboot整合mybatis
1.导入依赖
<!--驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--mybatis的 起步依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
2.配置yml文件
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost/springboot_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: 1998
#配置mapper的映射文件的位置
mybatis:
mapper-locations: classpath:mappers/*Mapper.xml
3.Mapper扫描
@SpringBootApplication
@MapperScan(basePackages = "com.itheima.dao")
//MapperScan 用于扫描指定包下的所有的接口,将接口产生代理对象交给spriing容器
public class MybatisApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisApplication.class,args);
}
}
8.SpringBoot整合Junit
1.添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2.在service层创建一个UserService
@Service
public class UserService {
public String getUser() {
System.out.println("获取用户的信息");
return "zhangsan";
}
}
3.在test/java/下创建测试类,类的包名和启动类的报名一致即可
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootApplicationTests {
@Autowired
private UserService userService;
@Test
public void getUser() {
String userinfo = userService.getUser();
System.out.println(userinfo);
}
}
4.解释
@RunWith(SpringRunner.class) 使用springrunner运行器
@SpringBootTest 启用springboot测试
使用的方式和之前的spring的使用方式差不多。
9.SpringBoot整合redis
1.添加起步依赖
2.准备好redis服务器 并启动
3.在Service中的方法中使用
3.1 注入redisTemplate
3.2 在方法中进行调用
4.配置yml 配置redis的服务器的地址
1.添加起步依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.配置redis链接信息
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost/springboot_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password:
redis:
host: localhost
port: 6379
#配置mapper的映射文件的位置
mybatis:
mapper-locations: classpath:mappers/*Mapper.xm
3.service中调用
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RedisTemplate redisTemplate;
@Override
public List<User> findAllUser() {
//1.获取redis中的数据
List<User> list = (List<User>) redisTemplate.boundValueOps("key_all").get();
//2.判断 是否有,如果有则返回,如果没有则从mysql中获取设置到redis中再返回
if (list != null && list.size() > 0) {
return list;
}
List<User> allUser = userMapper.findAllUser();
//3 从mysql中获取设置到redis中再返回
redisTemplate.boundValueOps("key_all").set(allUser);
return allUser;
}
}
10.redis的序列化机制
如上图所示,出现了乱码,这个是由于redis的默认的序列化机制导致的。这里需要注意下:并不是错误,由于序列化机制,导致我们数据无法正常显示。如果有代码的方式获取则是可以获取到数据的。
1,默认的情况下redisTemplate操作key vlaue的时候 必须要求 key一定实现序列化 value 也需要实现序列化
2,默认的情况下redisTemplate使用JDK自带的序列化机制:JdkSerializationRedisSerializer
3,JDK自带的序列化机制中要求需要key 和value 都需要实现Serializable接口
4. RedisTemplate支持默认以下几种序列化机制:机制都实现了RedisSerializer接口
+ OxmSerializer
+ GenericJackson2JsonRedisSerializer
+ GenericToStringSerializer
+ StringRedisSerializer
+ JdkSerializationRedisSerializer
+ Jackson2JsonRedisSerializer
1.我们可以进行自定义序列化机制:例如:我们定义key 为字符串序列化机制,value:为JDK自带的方式则,应当处理如下:
@Bean
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
//设置key的值为字符串序列化方式 那么在使用过程中key 一定只能是字符串
template.setKeySerializer(new StringRedisSerializer());
//设置value的序列化机制为JDK自带的方式
template.setValueSerializer(new JdkSerializationRedisSerializer());
return template;
}
在工作中,根据我们业务的需要进行设置和选择,如果没有合适的还可以自己定义。只要实现RedisSerializer接口即可。