手写Spring简易版源码

本文是纪录哔哩哔哩图灵-周瑜老师的手写Spring源码课程,仅供纪录


零、项目目录

├─src
│  ├─main
│  │  ├─java
│  │  │  └─com
│  │  │      └─springCode
│  │  │          ├─service
│  │  │          └─spring
│  │  └─resources
│  └─test
│      └─java
└─target
    ├─classes
    │  └─com
    │      └─springCode
    │          ├─service
    │          └─spring
    └─generated-sources
        └─annotations

一、Spring回顾

对比一下三种创建Spring容器的方式,

  • AnnotationConfigApplicationContext 是需要传入一个xml文件,告诉Spring需要按照指定的配置创建一个Spring容器classPathXmlApplicationContext,创建完之后就可以通过getBean从容器中拿到相应的bean对象
  • SpringCodeApplicationContext也是一个容器,只是传入的配置文件是以Java类的形式传入。可以在类上加上@ComponentScan定义Spring等下需要扫描的路径,还可以通过@Bean的方法(等于bean标签),通过这种注解的方式,给容器加入一个Bean
  • SpringCodeApplicationContext则是手写模拟SpringCodeApplicationContext进行创建Ioc容器并注入Bean
public class Test {
	public static void main(String[] args) {
		ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring.xml");
		classPathXmlApplicationContext.getBean("user");

		AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
		
		SpringCodeApplicationContext applicationContext = new SpringCodeApplicationContext(AppConfig.class);
	}
}

二、手写Spring准备工作(Ioc)

1.新建容器类

参考Spring框架可知,这个容器类需要有构造方法、传入参数、getBean()方法

// com/springCode/spring/SpringCodeApplicationContext.java
public class SpringCodeApplicationContext {

    private Class configClass;

	public SpringCodeApplicationContext(Class configClass) {
    	this.configClass = configClass;

		// 解析配置类
		// ComponentScan注解-->扫描路径-->扫描
    }

    public Object getBean(String beanName){
		return null;
	}

2.新建主函数入口

// com/springCode/service/Test.java
public class Test {
	public static void main(Stringp[] args) {
        // Spring容器
        SpringCodeApplicationContext applicationContext = new SpringCodeApplicationContext(AppConfig.class);	
	}
}

3.新建@ComponentScan注解

// com/springCode/spring/ComponentScan.java
// 注解生效的时间
@Retention(RetentionPolicy.RUNTIME)
// 表示只能在类上
@Target(ElementType.TYPE)
public @interface ComponentScan {

    String value() default "";
}

4.新建配置文件

// com/springCode/service/UserService.java
@ComponentScan("com.springCode.service")
public class AppConfig {
}

5. 新建@Component注解

// com/springCode/spring/Component.java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {

    String value() default "";
}

6.创建用户类

三、完善SpringCodeApplicationContext(Ioc)

  • 针对SpringCodeApplicationContext需要在其有参构造方法中实现扫描注解并注册Bean

1.获取反射信息

  • 反射回顾:

    • 如何获取类上的注解信息? 比如本项目中
    // 拿到注解
    ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
    // 拿到注解里面的value(扫描路径)
    String path = componentScanAnnotation.value();
    
  • Spring在拿到扫描路径后,关心的是该路径下的类有没有@Component这个注解

  • 那扫描获取到路径后,下一步应该做什么?(借助类加载器)

    因为Java需要编译成.class文件,得到class对象才能利用Java提供api去调用查看这个类上是否有对应的注解

2.获取类路径信息

	扫描路径
	Bootstrap-->jre/lib
	Ext-->jre/ext/lib
	App-->classpath-->
  • 什么是classpath?
    • 查看调用的Java命令可知,可以看到classpath实际上是当前该应用对应的target路径
    D:\JAVA_Environment\jdk-8\bin\java.exe "-javaagent:D:\Idea\IntelliJ IDEA 2020.3.2\lib\idea_rt.jar=62539:D:\Idea\IntelliJ IDEA 2020.3.2\bin" -Dfile.encoding=UTF-8 -classpath D:\AJavaSpring\springCode\target\classes com.springCode.service.Test
    

