java架構之路-(spring源碼篇)由淺入深-spring實戰詳細使用


  今天我更新了一篇jvm垃圾回收的算法和垃圾回收器的內部邏輯,但是看的人不多啊......貌似大家還是比較喜歡看源碼吧,畢竟實戰要比理論用的多。

  這篇文章不會詳細的深入底層源碼,只是基於注解和配置來說說我們的spring的使用,別小看基礎,保證有你沒用過的注解和配置,走起。
我們先來建立一個maven項目,引入spring文件,不愛弄的在文章最下面有代碼地址可以去下載。先看,后面自己下載代碼自己去嘗試。先給你們吧,邊嘗試邊看吧。碼雲地址:https://gitee.com/dwyui/springboke.git

一、IOC容器注冊組件的方式

1.基礎XML注入Bean

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="car" class="com.springIOC.bean.CarBean"></bean><!--id是唯一表示,class是全路徑 -->
</beans>
package com.springIOC;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext cac = new ClassPathXmlApplicationContext("config.xml");
        Object car = cac.getBean("car");
    }
}

是不是超級簡單的,我們由淺入深一點點來。

2.基於注解的方式來配置

package com.springIOC2.config;

import com.springIOC.bean.CarBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MainConfig {
    @Bean
    public CarBean car(){//注意方法名 return new CarBean();
    }
}
package com.springIOC2;

import com.springIOC2.config.MainConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext aca = new AnnotationConfigApplicationContext(MainConfig.class);
        Object car = aca.getBean("car");
    }
}

我們通過方法名就可以直接得到我們的對象了,默認就是按照方法來裝配。也可以通過@Bean(value="newName") 來指定裝配的名字。

 3.按照包掃描的方式裝配(重點),使用@ComponentScan(basePackages={"包的全路徑"})

package com.springIOC3.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = {"com.springIOC3"})
public class MainConfig {

}
package com.springIOC3;

import com.springIOC3.config.MainConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainTest {

    /**
     * 基礎@CompentScan,包掃描方式來配置
     * @param args
     */
    public static void main(String[] args) {
        AnnotationConfigApplicationContext aca = new AnnotationConfigApplicationContext(MainConfig.class);
        String[] beanDefinitionNames = aca.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println("beanDefinitionName = " + beanDefinitionName);
        }
    }
}

這里在來說幾個參數,excludeFilters排除某一些對象,語法如下

excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class}),  //排除所有Controller的注解類,多個value可以用逗號分隔
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,value = {RepositoryBean.class}) //排除RepositoryBean類對象

}

FilterType有五種,分別是ANNOTATION(注解類)ASSIGNABLE_TYPE(類名),ASPECTJ(不常用,文檔說AspectJ類型模式表達式匹配),REGEX(正則表達式匹配),CUSTOM(自定義),常用的三種我標記了紅色。下面看一下具體寫法

package com.springIOC3b.config;

import com.springIOC3b.repository.RepositoryBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;

@Configuration
@ComponentScan(basePackages = {"com.springIOC3b"},excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class}),
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,value = {RepositoryBean.class})
})
/**
 * 1:排除用法 excludeFilters(排除@Controller注解的,和RepositoryBean類)
 */
public class MainConfig {

}

剛才我們說到了自定義過濾,我們來看一下怎么寫自定義的過濾,實現我們TypeFilter接口,重寫我們的match即可,只關注返回的true。下面是一個事例

package com.springIOC3c.config;

import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;

import java.io.IOException;

public class CustomFilterType implements TypeFilter {

    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        //獲取當前類的注解源信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        //獲取當前類的class的源信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata(); //獲取當前類的資源信息
        Resource resource = metadataReader.getResource();
        
        if (classMetadata.getClassName().contains("RepositoryBean")) { //這里注意,他是一個包含的匹配規則,即使我寫成下面注釋掉那樣也可以的
            return true;
        }
//        if (classMetadata.getClassName().contains("ryBean")) {
//            return true;
//        }
        return false;
    }
}

與包含相反的還有一個,只允許引入什么,也就是我們的includeFilters,需要注意需要把useDefaultFilters屬性設置為false(true表示掃描全部的)。語法和excludeFilters完全一致

package com.springIOC3d.config;

import com.springIOC3d.service.ServiceBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;

@Configuration
@ComponentScan(basePackages = {"com.springIOC3d"},includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class}),
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,value = ServiceBean.class)
},useDefaultFilters = false)
/**
 * 只許引入*** includeFilters(只許引入@Controller注解和ServiceBean類),useDefaultFilters設置為false,關閉全包掃描
 */
public class MainConfig {

}

4.回過頭來,我們看一下Bean的作用域。

