class userServiceProxy extends UserService(){ //生成代理类去继承UserService
UserService target;
public void test(){ //就可以重写test方法
//先执行@Before
//再执行被代理的逻辑
target.test();//所以代理类先调用子类test,接着调用切面逻辑,再而调用普通对象UserService的test方法(此时orderService是有值的)
}
}class userServiceProxy extends UserService(){ //生成代理类去继承UserService
UserService target;
public void test(){ //就可以重写test方法
//先执行@Before
//再执行被代理的逻辑
target.test();//所以代理类先调用子类test,接着调用切面逻辑,再而调用普通对象UserService的test方法(此时orderService是有值的)
}
}class userServiceProxy extends UserService(){ //生成代理类去继承UserService
UserService target;
public void test(){ //就可以重写test方法
//先执行@Before
//再执行被代理的逻辑
target.test();//所以代理类先调用子类test,接着调用切面逻辑,再而调用普通对象UserService的test方法(此时orderService是有值的)
}
}class userServiceProxy extends UserService(){ //生成代理类去继承UserService
UserService target;
public void test(){ //就可以重写test方法
//先执行@Before
//再执行被代理的逻辑
target.test();//所以代理类先调用子类test,接着调用切面逻辑,再而调用普通对象UserService的test方法(此时orderService是有值的)
}
}class userServiceProxy extends UserService(){ //生成代理类去继承UserService
UserService target;
public void test(){ //就可以重写test方法
//先执行@Before
//再执行被代理的逻辑
target.test();//所以代理类先调用子类test,接着调用切面逻辑,再而调用普通对象UserService的test方法(此时orderService是有值的)
}
}class userServiceProxy extends UserService(){ //生成代理类去继承UserService
UserService target;
public void test(){ //就可以重写test方法
//先执行@Before
//再执行被代理的逻辑
target.test();//所以代理类先调用子类test,接着调用切面逻辑,再而调用普通对象UserService的test方法(此时orderService是有值的)
}
}spring原理分析
主要了解spring全家桶最基本的原理、内容
Spring Framework
AOP
依赖注入
springboot是基于Spring Framework
@Component
public class OrderService{
}
@Component
public class UserService{
@Autowired
private OrderService orderService;
public void test(){
System.out.println("test");
}
}
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService)context.getBean("userService");
userService.test();
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService)context.getBean("userService");
userService.test();
以上两句都是学习spring时较为重要的两句话
第一句在springboot加载时被调用了 在spring3.0增加了该类 这个是以一个class的方式一个类的形式来充当一个配置文件 直接可以通过使用注解来定义扫描路径(同样也是创建一个spring容器)
第二句在使用springmvc时也调用了类似的PathXml了 作用是创建一个容器,传一个配置文件给spring容器(文件中可以定义一下扫描路径等操作) spring入门级时使用较多
然后可以通过上面的context来getBean获取bean对象,再执行方法
二者其实很相像,一个是以类class的形式,另一个是以xml配置的方式
这个我们可以测试
新增一个OrderService类这个类使用component注解标注为组件
并且在UserService中进行依赖注入(@Autowired)
肯定是有值的,因为OrderService是用@component注解标准,且在UserService中使用@Autowired依赖注入orderService
此时我们再测试一下自己new UserService对象出来,看看其中OrderService是否有值---没有值
这就是spring中的bean与Java中普通对象的区别
一个小小的区别用来理解这个bean
UserService.class--->构造方法---》对象(得到一个普通对象)
无论如何spring怎么操作,肯定是使用了UserService中的构造方法--》得到一个对象(可以发现UserService只有一个构造方法也就是无参构造方法---通过无参构造方法来获取对象),此时该对象中的orderService属性肯定是没有值的
这个时候就可以发现
UserService.class--->无参构造方法--->对象--->?--->bean
中间是什么操作使得对象中orderService有值(因为bean对象中的有值)
?==依赖注入(属性赋值)
spring会给加了@Autowired注解的属性赋值
依赖注入大概的工作原理(假设userService,其实任意对象均可以)
for(Field field:userService.getClass().getFields()){//获取userService对象中所有属性
if(field.isAnnotationPresent(Autowired.class)){//判断属性上是否携带了@Autowired注解
field.set(userService,????); //进行设置值
}
}
这时候还有一个问题是 值 应该从哪里来
初始化前
又可以得出新的路线
UserService.class--->无参构造方法--->对象--->依赖注入(属性赋值)--->初始化前--->初始化--->初始化后--->bean
引入一个小需求
定义了一个User对象(希望可以在mysql中保存数据-User对象-admin)
再UserService中定义了这个user属性admin
如果我们在User对象上加@Component注解以及在UserService的User.admin加@Autowired注解
那我们在userService对象中的admin是否有值,是否是我们需要的值呢
有值,但是不满足我们的需求不是我们需要的值,它此时的值就相当于是一个new User对象
因为我们没有做过任何对mysql进行操作的命令,spring根本不晓得到底连那个数据库等等,所以值是new User对象
因此我们应该编写一个方法,该方法内应该定义一系列对数据库的操作返回什么结果,赋值给我们的admin(这一系列操作应该在我们的初始化前这里进行操作)
UserService.class--->无参构造方法--->对象--->依赖注入(属性赋值)--->初始化前(方法调用)--->初始化--->初始化后--->bean
在UserService中有多个方法,如何让spring去调用你指定得方法呢?需要告诉spring在创建这个bean的过程中间去调用这个方法(@PostConstruct)
spring找的就是当前对象的哪些方法上面加了@PostConstruct注解
spring内部初始化前调用方法大概实现
for(Method method:userService.getClass().getMethods()){//获取到userService对象中的所有方法
if(method.isAnnotationPresent(PostConstruct.class)){//看看那个方法上有@PostConstruct注解
method.invoke(userService,参数);//执行那个方法并且传递什么参数
}
}
其实最开始spring只有初始化操作,没有初始化前与初始化后
以上初始化前的操作可以有另一种实现方式(不调用定义的方法不加@PostConstruct)
而是userService实现InitalizingBean并且实现其内部的afterPropertiesSet()方法
把以前定义的方法的内容全部放在这个afterPropertiesSet中
spring就会去调用这个afterPropertiesSet方法的内容
源码分析
//初始化
try{
invokeInitMethods(beanName,wrappedBean,mbd);
}catch(Throwable ex){
throw new BeanCreationException(
mbd!=null?mbd.getResourceDescription():null),
beanName,"Invocation of init method failed",ex);
}
进入invokeInitMethods方法查看会发现
protected void invokeInitMethods(String beanName,Object bean,@Notable RootBeanDefinition mbd )
这个Object bean其实就是我们spring根据某一个类创建的对象bean
执行bean instanceof InitalizingBean得到一个结果boolean initalizingBean
进行判断 这个bean是否为true实现了InitalizingBean接口么
为true就会判断是否打开安全管理器 再执行 ((InitalizingBean)bean).afterPropertiesSet()方法
为false就直接执行((InitalizingBean)bean).afterPropertiesSet()方法
初始化后
重点:实现了AOP
bean的创建生命周期
UserService.class--->无参构造方法--->对象(得到一个普通对象)--->依赖注入(属性赋值)--->初始化前(@PostConstruct)--->初始化(initializingBean)--->初始化后(AOP)--->代理对象--->bean
aop与spring事务联系紧密
定义了一个类
@Aspect
@Component
public class HHXYAspect{
@Before("execution(public void xxx.xxx.UserService.test())") //切的UserService里的test方法 UserService有一个切面
public void HHXYBefore(JoinPoint joinPoint){
System.out.println("hhxybefore")
}
}
开启AOP可以使用注解 @EnableAspectJAutoProxy
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService)context.getBean("userService");
userService.test();
在userService处打断点为什么userService中orderService没有值呢?为null
难道开启aop后依赖注入都不生效了么??? 并不是 其实我们如上面的步骤就可以发现了
UserService.class--->无参构造方法--->对象(得到一个普通对象)--->依赖注入(属性赋值)--->初始化前(@PostConstruct)--->初始化(initializingBean)--->初始化后(AOP)--->代理对象--->bean
spring进行初始化后(aop)--->得到代理对象--->bean 经过aop得到代理对象后续没有任何操作了直接变成bean了 设计时得到代理对象后续就没有依赖注入这种操作了
此时我们可以假设通过aop后得到代理对象中orderService属性就是空的
但是debug进入到UserService中的test方法就会发现orderService此时是有值的
这是为什么呢???
UserService此时进行aop应该使用的是什么 cglib
cglib是通过什么进行aop也就是生成代理对象----》通过代理,父子量继承
//以上spring流程中代理对象其实也就是通过代理类生成的代理对象
//UserServiceProxy--->代理对象
//cglib底层大概原理
class userServiceProxy extends UserService{ //生成代理类去继承UserService
public void test(){ //就可以重写test方法
}
}
再看回过来看这个方法。其实也就是拿到代理对象,接着调用代理对象中的test方法
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = context.getBean("userService");
userService.test();
进入到代理类的test方法
//为什么生成代理类因为有一个切面HHXYAspect
class userServiceProxy extends UserService{ //生成代理类去继承UserService
public void test(){ //就可以重写test方法
//先执行@Before
//再执行被代理的逻辑
super.test();//子类的test方法可以调用父类的test方法 也就是调用被代理的那个test方法
}
}
但是我们回过头来仔细思考 它这里只发生了一下方法调用 (子类的test调用,父类的方法调用,中间的切面方法) 那么我的UserService里的OrderService为什么会有值呢
思路是正确的,但是具体是怎么做的呢???
//UserServiceProxy--->代理对象--->代理对象.target==普通对象
//因为这个普通对象通过属性赋值(依赖注入),它的orderService是有值的
//其实这一步可以认为是生成代理对象的一步
class userServiceProxy extends UserService{ //生成代理类去继承UserService
UserService target;
public void test(){ //就可以重写test方法
//先执行@Before
//再执行被代理的逻辑
target.test();//所以代理类先调用子类test,接着调用切面逻辑,再而调用普通对象UserService的test方法(此时orderService是有值的)
}
}
为什么spring没有在代理对象那一步进行属性赋值呢----没太必要
通常切面都是用来切方法的 最终我会生成某一个代理对象
代理对象最重要的意义是啥 当这个代理对象去执行某个方法的时候 会来执行这些切面的逻辑
这时候在切面中为某一个属性进行赋值,用处不大
切面中最重要的还是那个方法 而且joinPoint.getTarget()一样可以获取到普通对象
为啥 代理对象.target==普通对象
创建bean的流程
查看方法doCreateBean
Object bean = instanceWrapper.getWrappedInstance() //通过构造方法得到一个对象
Object exposedObject =bean;
//填充属性
populateBean(beanName,mbd,instanceWrapper)
//初始化
exposedObject = initializeBean(beanName,exposedObject,mbd)
进入到initializeBean方法
会将刚刚那个bean对象传递进来
会将对象传到
wrappedBean = applyBeanProcessorAfterInitialization(wrappedBean,beanName) //这个方法就会进行aop了
这个方法就可以看作生成一个代理类,代理类实例化成代理对象,再把这个wrappedBean赋值给这个代理对象就OK了
UserService.class--->无参构造方法--->对象(得到一个普通对象)--->依赖注入(属性赋值)--->初始化前(@PostConstruct)--->初始化(initializingBean)--->初始化后(AOP)--->代理对象--->bean
这个步骤中要如何判断UserService要aop呢 因为UserService中的一个方法test被切面切了所以要aop
那么我UserService从最开始bean流程走的时候怎么知道自己要不要进行aop,被切了呢?
切面也加了@Aspect**@Component**注解 也在spring容器中 切面bean 一个方法不一定是被一个切面bean来切,并不是只有一个切入点
1,找到所有的切面Bean(找到@Aspect,@Component)
2,遍历所有切面Bean
3,遍历切面Bean中所有切面方法,找方法上是否标准了@Before、@After等注解的方法
4,有类似这种注解的话,查看该注解中是否定义了的表达式
5,拿着这些定义了的表达式去与当前spring正在创建的UserService去匹配
6,当前所遍历的方法与UserService匹配,那么就说明我这个UserService是需要进行aop的
7,就会将UserService匹配的方法 全部放到map
代理对象.test();
class userServiceProxy extends UserService{ //生成代理类去继承UserService
UserService target;
public void test(){ //就可以重写test方法
//先执行@Before
//再执行被代理的逻辑
target.test();//所以代理类先调用子类test,接着调用切面逻辑,再而调用普通对象UserService的test方法(此时orderService是有值的)
}
}
方法存储起来的map,可以在@Before这里执行
可以知道此时这个代理对象就是UserService 代理对象.test()方法 首先执行切面的逻辑 也就是上面的@Before需要执行业务逻辑
执行切面逻辑可以直接通过UserService中map缓存里的方法全部都匹配出来
这样代理对象.test()可以执行的更快了
所以这个流程可以有两个方面好处:
1,一方面是看我这个UserService要不要进行aop 要进行aop就可以把这些方法都缓存起来
2,而当我真正要执行时(代理对象.test()时) ,就可以从缓存中把所有的UserService匹配的方法全部拿出来,执行切面的逻辑
接着了解下spring事务
加上注解@EnableTransactionManagement 开启事务管理
//AppConfig
//都是一些需要操作数据库使用的
@Component
@EnableTransactionManagement
public class AppConfig{
@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("xxx连接信息");
dataSource.setUsername("用户名");
dataSource.setPassword("密码");
return dataSource;
}
}
有一个表 giao 五个字段都是空的
在UserService中改造
//UserService.class
@Component
public class UserService{
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
public void test(){
jdbcTemplate.execute("insert giao values(1,1,1,1,1)");
throw new NullPointerException();
}
}
//Test.class
public class Test {
public static void main(String []args){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService)context.getBean("userService");
userService.test();
}
}
这个时候猜测数据库中是否插入数据 后台报NullPointerException
但是数据库中已经存在数据了
为什么会添加到数据库呢(没有在AppConfig类上添加@Configuration注解)
加了该注解后再执行一遍Test.class的mian方法
发现抛出NullPointerException异常,且数据库中没有数据
我们再userService.test()这里打断点 会发现这是一个代理对象 是由spring事务创建的一个代理对象那么看看spring事务的代理对象.test
class userServiceProxy extends UserService{ //生成代理类去继承UserService
UserService target;
public void test(){ //就可以重写test方法
//代理逻辑是怎么样的呢
//1,判断当前代理对象所执行方法上是否有@Transactional注解
//2,创建一个数据库连接Conn(事务管理器dataSource)
//3,conn.autocommit = true 默认是true 自动提交 spring将conn.autocommit = false 改成false
可以执行方法完毕后,进行手动提交conn.commit();
若是执行方法抛出异常,就可以调用conn.rollback();
target.test();//这个时候就是调用普通方法的test(),也就是加了UserService中加了@Transactional注解的方法
假设该方法中有几条需要执行的sql语句,有conn.autocommit = true 就会执行一条sql就会提交一次 那么此时我们的@Transactional注解还有什么意义呢 sql都提交了后面的sql执行在提交,后面异常也要抛出,那么这个回滚还回滚什么呢
}
}
事务失效的场景了解
//改造UserService.class
//UserService.class
@Component
public class UserService{
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
public void test(){
jdbcTemplate.execute("insert giao values(1,1,1,1,1)");
a();
}
@Transactional(propagation=Propagation.NEVER)//当传播机制-NEVER 以非事务的方式执行,存在一个事务就会抛一个异常
public void a(){
}
}
按照理论是会抛出一个异常,因为已经test方法开启了一个事务 但是运行测试会发现并没有抛异常 为什么呢,我们查看a()是谁在调用 可以发现代理对象.test()不就是执行代理的test(),再执行普通对象UserService调用test() 在普通对象上加@Transactional是没有作用的
该如何解决这种事务失效的场景
要明确加了 @Transactional(propagation=Propagation.NEVER)注解该注解有没有用
是谁在调用 被调用的时候是不是代理对象在调用这个方法
只要是代理对象调用这个方法,那么上面写的这个注解它就有用
方案可以重新创建一个UserServiceBase类
就把a()方法写在里面
再从UserService中使用@Autowired注解依赖注入UserServiceBase()
再在test方法下把a()改成 userServiceBase.a();
运行测试可以发现确实抛出异常了 ----IllegalTransactionStateException
其实也可以不用拆分出UserServiceBase类
直接在UserService类中使用@Autowired注解自己依赖注入自己在UserService中
@Autowired
private UserService userService;
//spring会将UserService的代理对象赋值给userService这个属性
//也就是userService.a()就是一个代理对象调用a()方法
//就会出现上述异常
为什么没加@Configuration注解事务失效,加了之后事务成功呢
没有加@Configuration可以发现代理类中的dataSource与代理对象.test()的dataSource不是同一个
没有加@Configuration时,AppConfig.class中JdbcTemplate与DataSourceManager持有的是两个dataSource,两个单独的dataSource,不是同一个
在代理对象.test()时
我是拿着事务管理器的dataSource去创建数据库连接,修改autocommit属性
而target.test()执行时,是由jdbcTemplate的dataSource去建立一个新的数据库连接 所以它执行的SQL就自动提交了
加了@Configuration注解时,可以确保jdbcTemplate与DataSourceManger的dataSource是同一个 这样事务才能生效
为什么这里添加了@Configuration注解就能确保dataSource保持一致呢
与spring中的代理模式有关
我们要使用jdbcTemplate与DataSourceManger都是要使用AppConfig这个类的
如果我们加上了@Configuration注解就可以使得jdbcTemplate与DataSourceManger使用的是AppConfig的代理对象
@Component
@EnableTransactionManagement
@Configuration//保证datasource一致事务才会生效
public class AppConfig{//AppConfig代理对象
@Bean
public JdbcTemplate jdbcTemplate(){
return new JdbcTemplate(dataSource());//此时dataSource()直接到spring容器中寻找,有的话就直接返回了
//如果没有找到dataSource这个bean那就要真正执行下面的DataSource方法创建dataSource
}
@Bean
public PlatformTransactionManager transactionManager(){//配置事务管理器
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource());此时dataSource()直接到spring容器中寻找,有的话就直接返回了
//如果没有找到dataSource这个bean那就要真正执行下面的DataSource方法创建dataSource
return transactionManager;
}
@Bean
public DataSource dataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl("xxx连接信息");
dataSource.setUsername("用户名");
dataSource.setPassword("密码");
return dataSource;
}
}