因此,如果要获取对应文件,实际上是获取.class文件,其中需要两个重要信息。classpath和类信息,将两个信息拼接就能得到编译好的.class文件

尝试获取,注意这里的获取的路径是相对路径,实际上是classpath的相对路径

ClassLoader classLoader = SpringCodeApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource("com/springCode/service")

我们都知道,这个URL实际上是一个目录。我们可以先判断它是否是一个目录,如果是一个目录的话就遍历这个目录并输出(名字)。

// File既可以表示为一个文件也可以表示为一个目录
File file = new File(resource.getFile());
if (file.isDirectory()) {
    File[] files = file.listFiles();
    for (File f : files) {
    	System.out.println(f);
    }
}

所以我们已经有了类的具体路径,那么我们可以利用类加载器去加载类,获取.class对象,有了这个.class对象就能去判断当前类是否有指定的@Component注解

File file = new File(resource.getFile());
if (file.isDirectory()) {
    File[] files = file.listFiles();
    for (File f : files) {
    	System.out.println(f);
    	Class<?> aClass = classLoader.loadClass("..");
    	if(aClass.isAnnotationPresent(Component.class)){
    		...
    	}
    }
}

这里的Class<?> aClass = classLoader.loadClass("..");写的是加载的类的路径,但是这个路径是否能直接拼接?实际上是不行的,因为需要的是com.springCode.service.UserService(字符串),而不是绝对路径D:\XXX\target\classes\com\springCode\service\UserService.class
所以我们需要对这个字符串进行转换
D:\XXX\target\classes\com\springCode\service\UserService.class—>com.springCode.service.UserService

for (File f : files) {
	String fileName = f.getAbsolutePath();
    // fileName是D:\XXXX\XXX\XXX.class,因此要截取
    String className = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));
    // xxx\\xxx ---> xxx.xxx
    className = className.replace("\\", ".");
}

当前存在一个问题是,将包下的所有文件都扫描出来并转换,实际上我们只需要.class的文件,因此要加一个判断。并抛出异常,调整一下代码的位置。

for (File f : files) {
    // 获取文件的绝对路径
    String fileName = f.getAbsolutePath();

    if (fileName.endsWith(".class")) {
        // fileName是D:\XXXX\XXX\XXX.class,因此要截取
        String className = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));
        // xxx\\xxx ---> xxx.xxx
        className = className.replace("\\", ".");
        try {
            // 利用类加载器获取对应的类(反射)
            Class<?> clazz = classLoader.loadClass(className);
            // 再通过得到的类判断有没有对应的注解
            if (clazz.isAnnotationPresent(Component.class)) {
            	// 表示当前这个类是一个bean
				...
			} catch (ClassNotFoundException e) {
	            e.printStackTrace();
            } 
		}
	}
}

3.Bean的作用域

类上有一个@Component的注解,那么就代表这个类应该被Ioc容器所管理。那么是否有这个注解就需要生成bean对象呢?
实际上不一定,是否创建bean对象与bean的作用域相关。

@Scope注解

创建@Scope注解用于标识是一个单例bean还是一个原型bean,针对不同作用域的bean执行不一样的初始化步骤。
其中

  • 单例bean—>无论获取几次都是同一个bean对象
    • 单例池:Spring的思想是在底层或容器内部创建一个Map集合,Map <beanName, bean对象>
  • 原型bean—>每次获取都是一个新的bean对象
src/main/java/com/springCode/spring/Scope.java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {
    String value() default "";
}

单例池

模仿Spring创建一个单例池

public class SpringCodeApplicationContext {

    private Class configClass;
    // 用于存单例bean
    private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();

	...

}

那么什么时候才向单例池里注入对象呢?回到刚刚扫描到@Component的位置
我们需要去判断这个带@Component的是单例bean还是prototype的bean
这里不进行考虑懒加载
如果扫描到是prototype的bean,要做什么呢?