@Lazy懶加載,使用才實例化,看下代碼,我們在Bean里加入構造方法,更方便得出什么時候實例化的。

package com.springIOC4.config;

import com.springIOC4.bean.CarBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;

@Configuration
public class MainConfig {
    @Bean
    @Lazy
    public CarBean car(){
        return new CarBean();
    }
}

指定@Scpoe可以有四種作用域

a) singleton 單實例的(默認),單例的生命周期有spring容器來控制,非懶加載時在spring實例化以后就產生了對象,容器銷毀則對象銷毀

package com.springIOC4b.config;

import com.springIOC4b.bean.CarBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;

@Configuration
public class MainConfig {
    @Bean
    @Scope(value = "singleton")
    public CarBean car(){
        return new CarBean();
    }
}
package com.springIOC4b;

import com.springIOC4b.bean.CarBean;
import com.springIOC4b.config.MainConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainTest {
    /**
     * @Scope(value = "singleton")單例,默認也是@Scope(value = "singleton")
     * @param args
     */
    public static void main(String[] args) {
        AnnotationConfigApplicationContext aca = new AnnotationConfigApplicationContext(MainConfig.class);
        CarBean car = (CarBean)aca.getBean("car");
        CarBean car2 = (CarBean)aca.getBean("car");
        System.out.println(car == car2); // # true
    }
}

輸出結果為true,說明我們的對象是單例的,單例的對象,生命周期由spring來管理的。

b) prototype 多實例的

package com.springIOC4c.config;

import com.springIOC4c.bean.CarBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class MainConfig {
    @Bean
    @Scope(value = "prototype")
    public CarBean car(){
        return new CarBean();
    }
}
package com.springIOC4c;

import com.springIOC4c.bean.CarBean;
import com.springIOC4c.config.MainConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainTest {
    /**
     * @Scope(value = "prototype")多例
     * @param args
     */
    public static void main(String[] args) {
        AnnotationConfigApplicationContext aca = new AnnotationConfigApplicationContext(MainConfig.class);
        CarBean car = (CarBean)aca.getBean("car");
        CarBean car2 = (CarBean)aca.getBean("car");
        System.out.println(car == car2); // # false
    }
}

多例的不受ioc容器來管理,銷毀時是由GC來清理的,還有request同一次請求和session同一個會話級別的,這里就不一一演示了。

5.@Configuration注解,來判斷是否注入Bean的。

package com.springIOC5.config;

import com.springIOC5.bean.CarBean;
import com.springIOC5.bean.UserBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MainConfig {

    @Bean(value = "user")
    public UserBean userBean() {
        return new UserBean();
    }

    @Bean
    @Conditional(value = IOCConditional.class)
    public CarBean carBean() {
        return new CarBean();
    }
}
package com.springIOC5.config;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class IOCConditional implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        if (context.getBeanFactory().containsBean("user")) { //這里必須和Bean的名稱完全一致。
            return true;
        }
        return false;
    }
}

上面的代碼什么意思呢?就是我們是否需要注入carBean,如果包含user這個對象,就注入我們的carBean,不包含就不注入,這里有個意思的事,Configuration配置里類的注入是有順序的,我們必須把我們作為判斷條件的Bean放在上面,否則Conditional會識別你沒有那個判斷條件的Bean。

6.@Import引入方式注入Bean

package com.springIOC6.config;

import com.springIOC6.bean.CarBean;
import com.springIOC6.bean.UserBean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import({CarBean.class, UserBean.class})
public class MainConfig {

}

直接在注解內寫入我們的要注入的類即可,也可以使用接口的方式來實現,我們來看換一下。

package com.springIOC6b.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import({ImportSelector.class})
public class MainConfig {

}
package com.springIOC6b.config;

import org.springframework.core.type.AnnotationMetadata;

public class ImportSelector implements org.springframework.context.annotation.ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.springIOC6b.bean.CarBean","com.springIOC6b.bean.UserBean"};
    }
}

實現ImportSelector類,然后返回類名全路徑即可。自動裝配就是基於@Import實現的。

實現ImportBeanDefinitionRegistrar,重寫registerBeanDefinitions方法,也是可以的。

package com.springIOC6c.config;

import com.springIOC6c.bean.CarBean;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

public class ImportSelectorRegister implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(CarBean.class);
        registry.registerBeanDefinition("CarBean",rootBeanDefinition);
    }
}

7.通過FactoryBean注入

package com.springIOC7.config;

import com.springIOC7.bean.UserBean;
import org.springframework.beans.factory.FactoryBean;

public class IOCFactoryBean implements FactoryBean<UserBean> {

    @Override
    public UserBean getObject() throws Exception {//指定對象
        return new UserBean();
    }

