我们平时都是使用这两种方法获得spring容器,上面的是通过加载类路径上的配置文件来获得容器。下面的方式和上面的原理相同只不过是通过注解的形式去实现,我们传入的也是一个配置类的class文件,我们可以把这个文件类比成第一种方法中的xml文件,然后这个xml文件里的一个个标签都变成了注解。
我们的测试类:
public class MySpringTest {
public static void main(String[] args) {
MyApplicationContext applicationContext = new MyApplicationContext(AppConfig.class);
Object bean = applicationContext.getBean("");
System.out.println(bean);
}
}
我们的容器类MyApplicationContext :
public class MyApplicationContext {
private Class configClass;
public MyApplicationContext(Class configClass) {
this.configClass = configClass;
}
public Object getBean(String beanName){
return null;
}
}
我们原来在编写Spring的配置文件的时候会使用一个注解@ComponentScan,来定义一个扫描路径。所以我们这里也定义了一个。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface ComponentScan {
String value();
}
Retention指定该注解的生命周期,设置成RUNTIME便于反射获取
Target指定注解的使用位置,设置为TYPE表示能在类上使用
我们的AppConfig;
@ComponentScan("com.zyb.service")
public class AppConfig {
}
根据扫描包我们再创建一个业务层类UseService,这个UseService我们一般会使用@Component注解进行标记,这里我们也是如此:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
String value();
}
@Component("userService")
public class UserService {
}
我们的思路就是;
首先我们可以通过如下代码拿到AppConfig中ComponentScan的内容:
public MyApplicationContext(Class configClass) {
this.configClass = configClass;
//解析配置类
//ComponentScan注解 --》 扫描路径 --》 扫描
ComponentScan componentScanAnnotation = (ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
String path = componentScanAnnotation.value();
System.out.println(path);
}
我们测试一下:
拿到路径之后我们就可以开始扫描了。而扫描的目的并不是包下的所有类,而是那些带有@Component注解的类,而Spring会将他们的对象当作Spring中的一个bean。
这里我们扫描的思路如下:
\
为.
代码如下:
public MyApplicationContext(Class configClass) {
this.configClass = configClass;
//解析配置类
//ComponentScan注解 --》 扫描路径 --》 扫描
ComponentScan componentScanAnnotation = (ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
String path = componentScanAnnotation.value();
String searchPath = path.replace(".", "/");
ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(searchPath);
File file = new File(resource.getFile());
if (file.isDirectory()) {
//首先筛选末尾是.class文件的
File[] files = file.listFiles();
for (File classFile: files) {
String absolutePath = classFile.getAbsolutePath();
if (absolutePath.endsWith(".class")) {
//拼接全限定名
String fullyQualifiedClassName = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
fullyQualifiedClassName = fullyQualifiedClassName.replace("\\", ".");
//使用app类加载器加载这个类
Class<?> aClass = null;
try {
aClass = classLoader.loadClass(fullyQualifiedClassName);
if (aClass.isAnnotationPresent(Component.class)) {
//然后加载bean
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
注意;
当我们确认当前类有@Component注解的时候并不是急着去给其创建bean,我们在使用spring的时候是可以决定该bean是否为单例的。我们在这里还是创建一个同名注解@Scope:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {
String value() default "single";
}
默认是single单例的,如果是原型bean则传入prototype。
而我们实现单例bean是通过一个Map单例池,不考虑懒加载。
现在我们的大致思路就是在通过@Scope注解确定bean是单例模式还是原型模式之后,在进行相应的bean的创建,但是这里我们考虑到一个问题,我们在getBean的时候如果每次都去解析这个类获得@Scope是很麻烦的。所以我们在这里引入一个新的概念BeanDefinition。
BeanDefinition中包含了当前bean的所有定义,例如bean的类型、作用域、是否懒加载等等。注意bean的name不在BeanDefinition中:
public class BeanDefinition {
private Class beanClass;
private String scope;
public Class getBeanClass() {
return beanClass;
}
public void setBeanClass(Class beanClass) {
this.beanClass = beanClass;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
}
在Spring中每扫描到一个bean都会对其进行解析然后生成各自的BeanDefinition实例,随后将这个BeanDefinition对象放在map中,key为bean的name,BeanDefinition对象作为value。以后当我们要获取一个bean时先会去map中查看,如果查找不到bean的BeanDefinition才会去解析类,以此来减少解析类的次数提高效率。
接下来我们完善一下代码,思路如下:
getBean方法思路:
代码如下:
public class MyApplicationContext {
private Class configClass;
private ConcurrentHashMap<String,Object> singleBeanHashMap = new ConcurrentHashMap<>();
private HashMap<String,BeanDefinition> beanDefinitionHashMap = new HashMap<>();
public MyApplicationContext(Class configClass) {
this.configClass = configClass;
//解析配置类
//ComponentScan注解 --》 扫描路径 --》 扫描
ComponentScan componentScanAnnotation = (ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
String path = componentScanAnnotation.value();
String searchPath = path.replace(".", "/");
ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(searchPath);
File file = new File(resource.getFile());
if (file.isDirectory()) {
//首先筛选末尾是.class文件的
File[] files = file.listFiles();
for (File classFile: files) {
String absolutePath = classFile.getAbsolutePath();
if (absolutePath.endsWith(".class")) {
//拼接全限定名
System.out.println(classFile);
String fullyQualifiedClassName = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
fullyQualifiedClassName = fullyQualifiedClassName.replace("\\", ".");
//使用app类加载器加载这个类
Class<?> aClass = null;
try {
aClass = classLoader.loadClass(fullyQualifiedClassName);
if (aClass.isAnnotationPresent(Component.class)) {
BeanDefinition beanDefinition = new BeanDefinition();
if (aClass.isAnnotationPresent(Scope.class)) {
Scope scopeAnnotation = aClass.getAnnotation(Scope.class);
String scope = scopeAnnotation.value();
beanDefinition.setScope(scope);
}else{
beanDefinition.setScope("single");
}
Component componentAnnotation = aClass.getDeclaredAnnotation(Component.class);
String beanName = componentAnnotation.value();
beanDefinitionHashMap.put(beanName,beanDefinition);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
public Object getBean(String beanName){
//首先判断是否存在该bean的BeanDefinition
if (beanDefinitionHashMap.containsKey(beanName)) {
BeanDefinition beanDefinition = beanDefinitionHashMap.get(beanName);
if (beanDefinition.getScope().equals("single")) {
//单例模式创建bean
}else {
//原型模式创建bean
}
}else {
throw new NullPointerException("不存在该bean");
}
}
}
最后我们可以把扫描这部分逻辑提取出来重新建立一个scan方法:
public class MyApplicationContext {
private Class configClass;
private ConcurrentHashMap<String,Object> singleBeanHashMap = new ConcurrentHashMap<>();
private HashMap<String,BeanDefinition> beanDefinitionHashMap = new HashMap<>();
public MyApplicationContext(Class configClass) {
this.configClass = configClass;
scan(configClass);
}
private void scan(Class configClass) {
//解析配置类
//ComponentScan注解 --》 扫描路径 --》 扫描
ComponentScan componentScanAnnotation = (ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
String path = componentScanAnnotation.value();
String searchPath = path.replace(".", "/");
ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(searchPath);
File file = new File(resource.getFile());
if (file.isDirectory()) {
//首先筛选末尾是.class文件的
File[] files = file.listFiles();
for (File classFile: files) {
String absolutePath = classFile.getAbsolutePath();
if (absolutePath.endsWith(".class")) {
//拼接全限定名
System.out.println(classFile);
String fullyQualifiedClassName = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
fullyQualifiedClassName = fullyQualifiedClassName.replace("\\", ".");
//使用app类加载器加载这个类
Class<?> aClass = null;
try {
aClass = classLoader.loadClass(fullyQualifiedClassName);
if (aClass.isAnnotationPresent(Component.class)) {
BeanDefinition beanDefinition = new BeanDefinition();
if (aClass.isAnnotationPresent(Scope.class)) {
Scope scopeAnnotation = aClass.getAnnotation(Scope.class);
String scope = scopeAnnotation.value();
beanDefinition.setScope(scope);
}else{
beanDefinition.setScope("single");
}
Component componentAnnotation = aClass.getDeclaredAnnotation(Component.class);
String beanName = componentAnnotation.value();
beanDefinitionHashMap.put(beanName,beanDefinition);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
public Object getBean(String beanName){
//首先判断是否存在该bean的BeanDefinition
if (beanDefinitionHashMap.containsKey(beanName)) {
BeanDefinition beanDefinition = beanDefinitionHashMap.get(beanName);
if (beanDefinition.getScope().equals("single")) {
//单例模式创建bean
}else {
//原型模式创建bean
}
}else {
throw new NullPointerException("不存在该bean");
}
}
}
spring容器启动之后,先进行扫描步骤,而这个扫描的主要作用就是得到BeanDefinition,这样我们就有了BeanDefinitionMap,然后我们就可以根据BeanDefinitionMap去创建单例池singleBeanHashMap为我们的getBean方法提供支持。
在getBean方法中:
我们这里的creatBean方法就是用来创建bean的方法,我们这里暂时只对此方法进行一个简单的实现。
整体代码如下:
public class MyApplicationContext {
private Class configClass;
private ConcurrentHashMap<String,Object> singleBeanHashMap = new ConcurrentHashMap<>();
private HashMap<String,BeanDefinition> beanDefinitionHashMap = new HashMap<>();
public MyApplicationContext(Class configClass) {
this.configClass = configClass;
scan(configClass);
//scan之后就得到了beanDefinitionHashMap,然后我们根据此来构建singleBeanHashMap
for (String beanName:beanDefinitionHashMap.keySet()) {
BeanDefinition beanDefinition = beanDefinitionHashMap.get(beanName);
if (beanDefinition.getScope().equals("single")) {
singleBeanHashMap.put(beanName,createBean(beanName));
}
}
}
public Object createBean(String name){
BeanDefinition beanDefinition = beanDefinitionHashMap.get(name);
Class beanClass = beanDefinition.getBeanClass();
Object instance = null;
try {
instance = beanClass.getConstructor().newInstance();
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
return instance;
}
private void scan(Class configClass) {
//解析配置类
//ComponentScan注解 --》 扫描路径 --》 扫描
ComponentScan componentScanAnnotation = (ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);
String path = componentScanAnnotation.value();
String searchPath = path.replace(".", "/");
ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
URL resource = classLoader.getResource(searchPath);
File file = new File(resource.getFile());
if (file.isDirectory()) {
//首先筛选末尾是.class文件的
File[] files = file.listFiles();
for (File classFile: files) {
String absolutePath = classFile.getAbsolutePath();
if (absolutePath.endsWith(".class")) {
//拼接全限定名
System.out.println(classFile);
String fullyQualifiedClassName = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));
fullyQualifiedClassName = fullyQualifiedClassName.replace("\\", ".");
//使用app类加载器加载这个类
Class<?> aClass = null;
try {
aClass = classLoader.loadClass(fullyQualifiedClassName);
if (aClass.isAnnotationPresent(Component.class)) {
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setBeanClass(aClass);
if (aClass.isAnnotationPresent(Scope.class)) {
Scope scopeAnnotation = aClass.getAnnotation(Scope.class);
String scope = scopeAnnotation.value();
beanDefinition.setScope(scope);
}else{
beanDefinition.setScope("single");
}
Component componentAnnotation = aClass.getDeclaredAnnotation(Component.class);
String beanName = componentAnnotation.value();
beanDefinitionHashMap.put(beanName,beanDefinition);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
public Object getBean(String beanName){
//首先判断是否存在该bean的BeanDefinition
if (beanDefinitionHashMap.containsKey(beanName)) {
BeanDefinition beanDefinition = beanDefinitionHashMap.get(beanName);
if (beanDefinition.getScope().equals("single")) {
//单例模式创建bean
return singleBeanHashMap.get(beanName);
}else {
//原型模式创建bean
return createBean(beanName);
}
}else {
throw new NullPointerException("不存在该bean");
}
}
}
我们测试一下效果:
前面我们对createBean方法进行了一个简单地实现,这里依赖注入的实现就是将createBean写完整。我们知道不管是单例bean还是原型bean他们的作用范围可能不同,但是他们在创建某一个对象的时候步骤是一样的,这也就是我们为什么要单独抽取一个方法出来专门用来创建bean。
这里我们只考虑使用@Autowired注解进行注入,createBean方法思路:
代码如下:
public Object createBean(String name){
BeanDefinition beanDefinition = beanDefinitionHashMap.get(name);
Class beanClass = beanDefinition.getBeanClass();
Object instance = null;
try {
instance = beanClass.getConstructor().newInstance();
for (Field field:beanClass.getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
field.setAccessible(true);
//拿到属性名
String fieldName = field.getName();
field.set(instance,getBean(fieldName));
}
}
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
return instance;
}
我们测试一下:
我们思考一个场景:
如果我们想获得当前的beanName,这里是不能使用Autowired的。Spring为我们提供了一种接口BeanNameAware,将当前的beanName返回给我们。
我们将这个功能实现一下:
首先写一个BeanNameAware接口:
public interface BeanNameAware {
void setBeanNameAware(String beanName);
}
然后我们在createBean方法中调用接口方法,传入bean的name:
public Object createBean(String name){
···
//进行依赖注入
···
//BeanNameAware返回bean name
if (instance instanceof BeanNameAware) {
BeanNameAware beanNameAware = (BeanNameAware) instance;
beanNameAware.setBeanNameAware(name);
}
···
}
在bean的属性默认初始化设置完之后,我们程序员可以自定义进行业务方面的初始化,例如bean中的属性是否为空或者说是否符合业务方面的要求,或者你想对某一个属性赋值。那么这个功能如何完成呢?
在Spring中我们可以去实现InitializingBean接口来完成这一个功能,Spring在检测到当前类实现了InitializingBean接口之后,就会帮我们调用接口方法完成我们自定义的初始化。
InitializingBean接口定义如下:
public interface InitializingBean {
void afterPropertiesSet();
}
然后我们在createBean方法中调用接口方法:
public Object createBean(String name){
···
//进行依赖注入
···
//BeanNameAware返回bean name
if (instance instanceof BeanNameAware) {
BeanNameAware beanNameAware = (BeanNameAware) instance;
beanNameAware.setBeanNameAware(name);
}
//自定义初始化
if (instance instanceof InitializingBean) {
InitializingBean initializingBean = (InitializingBean) instance;
initializingBean.afterPropertiesSet();
}
···
}
BeanPostProcessor:bean的后置处理器
我们在前文中提到的初始化机制、Aware回调机制都是Spring提供给我们的拓展功能,我们只需要在需求类上实现相应接口,Spring即可帮我们完成需求。
那么Spring能否给我们提供帮助:在实例化之前或之后给我们提供额外的功能追加,或者说在初始化之前或初始化之后给我们提供额外的功能追加。
为了实现这一需求Spring为我们提供了BeanPostProcessor。
我们来看一下Spring的源码:
public interface BeanPostProcessor {
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
BeanPostProcessor有很多子接口,其中InstantiationAwareBeanPostProcessor的源码如下:
public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
@Nullable
default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
return null;
}
default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
return true;
}
@Nullable
default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
return null;
}
/** @deprecated */
@Deprecated
@Nullable
default PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
return pvs;
}
}
我们在Spring框架中要想使用这一个功能,一般是新创建一个类然后去实现相应的接口,而不会去在业务层的类中去实现。因为初始化、实例化都是相对于每一个bean来说的,我们理应将他们单独抽离出来,而且这样不会污染业务代码。
接下来我们开始BeanPostProcessor的代码实现:
实现思路如下:
代码实现:
public interface BeanPostProcessor {
Object postProcessBeforeInitialization(Object bean, String beanName);
Object postProcessAfterInitialization(Object bean, String beanName);
}
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
System.out.println("初始化之前");
if (beanName.equals("userService")) {
System.out.println("为userService初始化之前追加的功能");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("初始化之后");
return bean;
}
}
在MyApplicationContext中新建一个成员属性beanPostProcessorList:
注意:
我们测试一下:
public class MySpringTest {
public static void main(String[] args) {
MyApplicationContext applicationContext = new MyApplicationContext(AppConfig.class);
// UserService userService = (UserService) applicationContext.getBean("userService");
// System.out.println(userService.getOrderService());
// System.out.println(userService.getBeanName());
}
}
结果:
我们的扫描范围时service包下,而我们的service包下有三个类,与结果相吻合。
在spring框架中,使用代理模式比较典型的场景就是AOP的实现了,代理逻辑核心要点如下:
AOP的工作流程可以直接看我的另一篇文章:
[Spring Framework]AOP工作流程
除了动态代理技术之外,AOP在Spring中的实现还需要借助BeanPostProcessor。我们知道BeanPostProcessor是bean的后置处理器,其可以在bean初始化的前后对bean造成额外的影响。
这里我们可以借助BeanPostProcessor接口中的postProcessAfterInitialization方法,返回需要增强类的代理对象。
接下来我们进行代码实现:
这里我们实现JDK动态代理的情况,所以先要为委托类编写接口方法,方便根据接口实现代理对象:
public interface ProxyInterface {
void show();
}
@Component("userService")
@Scope("single")
public class UserService implements BeanNameAware, InitializingBean,ProxyInterface {
@Autowired
private OrderService orderService;
private String beanName;
public String getBeanName() {
return beanName;
}
@Override
public void afterPropertiesSet() {
System.out.println("自定义初始化");
}
@Override
public void setBeanNameAware(String beanName) {
this.beanName = beanName;
}
public OrderService getOrderService() {
return orderService;
}
public void show(){
System.out.println("这里是UserService的实例");
}
}
然后我们重写一下postProcessAfterInitialization方法:
@Component("myBeanPostProcessor")
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
System.out.println("初始化之前");
if (beanName.equals("userService")) {
System.out.println("为userService初始化之前追加的功能");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("初始化之后");
if (beanName.equals("userService")) {
return Proxy.newProxyInstance(
MyBeanPostProcessor.class.getClassLoader(),
bean.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("show")) {
System.out.println("这里是对show方法的前置增强");
}
return method.invoke(bean, args);
}
}
);
}
return bean;
}
}
我们这里省略了是否进行AOP的判断以及找切点的逻辑
测试:
public class MySpringTest {
public static void main(String[] args) {
MyApplicationContext applicationContext = new MyApplicationContext(AppConfig.class);
ProxyInterface proxyInterface = (ProxyInterface) applicationContext.getBean("userService");
proxyInterface.show();
}
}
我们在Spring中使用AOP时会在配置类上使用一个注解@EnableAspectJAutoProxy。这个注解会向Spring的容器中注册一个bean: