Spring学习笔记(一)---Spring

目录

Spring概述

Spring是什么?

Spring的优点

Spring的体系结构

程序解耦

IOC的概念和作用

基于XML的IOC

Spring的依赖注入

基于注解的IOC配置 

Spring中IOC的常用注解

改造基于注解的IOC案例,使用纯注解的方式实现

Spring和Junit的整合

AOP

AOP的相关概念

Spring中的AOP

基于XML的AOP

基于注解的AOP

spring中的JdbcTemplate

spring中的事务控制

基于xml的

基于注解的


Spring概述

Spring是什么?

Spring是分层的Java SE/EE应用full-stack轻量开源框架,以IOC(Inverse Of Control:反转控制)和AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层Spring MVC和持久层Spring JDBC以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐称为使用最多的Java EE企业应用开源框架。

Spring的优点

  • 方便解耦,简化开发

通过Spring提供的IOC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件等这些很底层的需求编写代码,可以更专注于上层的应用。

  • AOP编程的支持

通过Spring的AOP功能,方便进行面向切面编程,许多不容易用传统OPP实现的功能可以通过AOP轻松应付。

  • 声明式事务的支持

可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明方式灵活的进行事务管理,提高开发效率。

  • 方便程序的测试

可以用非容器依赖编程的方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。

  • 方便继承各种优秀框架

Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts,Hibernate、Hessian、Quartz等)的直接支持。

  • 降低JavaEE API的使用难度

Spring对Java EE API(如JDBC、JavaMail、远程调用)进行了薄薄的封装层,使这些API的使用难度大为降低

  • Java源码是经典学习范例

Spring的源码设计精妙、结构清晰、匠心独用,处处体现着大师对Java设计模式灵活运用以及对Java技术的高深造诣。它的源代码无疑是Java技术的最佳实践范例。

Spring的体系结构

程序解耦

JDBC过程:

注册驱动可以有上面两种形式,第一种若没有导入依赖,那么在编译时就会报错,因为找不到要创建对象的jar包。第二种反射通过全限定类名来创建对象,编译时参数只是一个字符串,不会报错,只会在运行时抛出异常。为了提高程序的可扩展性,这个全限定类名字符串可以通过读取配置文件获取。

程序的耦合:程序间的依赖关系。包括:类之间的依赖;方法间的依赖。

解耦:降低程序间的依赖关系。

实际开发中应该做到:编译器不依赖,运行时才依赖。

解耦的思路:第一步:使用反射来创建对象,而避免使用new关键字。

                     第二步:通过读取配置文件来获取要创建的对象全限定类名。

web应用程序的耦合:表现层依赖业务层,业务层依赖持久层。

工厂模式实现解耦:在工厂中读取配置文件获取目标类的全限定名创建对象。

JavaBean:JavaBean 是一种JAVA语言写成的可重用组件。

进度:P15

IOC的概念和作用

IOC:控制反转(Inverse of Control)把创建对象的权力交给框架,是框架的重要特征,并非面向对象编程的专用术语。它包括依赖注入(Dendendency Injection,简称DI)和依赖查找(Dependency Lookup)。

IOC的作用:削减计算机程序的耦合(解除代码中的依赖关系)。

基于XML的IOC

  •  创建maven项目,导入spring相关的包
<!--导入spring包-->
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.0.0.RELEASE</version>
        </dependency>
    </dependencies>
  • 在resources文件夹下创建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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--把对象的创建交给spring来管理-->
    <bean id="accountService" class="service.IAccountServiceImpl"></bean>
    <bean id="accountDao" class="dao.IAccountDaoImpl"></bean>
<beans>
  • 获得spring的核心容器,并根据id获取对象
public static void main(String[] args) {
        //获取核心容器对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //根据id获取bean对象
        IAccountService as = (IAccountService) ac.getBean("accountService");
        IAccountDao ada = ac.getBean("accountDao",IAccountDao.class);
        System.out.println(as);
        System.out.println(ada);

}

ApplicationContext就是核心容器对象,它是一个接口,继承至BeanFactory。常用的主要有三个实现类。

  1. ClassPathXmlApplicationContext(可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了。
  2. FileSystemXmlApplicationContext(它可以加载任意路径下的配置文件(必须有访问权限))
  3. AnnotationConfigApplicationContext(它是用于读取注解创建容器的)

ApplicationContext:它在构建核心容器时,创建对象的策略是采用立即加载的方式。也就是说,只要一读完配置文件马上就创建配置文件中配置的对象(反射方式)。单例对象适用。

BeanFactory:创建对象采取的策略是采用延迟加载的方式,也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象。多例对象适用。

Spring中bean的细节之创建Bean对象的三种方式。

  • 第一种方式:使用默认构造函数创建

在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其它任何属性和标签的时。

采用的就是默认构造函数创建bean对象,如果此类中没有默认构造函数,则对象无法创建

第一种创建bean的方式
<bean id="accountService" class="service.IAccountServiceImpl"></bean>
  • 第二种方式:使用普通工厂方法中的方法创建对象(使用某个类的方法创建对象,并存入spring容器)
第二种方法
<bean id="instanceFactory" class="factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
  • 第三种方法:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
<!--第三种方式-->
<bean id="accountService" class="factory.StaticFactory" factory-method="getAccountService"></bean>

后面两种主要是为了获取jar包中的某个方法创建的对象。

Spring中bean的细节之Bean的作用范围调整-bean标签的scope属性

bean标签的scope属性:用于指定bean的作用范围。

取值:常用单例和多例

  • singleton:单例的(默认值)
  • prototype:多例的
  • request:作用于web应用的请求范围
  • session:作用于web应用的会话范围
  • global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是Session。

Spring中bean的细节之Bean对象的生命周期

  • 单例对象

出生:容器创建

活着:容器还在一直活着

死亡:容器销毁

单例对象的生命周期和容器相同

  • 多例对象

出生:当使用对象时spring框架创建

活着:对象在使用过程中

死亡:当对象长时间不用,且没有别的对象引用时,有GC来进行回收。

Spring的依赖注入

依赖注入:Dependency Injection

IOC的作用:降低程序间的耦合(依赖关系)

依赖关系的管理:以后都交给spring来维护

依赖关系:在当前类需要用到其它类的对象,由spring为我们提供,我们只需要在配置关系中说明。

依赖关系的维护称之为依赖注入。

能注入的数据:

  1. 基本类型和String
  2. 其它bean类型(在配置文件中或者注解中配置过的)
  3. 复杂类型/集合类型

注入的方式:

  1. 使用构造函数提供
  2. 使用set方法提供
  3. 使用注解提供
  • 使用构造函数注入

使用标签:constructor-arg

标签出现的位置:bean标签的内部

标签中的属性:

  1. type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
  2. index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引从0开始
  3. name:用于指定给构造函数中指定名称的参数赋值
  4. value:用于提供基本数据类型和String的数据
  5. ref:用于指定其它数据类型的bean类型数据。它指的就是在spring的IOC核心容器中出现过的bean

优势:在获取bean对象的时候,注入数据是必须的操作,否则对象无法创建成功

弊端:改变了bean对象的实例化方式,使我们在创建对象时,如果使用不到这些数据,也必须提供。

public IAccountServiceImpl(String name, Integer age, Date birthday) {
    this.name = name;
    this.age = age;
    this.birthday = birthday;
}
<!--Spring中的依赖注入-->
<bean id="accountService" class="service.IAccountServiceImpl">
    <constructor-arg name="age" value="18"></constructor-arg>
    <constructor-arg name="name" value="test"></constructor-arg>
    <constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>

<bean id="now" class="java.util.Date"></bean>
  • 使用Set方法注入(更常用,相比构造函数注入)

涉及的标签:property

出现的位置:bean标签的内部

标签的属性:

  1. name:用于指定注入时所调用的set方法名称
  2. value:用于提供基本数据类型和String的数据
  3. ref:用于指定其它数据类型的bean类型数据。它指的就是在spring的IOC核心容器中出现过的bean

优势:创建对象时没有明确的限制,可以直接使用默认构造函数

弊端:如果有某个成员必须有值,则获取对象有可能set方法并没有执行。

<bean id="now" class="java.util.Date"></bean>

<bean id="accountService" class="service.IAccountServiceImpl">
    <property name="name" value="test"></property>
    <property name="age" value="22"></property>
    <property name="birthday" ref="now"></property>
</bean>

注意-复杂类型的注入/集合类型的注入:选用property下的子标签为不同类型的数据注入数据。用于给list结构注入的标签有list array set。用于map结构的有map和props。结构相同,标签可以互换。

public class IAccountServiceImpl implements IAccountService {

   private String[] myStrs;
   private List<String> myList;
   private Set<String> mySet;
   private Map<String,String> myMap;
   private Properties muProps;

    public void setMyStrs(String[] myStrs) {
        this.myStrs = myStrs;
    }

    public void setMyList(List<String> myList) {
        this.myList = myList;
    }

    public void setMySet(Set<String> mySet) {
        this.mySet = mySet;
    }

    public void setMyMap(Map<String, String> myMap) {
        this.myMap = myMap;
    }

    public void setMuProps(Properties muProps) {
        this.muProps = muProps;
    }

    public void saveCount() {
        System.out.println(Arrays.toString(myStrs));
        System.out.println(myList);
        System.out.println(mySet);
        System.out.println(myMap);
        System.out.println(muProps);
    }
}
<bean id="accountService" class="service.IAccountServiceImpl">
    <property name="myStrs">
        <array>
            <value>AAA</value>
            <value>BBB</value>
            <value>CCC</value>
        </array>
    </property>
    <property name="myList">
       <list>
           <value>AAA</value>
           <value>BBB</value>
           <value>CCC</value>
       </list>
    </property>
    <property name="myMap">
        <map>
            <entry key="AAA" value="BBB"></entry>
        </map>
    </property>
    <property name="muProps">
        <props>
            <prop key="AAA">bbb</prop>
        </props>
    </property>
</bean>

基于注解的IOC配置 

Spring中IOC的常用注解

需要先配置需要扫描的包,spring就会去扫描这些包中的注解,注意和bean.xml的约束不一样。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    <!--告知spring在创建容器需要扫描的包,配置所需要的标签不是在beans的约束中,而是一个名称为context名称空间约束中-->
    <context:component-scan base-package="com"></context:component-scan>
</beans>

注解的作用分类:

用于创建对象的:和在xml配置中编写一个bean标签是一样的

  • @Component:用于把当前类对象存入spring容器中。属性:value用于指定bean的id。不写时默认为当前类名首字母小写

以下三个注解和@Component的作用和属性是一模一样的。它们三个是spring框架为我们提供明确的三层使用的注解,使三层对象更加清晰。

  • @Controller:一般用在表现层
  • @Service:一般用在业务层
  • @Repository:一般用在持久层

用于注入数据的:就和在xml配置文件中写一个properties一样的

  • @Autowired:

作用:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量匹配成功,就可以注入成功。如果IOC容器中没有任何bean的类型和要注入的变量类型匹配,则报错。如果IOC容器中有多个类型匹配时:首先找到匹配的对象,再使用要注入的变量名称和bean的id继续匹配查找,如果有bean的id和变量名称相同,则将其注入。如果都不匹配成功,注入失败。

出现位置:可以是成员变量上,也可以是方法上。在使用注解注入时,set方法就不是必须的了。

  • @Qualifier

作用:在按照类中注入的基础上再按照名称注入。它在给类成员注入时不能单独使用。但是在给方法参数注入时可以。

属性:value:用于指定注入bean的id

@Autowired
@Qualifier("accountDao")
private IAccountDaoImpl accountDao;
  •  @Resource

作用:直接按照bean的id注入。它可以独立使用

属性:name:用于指定bean的id

@Resource(name = "accountDao")
private IAccountDaoImpl accountDao;

 以上三个数据只能注入其它bean类型的数据,而基本数据类型和String无法使用上述注解实现。另外,集合类型的注入只能通过XML来实现。

  • @Value

作用:用于注入基本类型和String类型的数据

属性:value:用于指定数据的值。它可以使用spring中的spEL。(也就是spring的el表达式)

spEL的写法:${表达式}

用于改变作用范围的:在bean标签中使用scope属性

  • @Scope

作用:指定bean的作用范围

属性:value:指定取值范围。常用范围Singleton和Prototype(默认单例)

和生命周期相关的:在bean标签中使用init-method和destory-method一样的

  • @PreDestory:用于指定销毁方法
  • @PostConstruct:用于指定初始化方法

使用XML方式和注解方式实现单表的CRUD操作

改造基于注解的IOC案例,使用纯注解的方式实现

不再配置bean.xml(不再配置bean),要替代扫描包的配置,同时将数据源对象放入ioc容器中。

  • @ComponentScan:

作用:用于通过注解指定spring在创建容器时要扫描的包

属性:value:指定创建容器要扫描的包

  • @Configuration:作用:指定当前类是一个配置类。当配置类作为AnnotationConfigApplicationContext对象创建的参数的时候,该注解可以不写。
  • @Bean:作用:用于当前方法的返回值作为bean对象存入spring的ioc容器中。属性:name用于指定bean的id,默认当前方法名称。

细节:当我们使用注解配置方法时,如果方法有参数spring框架会去容器中查找有没有可用的bean对象。查找方式和autowired是一样的。

这个时候获取容器用:基于注解的spring容器

  •  @Import 将其它配置类导入到主配置类

改造2:将数据库的连接信息放入properties中。

@PropertiSource:用于指定properties文件的位置。属性:value:指定文件的的名称和路径

Spring和Junit的整合

AOP

AOP的相关概念

ThreadLocal用于保存某个线程共享变量

  • 动态代理

就是根据对象在内存中加载的Class类创建运行时类对象,从而调用代理类方法和属性。

特点:字节码随用随创建,随用随加载。

作用:不修改源码的基础上对方法增强。

分类:基于接口的动态代理;基于子类的动态代理。

  • 基于接口的动态代理:

涉及的类:Proxy;提供者:官方

如何创建代理对象:使用Proxy类中的newProxyInstance方法。

创建代理对象的要求:被代理类最少实现一个接口,如果没有则不能使用

newProxyInstance方法的参数:

  • ClassLoader:类加载器,用于加载代理对象的字节码的,和被代理对象使用相同的类加载器。
  • Class[]:字节码数组,它是用于让代理对象和被代理对象有相同的方法
  • InvocationHandler:用于提供增强的代码,它是让我们写如何代理。一般都是写一个该接口的实现类,通常情况下是匿名内部类,但是不必须。此接口的实现类都是谁用谁写。
public class Client {
    public static void main(String[] args) {
        Produce produce = new Produce();
        IProduce proxyProduce = (IProduce)Proxy.newProxyInstance(produce.getClass().getClassLoader(),
                produce.getClass().getInterfaces(), new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //提供增强的代码
                        //获取方法执行的参数
                        Object returnVal = null;
                        Float money = (Float) args[0];
                        if("saleProduce".equals(method.getName())){
                            returnVal = method.invoke(produce,money);
                        }
                        return returnVal;
                    }
                });

        proxyProduce.saleProduce(10000f);
}

invoke(Object proxy, Method method, Object[] args)方法

作用:执行被代理对象的任何接口方法都会经过该方法。 

proxy:代理对象的引用

method:当前执行的方法

args:当前执行方法所需的参数

return:和被代理对象方法有相同的返回值。

  • 基于子类的动态代理

涉及的类:Enhancer

提供者:第三方cglib库

如何创建代理对象:使用Enhancer类中的create方法。

创建代理对象的要求:被代理类不能是最终类

  • create方法的参数:

Class:用于指定被代理对象的字节码

Callback:用于提供增强的代码。它是让我们写如何代理。一般都是写一个该接口的实现类,通常情况下是匿名内部类,但是不必须。此接口的实现类都是谁用谁写。我们一般写的都是该接口子接口的的实现类:MethodInterceptor

public class Client {
    public static void main(String[] args) {
        Produce produce = new Produce();
        Produce cglibProduce = (Produce)Enhancer.create(produce.getClass(), new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                Object returnVal = null;
                        Float money = (Float) objects[0];
                        if("saleProduce".equals(method.getName())){
                            returnVal = method.invoke(produce,money*0.8f);
                        }
                        return returnVal;
            }
        });
        cglibProduce.saleProduce(1000f);

intercept(Object proxy, Method method, Object[] args)方法

作用:执行被代理对象的任何接口方法都会经过该方法。 

proxy:代理对象的引用

method:当前执行的方法

args:当前执行方法所需的参数

methodProxy:当前执行方法的代理对象

return:和被代理对象方法有相同的返回值。

Spring中的AOP

AOP:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理技术,在不修改源码的基础上,对我们已有的方式进行增强。

作用:在程序运行期间,不修改源码对已有方法进行增强。

优势:1.减少重复代码 2.提高开发效率 3.维护方便

AOP的实现方式:动态代理技术

  • Spring中的AOP术语和细节

JoinPoint(连接点,代理的方法),PointCut(切入点,被增强的方法),Advice(通知/增强)

基于XML的AOP

首先maven中导入包

public class AccountService implements IAccountService {

    public void saveAccount() {
        System.out.println("执行了保存");
    }

    public void updateAccount(int i) {
        System.out.println("执行了更新"+i);
    }
    
    public int deleteAccount() {
        System.out.println("执行了删除");
        return 0;
    }
}
public class Logger {

    /**
     * 用于打印日志,计划让其在切入点方法执行之前执行(切入点方法就是业务层方法)
     */
    public void printLog(){
        System.out.println("Logger类printLog方法开始记录日志");
    }
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--配置spring的ioc-->
    <bean id="accountService" class="AOP.impl.AccountService"></bean>
    <!--spring中基于xml的Aop配置步骤
    1、把通知的bean也交给spring来管理
    2、使用Aop:config标签表明开始aop的配置
    3、使用aop:aspect标签表示开始配置切面.id属性:给切面提供一个唯一标识。ref属性:指定通知类bean的id。标识配置前置通知
    4、在aop:aspect标签的内部使用对应得标签来配置通知的类型:使用示例的方法是前置通知.aop:before标识配置前置通知.method属性用于
    指定Logger类中哪个属于前置通知.pointcut属性:用于指定切入点表达式,该表达式的含义指得是对业务层中哪些方法增强。
    切入点表达式得写法:关键字:execution(表达式)。表达式:访问修饰符  返回值   报名.包名.报名...方法名(参数列表)
    -->
    <bean id="logger" class="AOP.util.Logger"></bean>

    <aop:config>
        <!--配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--配置通知得类型,并且简历通知方法得切入点关联-->
            <aop:before method="printLog" pointcut="execution(public void AOP.impl.AccountService.saveAccount())"></aop:before>
        </aop:aspect>
    </aop:config>

</beans>
  • 切入点表达式写法

表达式:访问修饰符  返回值   报名.包名.报名...方法名(参数列表)

通配符写法:* *..*.*(..)

访问修饰符可省略

  • 四种常用通知类型

前置通知

后置通知

异常通知

最终通知

通知位置:

try{

前置通知(before)

方法执行

后置通知(after)(方法正常执行才执行)

}catch{

异常通知(after-throwing)

}finall{

最终通知(after-returning)(无论方法是否正常执行都执行)

}

  • 环绕通知

基于注解的AOP

导入相应的包

<dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjweaver</artifactId>
   <version>1.7.3</version>
</dependency>

bean.xml配置

<!--配置要扫描的包-->
    <context:component-scan base-package="com.service"></context:component-scan>

<!--开启aop开启注解aop的支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

切面配置

@Component("logger")
@Aspect
public class Logger {
    @Pointcut("execution(* *..*.*(..))")
    private void pt1(){

    }
    @Before("pt1()")
    public void printLog(){
        System.out.println("前置打印了日志");
    }
    @AfterReturning("pt1()")
    public void afterReturingPringLog(){
        System.out.println("后置打印了日志");
    }
    @AfterThrowing("pt1()")
    public void afterThorwingPrintLog(){
        System.out.println("异常打印了日志");
    }
    @After("pt1()")
    public void afterPrintLog(){
        System.out.println("最终打印了日志");
    }
}

spring中的JdbcTemplate

JdbcTemplate的作用:它是用于和数据库的交互,实现对表的crud操作。

如何创建该对象

对象中的常用方法

配置:将数据源注入

    <!--配置JdbcTmplate-->
    <bean id="jdbcTmplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>

常用方法:

ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTmplate");
//        jt.execute("insert into jdbc(name,money)values ('DDD',1000)");
        //CRUD
        //保存
//        jt.update("insert into jdbc(name,money)values (?,?)","eee",1000f);
        //更新
//        jt.update("update jdbc set name=?,money=? where id=?","fff",100f,5);
        //删除
//        jt.update("");
        //查询所有
//        jt.query("");
        //

    }

 

spring中的事务控制

基于xml的

基于注解的




 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值