“我正在参加「掘金·启航计划」”
1. spring 入口:看以下两个几行代码:
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
ClassPathXmlApplicationContext context1 = new ClassPathXmlApplicationContext("spring.xml");
CompanyService companyService = (CompanyService)context.getBean("companyService");
companyService.test();
}
}
AnnotationConfigApplicationContext 和 ClassPathXmlApplicationContext 两者都是创建spring容器,只不过是加载配置的方式不一样,AnnotationConfigApplicationContext 是在spring3.0版本增加的一个类。
AnnotationConfigApplicationContext :通过加载配置类,在配置类上配置扫描路径,将扫描路径下的bean加载到spring容器当种。如下:
@ComponentScan("com.example.demo")
public class AppConfig {}
ClassPathXmlApplicationContext:通过加载配置文件,在spring.xml文件中配置扫描路径,将路径下的bean加载到spring容器当中。如下:
<context:component-scan base-package="com.example.demo"></context:component-scan>
因为springboot 中用的是AnnotationConfigApplicationContext,所以我们这里以AnnotationConfigApplicationContext为主来分析spring的核心原理。 然后,我们可以通过getBean("companyServcie")获取bean对象。
2. spring创建的bean和我们自己创建的对象有什么区别?
我们经常说spring帮我们管理bean,不用我们自己去创建bean对象,spring会帮我们创建bean对象,我们只需要获取bean对象就行。那么我们看下面这个简单的示例:
@Component
public class CompanyService {
@Autowired
private PersonService personService;
public void test(){
System.out.println("company test");
}
}
@Component
public class PersonService {
}
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
CompanyService companyService = (CompanyService)context.getBean("companyService");
CompanyService companyService1 = new CompanyService();
companyService.test();
}
}
有两个类,CompanyService , PersonService 。PersonService 是CompanyService 里的属性。且加了@Autowired注解。我们打debug执行:
可以看到,companyService里的personService属性是有值的,companyService1里的personService属性是没有值的,这也是自己创建bean和spring帮我们创建bean的不同之处。(只是其中一点,方便理解。)
由此,我们可以得出,spring在帮我们创建bean之后肯定做了其他处理,给personService属性赋值。
3.spring创建bean的生命周期
spring创建bean的过程大致可以概括为以下:
companyService --> 构造方法-->普通对象-->依赖注入-->初始化前-->初始化-->初始化后-->代理对象-->bean
4. spring 通过构造方法创建普通对象(推断构造方法)。
例如,要创建一个CompanyService对象,一般情况下,我们肯定是要用这个类的构造方法。spring 在去创建的时候是分这么几种情况:
- 当bean 中 没有构造方法的时候,如下代码: 会使用bean 默认自带的无参构造方法。
@Component
public class CompanyService {
private PersonService personService;
}
- 当bean 中 同时有无参构造方法和 有参构造方法,如下代码:会默认使用无参构造方法
@Component
public class CompanyService {
private PersonService personService;
public CompanyService() {
}
public CompanyService(PersonService personService) {
this.personService = personService;
}
}
-
当bean 中同时有两个有参构造方法(没有指定使用哪一个的时候),如下代码: 执行报错。
解决办法:可以在想用的方法上面加@Autowired注解。
@Component
public class CompanyService {
private PersonService personService;
private String user;
public CompanyService(PersonService personService) {
this.personService = personService;
}
public CompanyService(PersonService personService, String user) {
this.personService = personService;
this.user = user;
}
}
我们通过上面的三种情况可以得知:spring会去推断使用哪个构造方法来创建对象,也就是所谓的spring推断构造方法。
5.普通对象-->依赖注入
我们都知道一个bean中的属性加上@Autowired注解spring就会自动赋值。看下面这段代码
@Component
public class PersonService {
}
@Component
public class CompanyService {
private PersonService personService;
public CompanyService(PersonService personService) {
this.personService = personService;
}
}
@Component
public class CompanyService {
@Autowired
private PersonService personService;
}
上面这段代码,其实很简单,但是我还是想说明下: 加@Autowired注解 和 有参的构造方法 这两种写法,在spring 加载后都可以给 personService 属性赋值。
那我们看以下几种情况。
- 第一种情况
@ComponentScan("com.example.demo")
public class AppConfig {
@Bean
public PersonService personService1(){
return new PersonService();
}
@Bean
public PersonService personService2(){
return new PersonService();
}
}
@Component
public class PersonService {
}
@Component
public class CompanyService {
private PersonService personService;
public CompanyService(PersonService personService) {
this.personService = personService;
}
}
spring在去加载的时候会创建三个 PersonService 类型的bean, 那么在赋值属性的时候会选择 名称为personService 的bean给CompanyService的 personService 属性赋值。
- 第二种情况
@ComponentScan("com.example.demo")
public class AppConfig {
@Bean
public PersonService personService1(){
return new PersonService();
}
}
public class PersonService {
}
@Component
public class CompanyService {
private PersonService personService;
public CompanyService(PersonService personService) {
this.personService = personService;
}
}
spring 创建了 一个PersonService类型的bean,名字是:personService1 ,但是成功赋值给CompanyService 类中的personService属性。 3. 第三种情况
@ComponentScan("com.example.demo")
public class AppConfig {
@Bean
public PersonService personService1(){
return new PersonService();
}
@Bean
public PersonService personService2(){
return new PersonService();
}
}
public class PersonService {
}
@Component
public class CompanyService {
private PersonService personService;
public CompanyService(PersonService personService) {
this.personService = personService;
}
}
这种执行时会直接报错。 由上述三种情况可以得出,spring 在依赖注入的时候会先通过类型匹配,然后再通过名字。
6.初始化前(@PostConstruct)
我们再开发一些功能的时候,可能会遇到这样的问题:
在项目启动的时候,有些类的属性需要从数据库拿些数据给属性赋值,那这时候我们改怎么做呢? 亦或者是,我们想在项目启动成功的时候,将数据库的数据加载到缓存中。
首先,肯定是定义一个方法,这个方法的功能就是将加载数据,现在的问题就是,这个方法要在项目启动完成前执行。可以用 @PostConstruct 这个注解。将注解放到方法上面就可以了。为了形象点,看下面这个代码:
@Component
public class JopType {
// 将数据库中的工种缓存起来
public void getTypes(){
// 1. 查数据库获取工种
// 2. 加入到缓存中。
}
}
需求:在项目启动完成前将数据库中的工种加入到缓存中。
只需要这个样子就行:
@Component
public class JopType {
// 将数据库中的工种缓存起来
@PostConstruct
public void getTypes(){
// 1. 查数据库获取工种
// 2. 加入到缓存中。
}
}
spring创建bean初始化前,就是检查bean中有没有加@PostConstruct这个注解,如果加了,执行这个加了@PostConstruct注解的方法。
初始化前当然不止这么多东西,还有很多,这个只是简单的举例。我们看到这里就该想spring是怎么知道这些方法有没有加注解,是怎么判断的?我想大部分应该都是知道怎么判断的。
7.初始化(InitializingBean)
初始化和初始化前要实现的作用基本是一致的,这个是实现了一个接口:InitializingBean
@Component
public class JopType implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
}
}
实现 InitializingBean 接口,重写 afterPropertiesSet 方法。
spring在会判断这个类有没有实现这个接口,如果有实现这个接口,就会执行重写的方法。在初始化前描述的问题放到这里一样可以解决。
8.初始化后(AOP)到代理对象。
AOP切面:使用的是cglib代理。实现就是:创建一个代理类继承之前的类。在使用代理类创建代理对象。
public class CompanyServiceProxy extends CompanyService {
private CompanyService target;
public void test(){
target.test();
}
}
添加AOP
@Aspect
@Component
public class LyAspect {
@Before("execution(public void com.example.demo.service.CompanyService.test())")
public void before(JoinPoint joinPoint){
}
}
@ComponentScan("com.example.demo")
@EnableAspectJAutoProxy
public class AppConfig {
@Bean
public PersonService personService1(){
return new PersonService();
}
}
开启aop后执行,getBean 拿出来的Bean就是代理Bean了,我们可以看代理bean里面有target属性personServie 是有值的。而下面的personService是没有值的。
当我们进入到test方法里面的时候是有值的。
为什么会这样呢?其实是这样的。
1.创建一个代理类继承之前的类,然后在里面声明一个target属性,这个target属性的类型是父类。
2.再使用代理类创建代理对象,把普通对象赋值给代理对象的target属性。
public class CompanyServiceProxy extends CompanyService {
private CompanyService target;
public void test(){
@切面方法
target.test();
}
public CompanyService getTarget() {
return target;
}
public void setTarget(CompanyService target) {
this.target = target;
}
}
CompanyServiceProxy companyServiceProxy = new CompanyServiceProxy();
companyServiceProxy.setTarget("普通的companyService对象");
大致的流程就是这样,这个 "普通的companyService对象" 是spring前面已经创建过的。
spring判断bena是否被切(AOP)
1.spring会遍历所有的bean,找出所有的切面bean,带有@Aspect注解的类。
2.再遍历这些类
3.再遍历这些类的方法
4.找出与当前bean有关系的切面方法,缓存起来,以便后续执行。
9.spring 事物
看下面测试代码
@ComponentScan("com.example.demo")
@EnableTransactionManagement
@Configuration
public class AppConfig {
@Bean
public PersonService personService1(){
return new PersonService();
}
@Bean
public JdbcTemplate jdbcTemplate(){
return new JdbcTemplate(dataSource());
}
@Bean
public PlatformTransactionManager transactionManager(){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource());
return transactionManager;
}
@Bean
public DataSource dataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/chy?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8");
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
return dataSource;
}
}
@Component
public class CompanyService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
public void test() {
jdbcTemplate.execute("INSERT INTO `chy`.`ceshi`(`e`, `w`, `c`) VALUES (1, 1, 1);");
throw new NullPointerException();
}
}
以上代码我们可以测试下:
1.如上代码没做什么改动,执行后数据库表中是不会添加数据的。
2.将AppConfig配置类上的@Configuration注释了,执行后数据会插入到表中。
spring事物也是基于aop的 ,也就是说 事物会生成一个代理对象,那么这个代理对象的逻辑是这样的;
1.会判断有没有加@Transactional注解
2.创建一个数据库链接,这个数据库连接时 事物管理器创建的
3.修改属性comit 属性,自己手动控制提交。
4.执行业务方法
5.提交或者回滚。
加上事物获取bean,获取的是spring事物创建的代理对象
为什么加了 @Configuration 会生效不加事物就不会生效。
dataSource() 这里是个方法,如果加上@Configuration ,在执行dataSource()方法时回去缓存中拿DataSource对象,如果有就直接使用,没有就会创建。
我们再用事物的时候也会发现事物失效。
//事物失效
@Component
public class CompanyService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
public void test() {
jdbcTemplate.execute("INSERT INTO `chy`.`ceshi`(`e`, `w`, `c`) VALUES (1, 1, 1);");
//throw new NullPointerException();
getId();
}
@Transactional(propagation = Propagation.NEVER)
public void getId(){
}
}
//解决方法
@Component
public class CompanyService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private CompanyService companyService;
@Transactional
public void test() {
jdbcTemplate.execute("INSERT INTO `chy`.`ceshi`(`e`, `w`, `c`) VALUES (1, 1, 1);");
//throw new NullPointerException();
companyService.getId();
}
@Transactional(propagation = Propagation.NEVER)
public void getId(){
System.out.println("===");
}
}
上面这种写法事物是失效的。
事物失效问题:我们在使用事物的时候要考虑是不是事物的代理对象执行该方法,上面这种直接getId()相当于是普通对象调用getId(),而不是事物创建的代理对象调用。
以上个人见解,如有不对还请指正。