    @Override
    public Class<?> getObjectType() {//指定類型
        return UserBean.class;
    }

    @Override
    public boolean isSingleton() {//指定是否為單例
        return true;
    }
}
package com.springIOC7.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MainConfig {
    @Bean
    public IOCFactoryBean iocFactoryBean(){
        return new IOCFactoryBean();
    }
}
package com.springIOC7;

import com.springIOC7.config.MainConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainTest {

    /**
     * FactoryBean注入
     * @param args
     */
    public static void main(String[] args) {
        AnnotationConfigApplicationContext aca = new AnnotationConfigApplicationContext(MainConfig.class);
        Object carBean = aca.getBean("iocFactoryBean");//取出userBean
        System.out.println(carBean);

        Object iocFactoryBean = aca.getBean("&iocFactoryBean");//取得FactoryBean
        System.out.println(iocFactoryBean);
    }
}

說到這所有往IOC容器中添加組件的方式就全部說完了,簡單總結一下:

  @Bean注入,可以指定四種作用域,單例,多例(生命周期不受IOC容器管理),一次請求和一次會話,也可以設置懶加載,

  @ComponentScan指定包掃描的方式來注入,配合@Controller,@Repository,@Service,@Component注解來使用。

  @Import方式注入,兩種實現類ImportSelector和ImportBeanDefinitionRegistrar兩種方式。

  @FactoryBean,工程Bean的方式也可以注入。注意不帶&是取得最終對象,帶&是取得真實Bean。三個方法,一個指定對象,一個指定類型,一個指定是否為單例。

二、Bean的生命周期---初始化方法和銷毀方法

針對單實例bean的話,容器啟動的時候,bean的對象就創建了,而且容器銷毀的時候,也會調用Bean的銷毀方法。

針對多實例bean的話,容器啟動的時候,bean是不會被創建的而是在獲取bean的時候被創建,而且bean的銷毀不受IOC容器的管理。

1.我們先來看個最簡單的方法,用initMethod和destroyMethod來指定我們的初始化方法和銷毀方法

package com.springlifeCycle1a.config;

import com.springlifeCycle1a.bean.CarBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MainConfig {

    @Bean(initMethod = "init",destroyMethod = "destroy")
    public CarBean car() {
        return new CarBean();
    }
}

我們在指定了我們的init初始方法,銷毀方法為destroy方法。調用順序是,Car的構造方法,Car的init方法,Car的destroy方法,也可以自己嘗試使用@Lazy注解。碼雲代碼里有可以自己去嘗試。

2.通過 InitializingBean和DisposableBean 的兩個接口實現bean的初始化以及銷毀方法 

package com.springlifeCycle2a.bean;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

public class CarBean implements InitializingBean, DisposableBean {
    public CarBean() {
        System.out.println("我是Car");
    }

    public void afterPropertiesSet() {
        System.out.println("我是初始化init");
    }

    public void destroy() {
        System.out.println("我是銷毀destroy");
    }
}

3.通過JSR250規范 提供的注解@PostConstruct 和@ProDestory標注的方法

package com.springlifeCycle3.bean;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

public class CarBean {
    public CarBean() {
        System.out.println("我是Car");
    }

    @PostConstruct
    public void init() {
        System.out.println("我是初始化init--PostConstruct");
    }

    @PreDestroy
    public void destory() {
        System.out.println("我是銷毀destroy--PreDestroy");
    }
    
}

4.通過Spring的BeanPostProcessor的 bean的后置處理器會攔截所有bean創建過程 (這個方法后面講源碼的時候會去講內部實現,自己覺得有必要看這個的源碼

package com.springlifeCycle4.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class LifeBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("初始化方法" + beanName);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("銷毀方法" + beanName);
        return bean;
    }
}

這里也總結一下,我們來指定容器內對象的初始化方法和銷毀方法的方式一共有四種

1.用@Bean的initMethod 和destroyMethod 來給予初始化方法和銷毀方法。

2.通過 InitializingBean和DisposableBean 的二個接口實現bean的初始化以及銷毀方法。

3.通過JSR250規范 提供的注解@PostConstruct 和@ProDestory標注的方法。

4.通過Spring的BeanPostProcessor的 bean的后置處理器會攔截所有bean創建過程。

三、給屬性賦值

  這里的東西不多,我就盡快說一下啦,賦值的方式有三種我們來看一下。

package com.springValue.config;

import com.springValue.bean.CarBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
@PropertySource(value = "classpath:carBean.properties",encoding = "utf-8") //指定外部文件的位置
public class MainConfig {

    @Bean
    public CarBean carBean() {
        return new CarBean();
    }
}
import org.springframework.beans.factory.annotation.Value;

public class CarBean {

    @Value("寶馬")//通過普通的方式
    private String name;

    @Value("#{5-2}")//spel方式來賦值
    private int carNum;

    @Value("${carBean.realName}")//通過讀取外部配置文件的值
    private String realName;
        
}//自己記得加get set方法。

這里值得一提的就是導入文件最好設置一下encoding = "utf-8",不然漢字會亂碼。

四、自動裝配:

  主動裝配平時是我們最熟悉的,用的也是最多的,我們來復習一下。

1.@Autowired  自動裝配首先時按照類型進行裝配,若在IOC容器中發現了多個相同類型的組件,那么就按照 屬性名稱來進行裝配

package com.springxAutowired1.config;

import com.springxAutowired1.dao.AutowiredDao;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(value = "com.springxAutowired1.service")
public class MainConfig {

    @Bean
    public AutowiredDao autowiredDao1(){
        return new AutowiredDao(1);
    }

    @Bean
    public AutowiredDao autowiredDao2(){
        return new AutowiredDao(2);
    }
}
package com.springxAutowired1.service;

import com.springxAutowired1.dao.AutowiredDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class ServiceBean {

    @Autowired
    private AutowiredDao autowiredDao2;

    public ServiceBean() {
        System.out.println("我是serviceBean");
    }

    @Override
    public String toString() {
        return "ServiceBean{" +
                "AutowiredDao=" + autowiredDao2 +
                '}';
    }
}

在這里我們設置了兩個AutowiredDao的對象,一個標識為1,一個標識為2,ServiceBean默認是按照名字來裝配的。

2.我們也可以通過@Qualifier來指定裝配名字。

package com.springxAutowired1b.service;

import com.springxAutowired1b.dao.AutowiredDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class ServiceBean {

    @Autowired
    @Qualifier(value = "autowiredDao1")
    private AutowiredDao autowiredDao2;

    public ServiceBean() {
        System.out.println("我是serviceBean");
    }

    @Override
    public String toString() {
        return "ServiceBean{" +
                "AutowiredDao=" + autowiredDao2 +
                '}';
    }
}

3.我們使用Qualifier是如果名字寫錯了,可能裝配錯誤會報錯,這時我們可以使用required = false來阻止異常的拋出

package com.springxAutowired3.service;

import com.springxAutowired3.dao.AutowiredDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class ServiceBean {

    @Qualifier(value = "autowiredDaoError")
    @Autowired(required = false)
    private AutowiredDao autowiredDao2;

    public ServiceBean() {
        System.out.println("我是serviceBean");
    }

    @Override
    public String toString() {
        return "ServiceBean{" +
                "AutowiredDao=" + autowiredDao2 +
                '}';
    }
}

注意:這里我並沒有說@Resource注解,這個注解其實不是spring里的,是JSR250規范的,但是不支持@Primary 和@Qualifier的支持

五、環境切換:

我們有時候需要通過不同的環境來切換我們的配置,我們通過@Profile注解,來根據環境來激活標識不同的Bean,

@Profile標識在類上,那么只有當前環境匹配,整個配置類才會生效

@Profile標識在Bean上 ,那么只有當前環境的Bean才會被激活

package com.springxAutowired4.config;

import com.springxAutowired4.bean.Environment;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration
public class MainConfig {

    @Bean
    @Profile(value = "test")
    public Environment environment_test() {
        return new Environment("test");
    }

    @Bean
    @Profile(value = "dev")
    public Environment environment_dev() {
        return new Environment("dev");
    }

    @Bean
    @Profile(value = "pro")
    public Environment environment_pro() {
        return new Environment("pro");
    }
}

激活切換環境的方法

方法一:通過運行時jvm參數來切換 -Dspring.profiles.active=test,dev,prod多個參數表中間使用英文的逗號來分隔

方法二:通過代碼的方式來激活

package com.springxAutowired4;

import com.springxAutowired4.config.MainConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainTest {

    /**
     * 環境切換
     * @param args
     */
    public static void main(String[] args) {
        AnnotationConfigApplicationContext aca = new AnnotationConfigApplicationContext();
        aca.getEnvironment().setActiveProfiles("test","dev");//方便演示,我寫了兩個,一般都是一個的.

        aca.register(MainConfig.class);
        aca.refresh();
        for (String beanDefinitionName : aca.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }
    }
}

如果兩個都寫了,按照代碼中的來實現,參數不再起作用

 

今天就說到這里, 大概就這么多吧。后期我也會基於這篇博客來詳細的扒一下源碼,中間會穿插一些spring的常見面試題。

代碼地址:https://gitee.com/dwyui/springboke.git

 

 

最進弄了一個公眾號,小菜技術,歡迎大家的加入


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM