目录
项目结构
entity
- User
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class User implements Serializable {
private String username;
private String password;
private int age;
}
dao
IUserDao
public interface IUserDao {
/**
* Add user
*/
int addUser(User user);
/**
* Get user by id.
*/
User getUserById(int id);
/**
* Get all users.
*/
List<User> getAllUsers();
/**
* Get users by name.
*/
List<User> getUsersByName(String name);
}
UserDaoImpl
public class UserDaoImpl implements IUserDao {
@Override
public int addUser(User user) {
System.out.println("Dao: addUser()");
return 0;
}
@Override
public User getUserById(int id) {
System.out.println("Dao: getUserById()");
return null;
}
@Override
public List<User> getAllUsers() {
System.out.println("Dao: getAllUsers()");
return Collections.emptyList();
}
@Override
public List<User> getUsersByName(String name) {
System.out.println("Dao: getUsersByName()");
return Collections.emptyList();
}
}
service
IUserService
public interface IUserService {
void login();
void register();
}
UserServiceImpl
public class UserServiceImpl implements IUserService {
/**
* Each time this interface is called, aenw UserDaoImpl object is created,
* resulting in resource waste or causing OutOfMemoryError in serious cases.
*/
IUserDao userDao = new UserDaoImpl();
@Override
public void login() {
userDao.getAllUsers();
System.out.println("This is implementation o login method.");
}
@Override
public void register() {
userDao.getAllUsers();
System.out.println("This is implementation o register method.");
}
}
假设 Web 应用程序现在运行正常。
每次用户调用 IUserService
接口时,都会创建一个 IUserDaoImpl
对象,这会导致资源浪费,在严重情况下可能会引发 OutOfMemoryError
。
IUserDao userDao = new UserDaoImpl();
如何解决这个问题❓
我们可以将 IUserDaoImpl
的 Class
作为 key,IUserDaoImpl
实例作为 value 存储在一个HashMap<Class, Object>
中。
- 在 Web 应用程序运行之前,将 ApplicationConext 类中的对象加载并初始化到 IOC 容器中。
- 当调用
IUserService
接口时,Java 会获取IUserDaoImpl
对象,而不再需要创建新的IUserDaoImpl
对象。
ApplicationContext
public class ApplicationContext {
// Store bean objects in a Hashmap, equivalent to ioc container.
private HashMap<Class<?>, Object> beanFactory = new HashMap<>();
/**
* init ApplicationContext, and put bean objects into IOC container.
*/
public void initContext() throws IOException, ClassNotFoundException {
// Load specified objects to IOC container.
beanFactory.put(IUserService.class,new UserServiceImpl());
beanFactory.put(IUserDao.class,new UserDaoImpl());
}
/**
* get bean object by class from IOC container.
*/
public Object getBean(Class<?> clazz) {
return beanFactory.get(clazz);
}
/**
* return all bean objects in IOC container.
*/
public HashMap<Class<?>, Object> getAllBeans() {
return beanFactory;
}
}
配置文件初始化 IOC 容器
❓问题:如上述代码所示,我们可以将指定的对象加载到IOC容器中,但如果我们需要添加一些新对象时,就必须修改源代码,这非常麻烦。
public void initContext() throws IOException, ClassNotFoundException {
// Load specified objects to IOC container.
beanFactory.put(IUserService.class,new UserServiceImpl());
beanFactory.put(IUserDao.class,new UserDaoImpl());
// Add new objects into beanFactory.
// beanFactory.put(xxxx.class,new xxxxx());
// ...........
}
为了解决这个问题,我们可以使用 Java 中的反射。
- 创建一个
application.properties
文件,在其中可以添加我们需要的对象信息。
IOC.service.IUserService = IOC.service.Impl.UserServiceImpl
IOC.dao.IUserDao = IOC.dao.Impl.UserDaoImpl
- 利用反射机制加载对象,并存储到 IOC 容器。
public void initContext() throws IOException, ClassNotFoundException {
// Load application.properties file and Get information of bean object.
Properties properties = new Properties();
properties.load(new FileInputStream("src/main/resources/application.properties"));
Set<Object> keys = properties.keySet();
for (Object key : keys) {
Class<?> keyClass = Class.forName(key.toString());
String value = properties.getProperty(key.toString());
Class<?> valueClass = Class.forName(value);
Object instance = valueClass.newInstance();
// put bean object into IOC container.
beanFactory.put(keyClass, instance);
}
}
RunApplication
public class RunApplication {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ApplicationContext applicationContext = new ApplicationContext();
applicationContext.initContext();
// Get all beans.
HashMap<Class<?>, Object> allBeans = applicationContext.getAllBeans();
Set<Class<?>> classes = allBeans.keySet();
for (Class<?> clazz : classes) {
System.out.println(clazz.getName() + " -> " + allBeans.get(clazz));
}
}
}
注解初始化 IOC 容器
@Bean
- 新增
annotation
包,创建 Bean 注解类。
@Target(ElementType.TYPE) // 只能修饰类型元素
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {}
- 在
UserDaoImpl
和UserServiceImpl
类上加上 **@Bean **注解。
@Bean
public class UserServiceImpl implements IUserService {}
@Bean
public class UserDaoImpl implements IUserDao {}
- 在
ApplicationContext
类中添加使用注解初始化 bean 对象。
// root path of project.
private String filePath;
/**
* Load all bean objects with annotation @Bean from the project path.
*/
public void initContextByAnnotation() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
// Get the project path.
filePath = Objects.requireNonNull(ApplicationContext.class.getClassLoader().getResource("")).getFile();
// Load all bean objects into IOC container.
loadOne(new File(filePath));
}
/**
* Load all class file with @Bean.
*/
public void loadOne(File fileParent) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
if(!fileParent.isDirectory()){
return;
}
File[] childrenFiles = fileParent.listFiles();
if (childrenFiles == null) {
return;
}
for (File child : childrenFiles) {
if (child.isDirectory()) {
loadOne(child);
} else {
String pathWithClass = child.getAbsolutePath().substring(filePath.length() - 1);
// Get file name like UserServiceImpl.class
if (pathWithClass.contains(".class")) {
String fullName = pathWithClass
.replaceAll("\\\\", ".")
.replace(".class", "");
Class<?> clazz = Class.forName(fullName);
if (!clazz.isInterface()) {
Bean annotation = clazz.getAnnotation(Bean.class);
if (annotation != null) {
Object instance = clazz.newInstance();
Class<?>[] interfacesList = clazz.getInterfaces();
// interface as key, if object has no interface.
if (interfacesList.length > 0) {
Class<?> interfaceClass = interfacesList[0];
beanFactory.put(interfaceClass, instance);
}
// clazz as key, if object has interface.
else {
beanFactory.put(clazz, instance);
}
}
}
}
}
}
}
- 修改
RunApplication
启动类中使用注解初始化 IOC 容器。
public class RunApplication {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
ApplicationContext applicationContext = new ApplicationContext();
//applicationContext.initContext();
// Load beans by @Bean.
applicationContext.initContextByAnnotation();
// Get all beans.
HashMap<Class<?>, Object> allBeans = applicationContext.getAllBeans();
Set<Class<?>> classes = allBeans.keySet();
for (Class<?> clazz : classes) {
System.out.println(clazz.getName() + " -> " + allBeans.get(clazz));
}
}
}
输出:
@Autowired
- 在 annotation 包中添加 Aurowired 注解类。
@Target(ElementType.FIELD) // 只能修饰成员变量
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {}
- 在
UserServiceImpl
成员变量添加@Autowired
注解。
@Bean
public class UserServiceImpl implements IUserService {
@Autowired
private IUserDao userDao;
}
- 在
ApplicationContext
类中添加使用注解初始化 bean 对象后,初始化各个 bean 对象的成员变量。- 这里默认只实现了根据类型初始化成员变量(在 springboot 中支持类型和名称)。
/**
* Load all bean objects with annotation @Bean from the project path.
*/
public void initContextByAnnotation() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
// Get the project path.
filePath = Objects.requireNonNull(ApplicationContext.class.getClassLoader().getResource("")).getFile();
// Load all bean objects into IOC container.
loadOne(new File(filePath));
// initiate fields of object.
assembleObject();
}
/**
* @Autowired annotation to initiate fields of bean objects.
*/
public void assembleObject() throws IllegalAccessException {
Set<Class<?>> keys = beanFactory.keySet();
for (Class<?> key : keys) {
Object value = beanFactory.get(key);
Class<?> clazz = value.getClass();
// Set value of field by @Autowired.
Field[] declaredFields = clazz.getDeclaredFields();
for (Field filed : declaredFields) {
Autowired annotation = filed.getAnnotation(Autowired.class);
if (annotation != null) {
filed.setAccessible(true);
// Get bean object by type from IOC container.
Object object = beanFactory.get(filed.getType());
filed.set(value, object);
}
}
}
}
- 修改 RunApplication 启动类,并且调用 login() 方法 。
public class RunApplication {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
ApplicationContext applicationContext = new ApplicationContext();
//applicationContext.initContext();
applicationContext.initContextByAnnotation();
// Get all beans.
HashMap<Class<?>, Object> allBeans = applicationContext.getAllBeans();
Set<Class<?>> classes = allBeans.keySet();
for (Class<?> clazz : classes) {
System.out.println(clazz.getName() + " -> " + allBeans.get(clazz));
}
// Get bean object by class type.
IUserService bean = (IUserService) applicationContext.getBean(IUserService.class);
// call login method.
bean.login();
}
}
输出:
Dao 层调用了 getAllUsers()
方法,说明依赖注入成功。
源码:https://github.com/RainbowJier/HandWrittenSpring