BeanDefinition

目前遇到一个问题是,怎么根据一个名字去找到对应的类。同时如果是一个prototype的bean,由于单例池里面是没有这个bean的,那么是否需要每次获取时都要去查看一下这个bean的定义(即去查看是单例bean还是多例bean)
因此需要引入BeanDefinition,标识bean的定义
解析类—>BeanDefinition

public class BeanDefinition {

    public Class getType() {
        return type;
    }

    public void setType(Class type) {
        this.type = type;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }

    private Class type;
    private String scope;
}

现在扫描到类上有一个@Component这个注解。我们先拿到这个注解并解析获取里面的属性。然后再查看这个类上是否有@Scope这个注解,有的话查看这个注解是标识这个类是单例还是原型。并将相关信息报错在BeanDefinition中,若没有则默认为单例。

if (clazz.isAnnotationPresent(Component.class)) {
	Component component = clazz.getAnnotation(Component.class);
	// 解析获取里面的属性
	String beanName = component.value();

	BeanDefinition beanDefinition = new BeanDefinition();

	if(clazz.isAnnotationPresent(Scope.class)){
		Scope scopeAnnotation = clazz.getDeclaredAnnotation(Scope.class);
		beanDefinition.setScope(scopeAnnotation.value());
	} else {
		beanDefinition.setScope("singleton");
		}
	}

BeanDefinitionMap

但实际上,更好的做法是设置一个Map用来存这个BeanDefinition

// 用于纪录BeanPostProcessor
private ArrayList<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

再将这个beanDefinition放在Map中

    BeanDefinition beanDefinition = new BeanDefinition();
    beanDefinition.setType(clazz);
    // 看看有没有Scope注解
    if (clazz.isAnnotationPresent(Scope.class)) {
        Scope annotation = clazz.getAnnotation(Scope.class);
        beanDefinition.setScope(annotation.value());
    } else {
        // 没有Scope的话默认就是单例的
        beanDefinition.setScope("singleton");
    }
    beanDefinitionMap.put(beanName, beanDefinition);

此时对于getBean就可以从这个Map中获取。此时遇到一个新的问题,如果获取不了,那么怎么去创建新的对象。

public Object getBean(String beanName){
		if(beanDefinitionMap.containsKey(beanName)){
			BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
			if(beanDefinition.getScope().equals("singleton")){
				Object o = singletonObjects.get(beanNames);
				return o;
			} else {
				...
			}
		else {
			throw new NullPointerException();
		}
	}
}

createBean

写一个创建bean的方法,根据获取到的BeanDefinition 信息,调用无参构造的反射方法进行新建。

    private Object createBean(BeanDefinition beanDefinition){
        Class clazz = beanDefinition.getType();
        try {
            // getConstructor()是调用无参构造
            Object instance = clazz.getConstructor().newInstance();
            return instance;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }

有了这个createBean方法,就可以在getBean获取不到时进行创建

public Object getBean(String beanName){
		if(beanDefinitionMap.containsKey(beanName)){
			BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
			if(beanDefinition.getScope().equals("singleton")){
				Object o = singletonObjects.get(beanNames);
				return o;
			} else {
				Object bean = createBean(beanDefinition);
				return bean;			}
		else {
			throw new NullPointerException();
		}
	}
}

回到上面的ApplicationContext中,可以总结一下的步骤
解析配置类
ComponentScan注解–>扫描路径–>扫描类的信息–>装载到BeanDefinition–>BeanDefinitionMap
此时所有的类都被扫描完毕,同时被装配到BeanDefinitionMap中,下一步就是进行创建bean

扫描
装配
prototype
singleton
没有
根据@ComponentScan获取扫描路径
扫描带@Bean和@Scope信息的类
将扫描到的信息装配到beanDefinition中并封装为beanDefinitionMap
根据beanDefinitionMap信息进行创建bean
创建一个新的
createBean
先查看是否在单例池中有
取出
新建
      // 实例化单例bean
      // 扫描完,就需要把单例bean创建出来
      for (String beanName : beanDefinitionMap.keySet()) {
          BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
          if (beanDefinition.getScope().equals("singleton")) {
              // 单例就创建bean,但是要怎样保证单例呢?
              Object bean = createBean(beanName, beanDefinition);
              singletonObjects.put(beanName, bean);
          }
      }

4.自动注入

目前,无参构造都已经做好了,但是有一个问题是,如果这个类里面需要注入其他的类,那该怎么做呢?

创建@Autowired

@Retention(RetentionPolicy.RUNTIME)
// 写在字段上
@Target(ElementType.FIELD)
public @interface Autowired {
    
}

在属性中使用@Autowired

@Component("userService")
public class UserService {
    @Autowired
    private OrderService orderService;
}

修改createBean

很明显,属性注入是在创建bean的时候进行处理的,因此回到createBean
需要先把类上的所有属性都拿到,然后一个个进行判断查看是否需要属性的自动注入
我们需要在单例池中找到相应的bean进行注入

    private Object createBean(String beanName, BeanDefinition beanDefinition){
        Class clazz = beanDefinition.getType();
        try {
            // getConstructor()是调用无参构造
            Object instance = clazz.getConstructor().newInstance();
            // 对加了@Autowired的属性赋值
            // 依赖注入
            for (Field f : clazz.getDeclaredFields()) {
                if (f.isAnnotationPresent(Autowired.class)) {
                    // 调用反射赋值,需要先暴力破解
                    f.setAccessible(true);
                    f.set(instance, getBean(f.getName()));
                }
            }
        }

5.Aware回调

此时有一个新的需求,我们需要Spring为我们传递beanName到我们的类中

@Component("userService")
public class UserService{

    @Autowired
    private OrderService orderService;

    private String beanName;
}

查看Spring源码,可以看到有一个BeanNameAware的接口去实现这个需求,因此我们可以模仿Spring

创建BeanNameAware

public interface BeanNameAware {

    /**
     * setBeanName  bean初始化后回调,即由Spring通知类(监听者模式)
     *
     * @param beanName
     *
     */
    public void setBeanName(String beanName);
}

实现BeanNameAware

@Component("userService")
public class UserService implements BeanNameAware {

    @Autowired
    private OrderService orderService;

    private String beanName;

    @Override
    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }

}

实现回调功能

很明显,这个功能的实现也是在createBean中
因此我们在实现完自动注入后,去判断当前的实例是否实现了BeanNameAware接口,若实现了,就把实例强制转化为BeanNameAware类,并实现回调功能。
由于这里没有beanName,因此修改createBean方法,传入beanName

    private Object createBean(String beanName, BeanDefinition beanDefinition){
    	...
    	
            for (Field f : clazz.getDeclaredFields()) {
                if (f.isAnnotationPresent(Autowired.class)) {
                    // 调用反射赋值,需要先暴力破解
                    f.setAccessible(true);
                    f.set(instance, getBean(f.getName()));
                }
            }
            // 判断是否实现了BeanNameAware接口
            // Aware回调
            if (instance instanceof BeanNameAware) {
                // 相当于间接通知你beanName
                ((BeanNameAware) instance).setBeanName(beanName);
            }
         ...
     }    

6. 初始化机制

Spring中提供了一个初始化接口initializingBean,因此我们仿照Spring

新建initializingBean接口

public interface InitializingBean {
    /**
     * afterPropertiesSet 初始化机制,直接初始化调用
     */
    public void afterPropertiesSet();
}

实现initializingBean接口

Spring在创建UserService的过程中,会调用afterPropertiesSet()方法。可以验证当前对象的某些属性是否符合要求等功能。

@Component("userService")
public class UserService implements BeanNameAware, InitializingBean {

    @Autowired
    private OrderService orderService;

    private String beanName;

    @Override
    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }

    @Override
    public void afterPropertiesSet() {
        System.out.println("hahah");
    }
}

实现initializingBean功能

很明显,这个功能的实现也是在createBean中
因此我们在实现完自动注入后,去判断当前的实例是否实现了initializingBean接口,若实现了,就把实例强制转化为initializingBean类,并实现回调功能。

    private Object createBean(String beanName, BeanDefinition beanDefinition){
    	...
    	
            for (Field f : clazz.getDeclaredFields()) {
                if (f.isAnnotationPresent(Autowired.class)) {
                    // 调用反射赋值,需要先暴力破解
                    f.setAccessible(true);
                    f.set(instance, getBean(f.getName()));
                }
            }
            // 判断是否实现了BeanNameAware接口
            // Aware回调
            if (instance instanceof BeanNameAware) {
                // 相当于间接通知你beanName
                ((BeanNameAware) instance).setBeanName(beanName);
            }
            // 初始化即直接调用方法
            if (instance instanceof InitializingBean) {
                ((InitializingBean) instance).afterPropertiesSet();
            }
         ...
     }    

7. BeanPostProcessorBean后置处理器

需求:在初始化之前之后可以操作某些事情,注意BeanNameAwareinitializingBean都是初始化之后Object instance = clazz.getConstructor().newInstance();做的事情。
我们需要在Object instance = clazz.getConstructor().newInstance();前后做一些操作。
Spring提供了一个BeanPostProcessor接口满足这个需求。
Spring底层用到大量的BeanPostProcessor,我们模仿Spring创建一个BeanPostProcessor接口

新建BeanPostProcessor接口

public interface BeanPostProcessor {

    public void postProcessBeforeInitialization(String beanName, Object bean);

    public void postProcessAfterInitialization(String beanName, Object bean);
}

使用(实现)BeanPostProcessor 接口

可以针对所有的bean,这里针对某一个bean(UserService)进行处理
【注意】:这里一定要加入@Component注解,让Spring找到这里的操作

@Component
public class SpringCodePostProcessor implements BeanPostProcessor {
    @Override
    public void postProcessBeforeInitialization(String beanName, Object bean) {
        if (beanName.equals("userService")) {
            System.out.println("before");
        }
    }

    @Override
    public void postProcessAfterInitialization(String beanName, Object bean) {
        if (beanName.equals("userService")) {
            System.out.println("after");
        }
    }
}

实现BeanPostProcessor 功能

回到SpringCodeApplicationContext
【注意】:这里不能用instanceof,因为是针对对象去判断是否是某个类型的。因为此时还没创建bean实例
BeanPostProcessor.class.isAssignableFrom(clazz)用于判断当前的类是否实现了BeanPostProcessor接口

try {
    // 利用类加载器获取对应的类(反射)
    Class<?> clazz = classLoader.loadClass(className);
    // 再通过得到的类判断有没有对应的注解
    if (clazz.isAnnotationPresent(Component.class)) {
        // 额外判断当前这个类是否实现BeanPostProcessor,有则提出来单独处理
        // 这里不能用instanceof,因为是针对对象去判断是否是某个类型的
        // 得到对象并加入List中
        if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
            BeanPostProcessor instance = (BeanPostProcessor) clazz.newInstance();
            beanPostProcessorList.add(instance);
        }

同时,针对不同的Bean实例可能会出现不同的BeanPostProcessor,因此会有很多的BeanPostPeocessor,可以设置一个列表进行保存

private ArrayList<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

BeanPostProcessor进行保存

 try {
     // 利用类加载器获取对应的类(反射)
     Class<?> clazz = classLoader.loadClass(className);
     // 再通过得到的类判断有没有对应的注解
     if (clazz.isAnnotationPresent(Component.class)) {
         // 额外判断当前这个类是否实现BeanPostProcessor,有则提出来单独处理
         // 这里不能用instanceof,因为是针对对象去判断是否是某个类型的
         // 得到对象并加入List中
         if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
             BeanPostProcessor instance = (BeanPostProcessor) clazz.newInstance();
             beanPostProcessorList.add(instance);
         }
         ...
	 }
 }     

扫描完之后会真正创建bean对象,因此在回调之后和创建之前进行调用BeanPostProcessorpostProcessBeforeInitialization方法。初始化之后调用BeanPostProcessorpostProcessAfterInitialization方法。

            // 判断是否实现了BeanNameAware接口
            // Aware回调
            if (instance instanceof BeanNameAware) {
                // 相当于间接通知你beanName
                ((BeanNameAware) instance).setBeanName(beanName);
            }

            // 调用所有方法,创造bean前
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                beanPostProcessor.postProcessBeforeInitialization(beanName, instance);
            }

            // 初始化即直接调用方法
            if (instance instanceof InitializingBean) {
                ((InitializingBean) instance).afterPropertiesSet();
            }
            
            // 调用所有方法,创造bean后
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                beanPostProcessor.postProcessAfterInitialization(beanName, instance);
            }

四、AOP

修改BeanPostProcessor

Aop产生的是代理对象,对Spring中的对象进行切面增强操作后。我们拿到的对象就是代理对象,即加强后的对象。我们在Ioc中创建的对象实际上是原始的Bean对象。

这里的instance是原始的Bean对象

// 调用所有方法,创造bean后
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
	beanPostProcessor.postProcessAfterInitialization(beanName, instance);
}

// BeanPostProcessor 初始化后 AOP
return instance;

我们可以在postProcessBeforeInitialization方法和postProcessBeforeInitialization方法加点操作。,此时就要修改为返回Object对象

public interface BeanPostProcessor {

    public Object postProcessBeforeInitialization(String beanName, Object bean);

    public Object postProcessAfterInitialization(String beanName, Object bean);
}
@Component
public class SpringCodePostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(String beanName, Object bean) {
        if (beanName.equals("userService")) {
            System.out.println("before");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(String beanName, Object bean) {
        if (beanName.equals("userService")) {
            System.out.println("after");
        }
        return bean;
    }
}

新建UserInterface

甚至可以自己创建一个新的对象,这里使用JDK的动态代理。新建一个接口满足JDK动态代理需要实现接口的要求

public interface UserInterface {
    void test();
}

@Component("userService")
public class UserService implements BeanNameAware, InitializingBean, UserInterface {

    @Autowired
    private OrderService orderService;

    private String beanName;

    @Override
    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }

    @Override
    public void afterPropertiesSet() {
        System.out.println("hahah");
    }

    @Override
    public void test() {
        System.out.println(orderService);
    }
}

使用动态代理

Proxy.newProxyInstance有三个参数,第一个是获取类加载器,第二个是获取所实现的接口,第三个是生成代理对象

    @Override
    public Object postProcessAfterInitialization(String beanName, Object bean) {
        if (beanName.equals("userService")) {
            Object instance = Proxy.newProxyInstance(SpringCodePostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("切面逻辑");
                    return method.invoke(bean, args);
                }
            });
            return instance;
        }
        return bean;
    }

相应的这里createBean也要改。因此此时获得的是代理对象

            // 调用所有方法,创造bean后
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                instance = beanPostProcessor.postProcessAfterInitialization(beanName, instance);
            }

            // BeanPostProcessor 初始化后 AOP
            return instance;

测试,发现报错。这是是JDK动态代理的问题。
由于在容器中得到的对象是代理对象UserService bean = (UserService) applicationContext.getBean("userService");,代理对象的类型是UserInterface,而不是UserService

before
Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy4 cannot be cast to com.springCode.service.UserService
	at com.springCode.service.Test.main(Test.java:14)

因此修改为UserInterface

UserInterface bean = (UserInterface) applicationContext.getBean("userService");

结果正确
知识回顾:动态代理,就是要求被代理的类需要实现接口。原理就是生成这个接口的子类,这个子类代理实际的对象。

before
切面逻辑
com.springCode.service.OrderService@63947c6b
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值