Spring基础知识——笔记整理(一)

Spring简介

Spring是一个轻量级Java开发框架,由Rod Johnson创建,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题。它是一个JavaSE/JavaEE分层的full-stack(一站式)轻量级开源框架,为开发Java应用程序提供全面的基础架构支持。

Spring负责基础架构,因此Java开发者可以专注于应用程序的开发。

Spring的优点

  1. 方便解耦,简化开发:Spring就是一个大工厂,可以将所有对象创建和依赖的关系维护,交给Spring管理。
  2. AOP编程的支持:Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能。
  3. 声明式事务的支持:只需要通过配置就可以完成对事务的管理,而无需手动编程。
  4. 方便程序的测试:Spring对Junit4支持,可以通过注解方便的测试Spring程序。
  5. 方便集成各种优秀框架:Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的直接支持(如:Struts、Hibernate、MyBatis等)。
  6. 降低JavaEE API的使用难度:Spring对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低。

Spring框架可以说是当前Java世界中最为成功的框架,在企业实际应用中,大部分的企业架构都基于Spring框架。Spring的成功来自于理念,而不是技术,最核心的理念是控制反转(IOC/DI)面向切面编程(AOP),以及声明式事务。其中IOC是spring的基础,AOP则是其重要的功能,最为典型的当属数据库事务的使用。

spring框架已经融入了J2EE开发的各个领域,不论是数据访问层,还是控制层,又或是表现层,全都可以看到spring的身影。

Spring体系结构

Spring框架至今已集成了20多个模块,这些模块分布在以下模块中:

  • 核心容器(Core Container)
  • 数据访问/集成(Data Access/Integration)层
  • Web层
  • AOP(Aspect Oriented Programming)模块
  • 植入(Instrumentation)模块
  • 消息传输(Messaging)
  • 测试(Test)模块

核心容器

Spring的核心容器是其他模块建立的基础,有Spring-core、Spring-beans、Spring-context、Spring-context-support和Spring-expression(Spring表达式语言)等模块组成。

  • Spring-core模块:提供了框架的基本组成部分,包括控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)功能。
  • Spring-beans模块:提供了BeanFactory,是工厂模式的一个经典实现,Spring将管理对象称为Bean。
  • Spring-context模块:建立在Core和Beans模块的基础之上,提供一个框架式的对象访问方式,是访问定义和配置的任何对象的媒介。ApplicationContext接口是Context模块的焦点。
  • Spring-context-support模块:支持整合第三方库到Spring应用程序上下文,特别是用于高速缓存(EhCache、JCache)和任务调度(CommonJ、Quartz)的支持。
  • Spring-expression模块:提供了强大的表达式语言去支持运行时查询和操作对象图。这是对JSP2.1规范中规定的统一表达式语言(Unified EL)的扩展。该语言支持设置和获取属性值、属性分配、方法调用、访问数组、集合和索引器的内容、逻辑和算术运算、变量命名以及从Spring的IOC容器中以名称检索对象。它还支持列表投影、选择以及常用的列表聚合。

AOP和Instrumentation

  • Spring-aop模块:提供了一个符合AOP要求的面向切面的编程实现,允许定义方法拦截器和切入点,将代码按照功能进行分离,以便干净地解耦。
  • Spring-aspects模块:提供了与AspectJ的集成功能,AspectJ是一个功能强大且成熟的AOP框架。
  • Spring-instrument模块:提供了类植入(Instrumentation)支持和类加载器的实现,可以在特定的应用服务器中使用。

消息

Spring4.0以后新增了消息(Spring-messaging)模块,该模块提供了对消息传递体系结构和协议的支持。

数据访问/集成

数据访问/集成层由JDBC、ORM、OXM、JMS和事务模块组成。

  • Spring-jdbc模块:提供了一个JDBC的抽象层,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析。
  • Spring-orm模块:为流行的对象关系映射(Object-Relational Mapping)API提供集成层,包括JPA和Hibernate。使用Spring-orm模块可以将这些O/R映射框架与Spring提供的所有其他功能结合使用,例如声明式事务管理功能。
  • Spring-oxm模块:提供了一个支持对象/XML映射的抽象层实现,例如JAXB、Castor、JiBX和XStream。
  • Spring-jms模块(Java Messaging Service):指Java消息传递服务,包含用于生产和使用消息的功能。自Spring4.1以后,提供了与Spring-messaging模块的集成。
  • Spring-tx模块(事务模块):支持用于实现特殊接口和所有POJO(普通Java对象)类的编程和声明式事务管理。

Web

Web层由Spring-web、Spring-webmvc、Spring-websocket和Portlet模块组成。

  • Spring-web模块:提供了基本的Web开发集成功能,例如多文件上传功能、使用Servlet监听器初始化一个IOC容器以及Web应用上下文。
  • Spring-webmvc模块:也称为Web-Servlet模块,包含用于web应用程序的Spring MVC和REST Web Services实现。Spring MVC框架提供了领域模型代码和Web表单之间的清晰分离,并与Spring Framework的所有其他功能集成。
  • Spring-websocket模块:Spring4.0以后新增的模块,它提供了WebSocket和SocketJS的实现。
  • Portlet模块:类似于Servlet模块的功能,提供了Portlet环境下的MVC实现。

测试

Spring-test模块支持使用JUnit或TestNG对Spring组件进行单元测试和集成测试。

spring环境搭建

使用Maven搭建spring环境,在pom.xml中进行以下依赖即可:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.2.2.RELEASE</version>
</dependency>

不使用Maven,则需要下载spring的压缩包,并解压将对应jar包导入项目。

下载: https://repo.spring.io/list/libs-release-local/org/springframework/spring ,选择spring-版本号-release-dist.zip下载即可。

将下载的压缩包解压后,可以从libs目录中找到spring各个模块的jar。

spring的不同模块具备不同的功能,因此,具体的spring环境搭建,应该根据当前开发的应用使用到的功能来决定,将某个模块的jar包导入项目即可使用该模块对应的功能。


学习笔记,整理不易。你的支持,我的动力!

更多学习资料,IT系列课程,请关注vx公众号:豆萌萌 网课大咖

为您提供全网最全的学习资料

更有面试题整理,金三银四冲刺,IT电子书籍等

你需要的,我恰好有,愿意推荐给你哦!


IOC/DI:控制反转/依赖注入

控制反转是软件设计大师 Martin Fowler在 2004 年发表的《Inversion of Control Containers and the Dependency Injection pattern》提出的。这篇文章系统阐述了控制反转的思想,提出了控制反转有依赖查找和依赖注入实现方式。

控制反转是一种通过描述(XML或注解)并通过第三方去产生或获取特定对象的方式。使用控制反转带来的最大好处就是降低对象之间的耦合。

程序中对象的产生是基于IOC容器,而不是开发者主动的行为。开发者主动创建的模式,责任归于开发者,在使用IOC容器被动创建的模式下,责任归于IoC容器。基于这样的被动形式,我们就说对象被控制反转了。

spring支持XML和注解两种方式实现IOC。

自定义IOC框架理解IOC/DI

需求:模拟通过配置文件实现IoC/DI、通过注解实现IoC/DI。

# 1.通过xml配置文件实现自定义IoC框架
//模拟三层架构
//dao
public class UserDao {
    public void dao(){
        System.out.println("dao层方法执行!!!");
    }
}
//service
public class UserService {
    private UserDao userDao=new UserDao();
    public void service(){
        System.out.println("service层方法执行!!!");
        userDao.dao();
    }
}
//servlet
public class UserServlet {
    public static void main(String[] args) {
        UserService service = new UserService();
        userService.service();
    }
}




# 2.理解IoC框架要实现的功能
# 3.创建对应的类
//PropertyDifinition
public class PropertyDifinition {
    private String name;
    private String ref;
	//setter/getter......
}

//BeanDifinition
public class BeanDifinition {
    private String id;
    private String className;
    private List<PropertyDifinition> propertyDifinitions;
    //setter/getter......
}

//ApplicationContext:容器类型有多种,通过读取数据方式不同而不同,此处需要使用两种:xml或注解
public interface ApplicationContext {
    void addBean(String id,Object object);//将实例存入容器
    Object getBean(String id);//根据实例唯一标识从容器中取出实例
}

//ClasspathXmlApplicatonContext:先实现读取xml文件完成IoC
public class ClasspathXmlApplicationContext implements ApplicationContext{
    private Map<String,Object> beans=new HashMap<>();//容器

    public ClasspathXmlApplicationContext() {
        BeanFactory beanFactory = new BeanFactory(this);//将容器传入BeanFactory,方便调用
        beanFactory.initBean();//初始化bean--控制反转
        beanFactory.dependencyInjection();//依赖注入
    }
    public void addBean(String id,Object object){
        beans.put(id,object);//向容器中存放实例
    }

    public Object getBean(String id){
        return beans.get(id);//从容器中取出实例
    }
}


//BeanFactory
public class BeanFactory {
    private List<BeanDifinition> beanDifinitions=new ArrayList<>();//存放所有的<bean>标签信息
    private ApplicationContext ApplicationContext;//

    /**
     * 在构造方法中读取配置文件,将配置文件内容存入beanDifinitions
     * IoC容器分为两种类型:一种通过读取配置文件构建,一种通过读取类的注解构建
     * 使用接口类型实现多态
     * 需要注意的是:此处只为简单实现以理解IoC原理,没有去考虑xml与注解混用的情况
     */
    public BeanFactory(ApplicationContext ApplicationContext) {
        this.applicationContext=applicationContext;
        String path = this.getClass().getResource("/").getPath();//获取项目根目录
        //项目根目录以“/”开头时,可使用subString()用于去除,否则不必执行截取操作,但replace()操作必须有
        path=path.substring(1).replace("/",File.separator);
        //如果传入的容器是读取xml配置时,读取配置文件信息
        if (applicationContext instanceof ClasspathXmlApplicationContext) {
            try {
                SAXReader saxReader = new SAXReader();
                Document document = saxReader.read(path+"applicationContext.xml");
                Element root = document.getRootElement();
                List<Element> beanElements = root.elements("bean");
                //遍历所有bean标签,将bean标签信息存入BeanDifinition
                beanElements.forEach(element -> {
                    BeanDifinition beanDifinition = new BeanDifinition();
                    String id = element.attributeValue("id");
                    String className = element.attributeValue("class");
                    beanDifinition.setId(id);
                    beanDifinition.setClassName(className);
                    //获取bean标签中所有property标签
                    List<Element> propertyElements = element.elements("property");
                    ArrayList<PropertyDifinition> propertyDifinitions = new ArrayList<>();
                    //遍历所有property标签,将property标签信息存入PropertyDifinition
                    propertyElements.forEach(element1 -> {
                        PropertyDifinition propertyDifinition = new PropertyDifinition();
                        String name = element1.attributeValue("name");
                        String ref = element1.attributeValue("ref");
                        propertyDifinition.setName(name);
                        propertyDifinition.setRef(ref);
                        propertyDifinitions.add(propertyDifinition);
                    });
                    beanDifinition.setPropertyDifinitions(propertyDifinitions);
                    beanDifinitions.add(beanDifinition);
                });
            } catch (DocumentException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 根据beanDifinitions内容反射构建类实例
     */
    public void initBean(){
        //遍历所有BeanDifinition,生成对应的Bean实例,存入IoC容器
        beanDifinitions.forEach(beanDifinition -> {
            try {
                String id = beanDifinition.getId();
                String className = beanDifinition.getClassName();
                //反射:根据类的全限定名字符串创建类的实例
                Class<?> c = Class.forName(className);
                Object instance = c.newInstance();
                //存入容器
                applicationContext.addBean(id,instance);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        });
    }

    /**
     * 实现依赖注入
     */
    public void dependencyInjection(){
        beanDifinitions.forEach(beanDifinition -> {
            String id = beanDifinition.getId();
            String className = beanDifinition.getClassName();
            List<PropertyDifinition> propertyDifinitions = beanDifinition.getPropertyDifinitions();
            propertyDifinitions.forEach(propertyDifinition -> {
                String name = propertyDifinition.getName();
                String ref = propertyDifinition.getRef();
                //需要注入的实例
                Object originBean = applicationContext.getBean(id);
                //用于注入的实例
                Object refBean = applicationContext.getBean(ref);
                try {
                    //反射获取需要注入属性
                    Class<?> c = Class.forName(className);
                    Field field = c.getDeclaredField(name);
                    //强制放开私有属性赋值操作
                    field.setAccessible(true);
                    //给私有属性赋值
                    field.set(originBean,refBean);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            });
        });
    }
}



# 4.在Servlet类中测试
//测试之前应将UserService类中创建UserDao实例的代码去掉
public class UserService {
    private UserDao userDao;
    public void service(){
        System.out.println("service层方法执行!!!");
        userDao.dao();
    }
}

//使用自定义IoC框架中的容器获取UserService实例
public class UserServlet {
    public static void main(String[] args) {
//        UserService service = new UserService();
        ApplicationContext applicationContext = new ClasspathXmlApplicationContext();
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.service();
    }
}


# 5.增加注解配置
	修改xml配置文件,在配置文件中指定框架递归扫描哪些包下的注解
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
    <!--指定框架启动后,扫描当前项目中所有com.woniu包下所有类上的注解-->
     <component-scan package="com.woniu"/>
</beans>


# 6.修改BeanFactory,增加扫描注解的代码
//创建注解
@Target(ElementType.TYPE)//作用于类上,为创建实例做准备
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
    String value() default "";
}

@Target(ElementType.FIELD)//作用于属性上,为依赖注入做准备
@Retention(RetentionPolicy.RUNTIME)
public @interface Resource {
}

//在UserDao、UserService类上加@Component注解,在UserService类中的userDao属性上增加@Resource注解

//增加容器类,用于注解配置
public class AnnotationConfigurationApplicationContext implements ApplicationContext{
    private Map<String,Object> beans=new HashMap<>();

    public AnnotationConfigurationApplicationContext() {
        BeanFactory beanFactory = new BeanFactory(this);
        beanFactory.initBean();
        beanFactory.dependencyInjection();
    }
    public void addBean(String id,Object object){
        beans.put(id,object);
    }

    public Object getBean(String id){
        return beans.get(id);
    }
}

//修改BeanFactory
public class BeanFactory {
    private List<BeanDifinition> beanDifinitions=new ArrayList<>();
    private ApplicationContext applicationContext;

    /**
     * 在构造方法中读取配置文件,将配置文件内容存入beanDifinitions
     */
    public BeanFactory(ApplicationContext applicationContext) {
        this.applicationContext=applicationContext;
        String path = this.getClass().getResource("/").getPath();//获取项目根目录
        path=path.substring(1).replace("/",File.separator);
        //如果是纯XML配置
        if (applicationContext instanceof ClasspathXmlApplicationContext) {
            ......
        }else if(applicationContext instanceof AnnotationConfigurationApplicationContext){//如果是注解配置
            try {
                //解析配置文件,获取扫描范围
                SAXReader saxReader = new SAXReader();
                Document document = saxReader.read(path+"applicationContext.xml");
                Element root = document.getRootElement();
                Element element = root.element("component-scan");
                String packageName = element.attributeValue("package");
                packageName=packageName.replace(".",File.separator);
                //递归扫描path所在路径下的所有文件
                File directory = new File(path+packageName);
                loopDirectory(directory,path);
            } catch (DocumentException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 递归遍历包下所有类,将被注解的类生成实例
     */
    
    public void loopDirectory(File file,String path){
        if (file.isDirectory()) {//如果是目录,递归遍历
            File[] files = file.listFiles();
            if (files != null && files.length > 0) {
                for (File child : files) {
                    loopDirectory(child,path);
                }
            }
        }else{//如果是文件,获取文件的绝对路径,从中筛选出所有以.class结尾的文件
            String absolutePath = file.getAbsolutePath();
            if (absolutePath.endsWith(".class")) {
                //从绝对路径中获取类的全限定名
                //示例:绝对路径为E:\a\b\c\com\woniu\dao\UserDao.class,path为E:\a\b\c\
                //链式操作,replace(path,"")====》  com\woniu\dao\UserDao.class
                //replace(".class","")====》  com\woniu\dao\UserDao
                //replace(File.separator,".")====>  com.woniu.dao.UserDao
                String className=absolutePath.replace(path,"").replace(".class","").replace(File.separator,".");
                try {
                    Class<?> c = Class.forName(className);
                    //查看类上是否有@Component注解
                    if (c.isAnnotationPresent(Component.class)) {
                        BeanDifinition beanDifinition = new BeanDifinition();
                        beanDifinition.setClassName(className);
                        String id=null;
                        //如果@Component注解没有指定value,使用的默认值(即类名首字母小写)
                        if (!"".equals(c.getDeclaredAnnotation(Component.class))) {                            id=c.getSimpleName().substring(0,1).toLowerCase()+c.getSimpleName().substring(1);
                        }else{//如果指定了value,以指定值为准
                            id=c.getDeclaredAnnotation(Component.class).value();
                        }
                        beanDifinition.setId(id);
                        //获取类中所有属性
                        Field[] fields = c.getDeclaredFields();
                        ArrayList<PropertyDifinition> propertyDifinitions = new ArrayList<>();
                        //如果属性上有@Resource注解,则进行依赖注入
                        if (fields != null && fields.length > 0) {
                            for (Field field : fields) {
                                if (field.isAnnotationPresent(Resource.class)) {
                                    PropertyDifinition propertyDifinition = new PropertyDifinition();
                                    propertyDifinition.setName(field.getName());
                                    propertyDifinition.setRef(field.getName());
                                    propertyDifinitions.add(propertyDifinition);
                                }
                            }
                        }
                        beanDifinition.setPropertyDifinitions(propertyDifinitions);
                        beanDifinitions.add(beanDifinition);
                    }
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }

            }
        }
    }
	......
}

public class UserServlet {
    public static void main(String[] args) {
//        UserService service = new UserService();
        AnnotationConfigurationApplicationContext applicationContext = new AnnotationConfigurationApplicationContext();
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.service();
    }
}

spring:XML实现

入门案例

需求:搭建spring IoC环境,使用spring容器创建并管理类的对象。

步骤1:创建web项目,将spring容器中的4个jar包:spring-core、spring-context、spring-expression、spring-beans以及spring-jcl导入在WEB-INF/lib目录中。如果希望使用log4j,还应导入对应的jar包。

注:spring的日志功能依赖于commons-logging,spring-jcl包中集成了commons-logging,较老版本的sping中没有spring-jcl,此时必须单独导入commons-logging。

步骤2:创建实体类src/com/wsjy/domain/Subject.java

public class Subject implements Serializable {

}

步骤3:创建spring核心配置文件src/applicationContext.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
	<!-- 
		bean标签对应于实体类对象,在java程序中可以通过容器的getBean(id属性值)获取实体类对象
		id属性是实体类对象的唯一标识
		class属性是通过反射创建对象时的全限定类名
	-->
    <bean id="subject" class="com.wsjy.domain.Subject"/>

</beans>
注:<beans>标签中的属性部分无需手动配置,可在spring解压目录:spring-framework-5.2.2.RELEASE\docs\spring-framework-reference\core.html中Ctrl+F查找xmlns,即可找到相应内容复制即可。

步骤4:创建测试类src/com/wsjy/test/Test.java

public class Test {
    public static void main(String[] args) {
        //1、读取配置文件,生成spring容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

        //2、根据bean标签id获取spring容器管理的bean实例
        Subject sub = (Subject) ac.getBean("subject");
        System.out.println(sub);
    }
}

运行测试类后,可在控制台看到对应Subject实例信息被打印,在测试类中并没有手动创建Subject类实例,而是通过spring容器获取的Subject类对象,由此可知,是spring容器创建并管理了Subject类对象。

spring创建对象(控制反转)的三种方式

使用构造方法创建

入门案例中使用的就是构造方法创建,使用的是默认的无参构造。

使用工厂创建

在实际应用开发过程中,会使用到很多的第三方类库,第三方类库中的类都是class字节码文件,无法通过修改源码的形式提供构造方法,此时类的对象一般情况下是通过工厂模式来提供的,spring核心容器也提供了根据工厂模式来创建和管理类的方法。

使用实例工厂创建

需求:模拟实例工厂创建。

步骤1:

创建Subject子类:src/com/wsjy/domain/PhysicsSubject.java

public class PhysicsSubject extends Subject implements Serializable {

}

创建Subject子类:src/com/wsjy/domain/LiteratureSubject.java

public class LiteratureSubject extends Subject implements Serializable {

}

步骤2:创建工厂类src/com/wsjy/factory/SubjectFactory.java

public class SubjectFactory {
	//工厂方法,根据sub_no生产不同的Subject子类
    public Subject getSubject(Integer sub_no){
        switch (sub_no) {
            case 1:
                return new PhysicsSubject();
            case 2:
                return new LiteratureSubject();
            default:
                return null;
        }
    }
}

步骤2:修改核心配置文件src/applicationContext.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
	<!-- 创建工厂实例 -->
    <bean id="subjectFactory" class="com.wsjy.factory.SubjectFactory"/>

	<!-- 
		使用工厂创建Subject类的实例
		factory-bean属性指定创建当前类的实例使用哪个工厂
		factory-method属性指定创建当前类的实例使用的是工厂的哪个方法
 	-->
    <bean id="subject" factory-bean="subjectFactory" factory-method="getSubject">
        <!-- 使用constructor-arg为工厂方法传递参数 -->
        <constructor-arg name="sub_no" value="2"/>
    </bean>
</beans>

使用静态工厂创建

需求:模拟静态工厂创建。

步骤1:修改工厂类src/com/wsjy/factory/SubjectFactory.java

public class SubjectFactory {
	//将原本的工厂方法修改成静态方法
    public static Subject getSubject(Integer sub_no){
        switch (sub_no) {
            case 1:
                return new PhysicsSubject();
            case 2:
                return new LiteratureSubject();
            default:
                return null;
        }
    }
}

步骤2:修改核心配置文件src/applicationContext.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 
		创建Subject类实例 
		class属性指定当前实例由哪个类来创建
		factory-method属性指定创建类实例的方法
	-->
	<bean id="subject" class="com.wsjy.factory.SubjectFactory" 
          factory-method="getSubject">
        <!-- 使用constructor-arg为工厂方法传递参数 -->
        <constructor-arg name="sub_no" value="2"/>
    </bean>
</beans>

学习笔记,整理不易。你的支持,我的动力!

更多学习资料,IT系列课程,请关注vx公众号:豆萌萌 网课大咖

为您提供全网最全的学习资料

更有面试题整理,金三银四冲刺,IT电子书籍等

你需要的,我恰好有,愿意推荐给你哦!


spring依赖注入的三种方式

spring依赖注入有以下三种方式:

  • 构造器注入:依赖于类的构造方法实现,构造方法可有参也可无参;
  • setter注入:依赖于类的setter方法实现,灵活且可读性高,这是spring中最主流的注入方式;
  • 接口注入:当注入的资源并非来自本系统,而是来自于系统外部,比如数据库连接资源在Tomcat下配置,并通过JNDI的方式去获取,此时数据库资源就属于外部资源,可以使用接口注入方式获取它;
接口注入模式因为具备侵入性,它要求组件必须与特定的接口相关联,因此并不被看好,实际使用有限。

构造器注入

需求:使用带参构造为spring管理的Subject实例注入相应属性值。

步骤1:修改src/com/wsjy/domain/Subject.java

public class Subject implements Serializable {
    private Integer subNo;
    private String subName;

    public Subject(Integer subNo, String subName) {
        this.subNo = subNo;
        this.subName = subName;
    }
	
    /*toString*/
}

步骤2:修改核心配置文件src/applicationContext.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="subject" class="com.wsjy.domain.Subject">
        <constructor-arg name="subNo" value="1"/>
        <constructor-arg name="subName" value="HTML"/>
    </bean>

</beans>
<constructor-arg>标签属性详解
  • type:为构造方法中的某个类型的参数赋值;
  • index:指定为构造方法中某个索引的参数赋值,索引从0开始计数;
  • name:指定为构造方法中某个名称为name属性取值的参数赋值,一般使用该方式;
  • value:被赋值的数据。
  • ref:指定为构造方法中某个参数赋值,该值的类型不是java基本数据类型和String类型,而是一个在spring容器中已被注册的bean;

setter注入

需求:使用setter方法为spring管理的Subject实例注入相应属性值。

步骤1:修改src/com/wsjy/domain/Subject.java

package com.wsjy.domain;

import java.io.Serializable;

public class Subject implements Serializable {
    private Integer subNo;
    private String subName;

    public Integer getSubNo() {
        return subNo;
    }

    public void setSubNo(Integer subNo) {
        this.subNo = subNo;
    }

    public String getSubName() {
        return subName;
    }

    public void setSubName(String subName) {
        this.subName = subName;
    }

    /*toString*/
}

步骤2:修改核心配置文件src/applicationContext.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

        <bean id="subject" class="com.wsjy.domain.Subject">
            <property name="subNo" value="1"/>
            <property name="subName" value="html"/>
        </bean>

</beans>
<property>与<constructor-arg>类似,但没有type和index属性。

接口注入

接口注入的形式很少见,一般用于注入外部资源。

需求:Tomcat中的web项目使用了spring,通过spring的机制,使用JNDI获取Tomcat启动的数据库连接池。

步骤1:在web/META-INF目录中新建context.xml,如果没有META-INF,需要手动添加;

<?xml version="1.0" encoding="UTF-8" ?>
<Context>
    <Resource name="mydb" auth="Container" type="javax.sql.DataSource"
   maxActive="100" maxIdle="30" maxWait="10000"
   username="root" p a s s w o r d="root" driverClassName="com.mysql.jdbc.Driver"
   url="jdbc:mysql://localhost:3306/taotao?useUnicode=true&amp;characterEncoding=utf-8"/>
</Context>

步骤2:修改核心配置文件src/applicationContext.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    
	<bean id="dataSource"
          class="org.springframework.jndi.JndiObjectFactoryBean">
    	<property name="jndiName">
        	<value>java:comp/env/mydb</value>
        </property> 
    </bean>

</beans>

步骤3:修改index.jsp

<%@ page import="javax.naming.NamingException" %>
<%@ page import="javax.sql.DataSource" %>
<%@ page import="java.sql.SQLException" %>
<%@ page import="java.sql.Connection" %>
<%@ page import="java.sql.PreparedStatement" %>
<%@ page import="java.sql.ResultSet" %>
<%@ page import="org.springframework.context.support.ClassPathXmlApplicationContext" %>
<%@ page import="org.springframework.context.ApplicationContext" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <%
      //获取spring容器
      ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
      //通过spring容器获取注册的数据源对象
      DataSource ds = ac.getBean("dataSource", DataSource.class);
      try {
        Connection conn = ds.getConnection();
        PreparedStatement ps = conn.prepareStatement("select * from t_subject");
        ResultSet rs = ps.executeQuery();
        while (rs.next()) {
          out.print(rs.getInt("sub_no")+"\t");
          out.print(rs.getString("sub_name")+"\t");
        }
        rs.close();
        ps.close();
        conn.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
  %>
  </body>
</html>

启动tomcat,可看到数据库t_subject表中内容在浏览器中全部展示。

依赖注入的三种类型

基本数据类型、String

基本数据类型和String可以直接在value处赋值,spring容器会自动完成类型转换并将值注入到对象属性中。

在容器中注册过的bean

在<property>或<constructor-arg>中使用ref属性引用在spring容器中注册过的bean。

需求:为Student类的birthday属性注入日期类型。

步骤1:创建实体类src/com/wsjy/domain/Student.java

public class Student implements Serializable {
    private Integer sid;
    private String sname;
    private Integer ssex;
    private Integer sage;
    private String saddress;
    private java.sql.Date sbirthday;
    private Integer cid;

	/*setter/getter/toString*/
}
注:为了与数据库表字段属性进行对应,此处的birthday属性类型是java.sql.Date,而不是java.util.Date。

步骤2:修改核心配置文件src/applicationContext.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="student" class="com.wsjy.domain.Student">
        <property name="sid" value="1"/>
        <property name="sname" value="张三"/>
        <property name="ssex" value="1"/>
        <property name="sage" value="35"/>
        <property name="saddress" value="北京海淀"/>
        <property name="sbirthday" ref="sqlDate"/>
        <property name="cid" value="3"/>
    </bean>

    <bean id="sqlDate" class="java.sql.Date">
        <constructor-arg name="year" value="95"/>
        <constructor-arg name="month" value="5"/>
        <constructor-arg name="day" value="25"/>
    </bean>

</beans>

注:可选择设置<bean>标签的autowire属性设置容器中注入过的bean的自动注入,也可在<beans>标签上设置default-autowire设置全局自动注入。
autowire属性常用取值:
  • byName,如果容器中有注册的bean名称与实体类属性名称相同,自动注入
  • byType,如果容器中有注册的bean的类型与实体类属性类型相同,自动注入
  • constructor,使用构造方法注入,底层还是byName
  • no,不自动注入
  • default,使用全局自动注入的值,如果没有设置全局自动注入,默认no

一般来说,不会配置全局自动注入,另外自动注入一般也不会在XML文件中配置,而是通过注解来实现,因为注解的自动注入功能比XML配置要更强大。

复杂类型(集合类型)

spring容器可以注入的复杂类型有数组、List、Set、Map、Properties类型。

需求:一个学生学习多个科目,分别使用数组、List、Set、Map、Properties进行实现。

步骤1:修改实体类src/com/wsjy/domain/Student.java

public class Student implements Serializable {
    private Integer sid;
    private String sname;
    private Integer ssex;
    private Integer sage;
    private String saddress;
    private Date sbirthday;
    private Integer cid;

    //private Subject[] subs;
	//private List<Subject> subs;
    //private Set<Subject> subs;
    //private Map<Integer,Subject> subs;
    private Properties subs;
      
	/*setter/getter/toString*/
}

步骤2:修改核心配置文件src/applicationContext.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="student" class="com.wsjy.domain.Student">
        <property name="sid" value="1"/>
        <property name="sname" value="张三"/>
        <property name="ssex" value="1"/>
        <property name="sage" value="35"/>
        <property name="saddress" value="北京海淀"/>
        <property name="sbirthday" ref="sqlDate"/>
        <property name="cid" value="3"/>
        <property name="subs">
            <!--
 				数组:<array>
				List:<list>
				Set:<set>
				测试时更改标签即可。
            <array>
                <bean class="com.wsjy.domain.Subject">
                    <property name="subNo" value="1"/>
                    <property name="subName" value="html"/>
                </bean>
                <bean class="com.wsjy.domain.Subject">
                    <property name="subNo" value="2"/>
                    <property name="subName" value="java"/>
                </bean>
                <bean class="com.wsjy.domain.Subject">
                    <property name="subNo" value="3"/>
                    <property name="subName" value="python"/>
                </bean>
            </array>-->
            
            <!--
 				Map:<map>和<entry>
				Properties:<pros>和<prop>
				应注意的是,properties不能保存对象,只能保存值。
			
            <map>
                <entry key="1">
                    <bean class="com.wsjy.domain.Subject">
                        <property name="subNo" value="1"/>
                        <property name="subName" value="html"/>
                    </bean>
                </entry>
                <entry key="2">
                    <bean class="com.wsjy.domain.Subject">
                        <property name="subNo" value="2"/>
                        <property name="subName" value="java"/>
                    </bean>
                </entry>
                <entry key="3">
                    <bean class="com.wsjy.domain.Subject">
                        <property name="subNo" value="3"/>
                        <property name="subName" value="python"/>
                    </bean>
                </entry>
            </map>-->
			<!--properties的用法-->
			<props>
                <prop key="1">html</prop>
				<prop key="2">java</prop>
				<prop key="3">python</prop>
    		</props>
			
        </property>
    </bean>


    <bean id="sqlDate" class="java.sql.Date">
        <constructor-arg name="year" value="95"/>
        <constructor-arg name="month" value="5"/>
        <constructor-arg name="day" value="25"/>
    </bean>

</beans>
注:
  1. list、set、array可以互换;
  2. 在集合中保存的不是对象时,map和props可以互换;

spring p命名空间和c命名空间

在通过构造方法或set方法给bean注入关联项时通常是通过constructor-arg元素和property元素来定义的。在有了p命名空间和c命名空间时我们可以简单的把它们当做bean的一个属性来进行定义。

p命名空间

使用p命名空间时需要先声明使用对应的命名空间,即在beans元素上加入xmlns:p="http://www.springframework.org/schema/p"。

c命名空间

c命名空间的用法和p命名空间类似,其对应于constructor-arg,即可以将constructor-arg元素替换为bean的一个以c命名空间前缀开始的属性。使用c命名空间之前也需要通过xmlns:c=”http://www.springframework.org/schema/c”进行声明。

P命名空间和c命名空间使用案例:

<?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:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 使用property子标签注入 -->
	<!--<bean id="student" class="com.wsjy.domain.Student">
        <property name="sid" value="1"/>
        <property name="sname" value="张三"/>
        <property name="ssex" value="1"/>
        <property name="sage" value="35"/>
        <property name="saddress" value="北京海淀"/>
        <property name="sbirthday" ref="sqlDate"/>
        <property name="cid" value="3"/>
    </bean>-->
	
    <!-- 使用p命名空间通过属性形式进入注入,属性为引用类型时,在属性名后加-ref引用即可 -->
    <bean id="student" class="domain.Student" p:sid="1" p:sname="张三" p:ssex="1"
           p:sage="35" p:saddress="北京海淀" p:sbirthday-ref="sqlDate" p:cid="3"/>
	
    <!-- 使用constructor-arg子标签注入 -->
 	<!--<bean id="sqlDate" class="java.sql.Date">
        <constructor-arg name="year" value="95"/>
        <constructor-arg name="month" value="5"/>
        <constructor-arg name="day" value="25"/>
    </bean>-->
	
    <!-- 使用c命名空间通过属性形式进入注入,属性为引用类型时,在属性名后加-ref引用即可
 		第一种方式按构造方法参数名称注入(按日期对象构造方法参数名称注入有bug,构造的日期对象不正确)
		第二种方式按构造方法参数位置注入
	-->
    <!--<bean id="sqlDate" class="java.sql.Date" c:year="95" c:month="5" c:day="25"/>-->
   	<bean id="sqlDate" class="java.sql.Date" c:_0="95" c:_1="5" c:_2="25"/>

</beans>

获取spring容器的三种方式

在入门案例的测试类中,先获取spring容器ApplicationContext后,再调用其方法获取其创建和管理的对象。ApplicationContext是BeanFactory接口的子接口之一,它对BeanFactory的功能做了很多有用的扩展,绝大部分情况下会使用ApplicationContext作为IoC容器。

BeanFactory和ApplicationContext的区别:

  • BeanFactory创建对象是延迟加载的,即什么时候调用对象,什么时候创建;
  • ApplicationContext创建对象则更智能,会根据对象的单例还是多例,来选择是否延迟加载(单例立即加载,多例延迟加载);

创建ApplicationContext容器的三种方式

  • 解析类路径下的XML文件创建(ClassPathXmlApplicationContext),要求配置文件必须存在于类路径下;
  • 解析系统文件路径下的XML文件创建(FileSystemXmlApplicationContext),配置文件可以在系统的任意路径中;
  • 解析注解创建(AnnotationConfigApplicationContext);
一般来说,不建议使用FileSystemXmlApplicationContext,因为系统路径的权限是否开放决定了是否能够访问到该路径下的文件。当权限没有开放时,由于访问不到该路径,因此加载不到配置文件,会出现异常。

bean的作用域

spring容器通过<bean>标签的scope属性控制其作用域。

<bean id="subject" class="com.wsjy.domain.Subject" scope="singleton">
 <property name="subNo" value="2"/>
 <property name="subName" value="html"/>
</bean>

在 Spring IoC 容器中具有以下几种作用域:

  • singleton:单例模式,默认,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例,适用于无状态bean;
  • prototype:原型模式(多例),每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例,适用于有状态的Bean;
  • request:对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。在Web应用中使用Spring时,该作用域才有效;
  • session:对于每次HTTP Session,使用session定义的Bean都将产生一个新实例。在Web应用中使用Spring时,该作用域才有效;
  • globalsession:每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。典型情况下,仅在使用portlet context的时候有效。在集群环境下使用spring时,该作用域生效,如不是集群环境,该作用域等同于session。

bean的生命周期

根据bean的作用域不同,其生命周期也是不相同的。

单例对象

  • 出生:容器创建时,由于单例对象会立即加载,因此单例也就随着容器的创建就被创建了;
  • 存活:当容器一直存在时,单例对象也一起存在;
  • 死亡:当容器销毁时,单例对象随着容器的销毁一起销毁。

也就是说,单例对象的生命周期与spring容器保持一致。

多例对象

  • 出生:容器创建时,多例对象延迟加载,直到使用该对象时spring容器执行创建操作;
  • 存活:只要对象被使用,对象就一直存在;
  • 死亡:多例对象不会随着容器销毁而销毁,它的销毁由java垃圾回收机制决定;

需求:测试bean的生命周期与作用域之间的关系。

步骤1:修改src/com/wsjy/domain/Subject.java

public class Subject implements Serializable {
	......
        
    public void init(){
        System.out.println("subject对象被创建了。。。");
    }

    public void destroy(){
        System.out.println("subject对象被销毁了。。。");
    }
    
    ......
}

步骤2:修改核心配置文件src/applicationContext.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="subject" class="com.wsjy.domain.Subject" 
          scope="prototype" init-method="init" destroy-method="destroy">
        <property name="subNo" value="1"/>
        <property name="subName" value="html"/>
    </bean>

</beans>

步骤3:修改测试类

public class Test {
    public static void main(String[] args) {
        //1、读取配置文件,生成spring核心容器
        //注意,此处不使用多态,因为ApplicationContext接口中没有close()方法
        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

        //2、根据bean标签id获取spring核心容器管理的bean实例
        Subject sub = (Subject) ac.getBean("subject");
        System.out.println(sub);
        //3、关闭spring容器
        ac.close();
    }
}

运行测试类可以发现:当<bean>标签scope属性为singleton时,spring容器关闭时,subject对象被销毁;当<bean>标签scope属性为prototype时,spring容器关闭时,subject对象没有被销毁。

spring:注解实现

在实际应用开发过程中,更多的会考虑使用注解而不是XML来装配bean。因为使用注解的方式可以大大减少XML配置,且功能更为强大。注解不但实现了XML的功能,还提供了自动装配功能,采用了自动装配后,开发人员需要做的决断就变少了,从而更有利于程序的开发,这也体现了“约定优于配置”的开发原则。

在spring中,提供了两种方式来让spring容器发现bean:

  • 组件扫描:通过定义资源的方式,让spring容器扫描对应的包,从而把bean装配进来;
  • 自动装配:通过注解定义,使得一些依赖关系可以通过注解完成;

通过扫描和自动装配,绝大部分的项目都可以用java配置完成,而不是XML,目前注解已经成为spring开发的主流。

spring IoC容器使用的注解有四类:

  • 创建对象,其作用相当在配置一个<bean>标签;
  • 注入数据,其作用相当于在<bean>标签中配置子标签<property>;
  • 控制作用域,其作用相当于为<bean>标签配置scope属性;
  • 生命周期相关,其作用相当于为<bean>标签配置init-method和destroy-method属性;

创建对象

spring提供了4个注解用于创建对象:@Component、@Controller、@Service、@Repository。这4个注解的作用都是一样的,都用于将被注解的类存入spring容器,让spring容器管理该类的对象。

@Controller、@Service、@Repository的语义更强,它们是spring用于区分三层架构而设计的。一般来说,@Controller用于表现层(控制层),@Service用于业务层,@Repository用于持久层。

需求:使用注解将src/com/wsjy/pojo/Subject.java注册到spring容器中。

步骤1:将spring-aop包导入项目,要使用注解,需要aop的支持。

步骤2:修改核心配置文件src/applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--导入context命名空间及约束-->
<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">
	
    <!-- 使用context扫描对应包下的所有类,并将其存入到spring容器 -->
    <context:component-scan base-package="com.wsjy"/>

</beans>

步骤3:修改src/com/wsjy/domain/Subject.java

/**
 * @Component只有一个value属性,用于设置被注解的类在spring容器中的id值
 * 如省略不写,默认以被注解类首字母小写的类名作为id
 * @Component("subject")或@Component(value="subject")都是合法的
 */
@Component
public class Subject implements Serializable {
	......
}

运行测试类,控制台可打印subject对象。

以上案例也可以使用纯注解实现,使用自定义的配置类来代替心配置文件:
创建一个无逻辑的配置类来代替核心配置文件src/com/wsjy/domain/ApplicationConfig.java
/**
 * @Configuration注解的类会被spring认为是一个配置类,相当于核心配置文件
 * @ComponentScan代表进行扫描,有两个配置项:basePackages\basePackageClasses
 * 当不写任何配置项时,默认扫描被注解的ApplicationConfig所在包中的所有被@Component注解的类
 * 当配置basePackages时,basePackages属性取值为一个包的完全限定名数组,
 * 表示spring容器会去把数组中存在的包下所有被@Component注解的类装配成bean
 * 当配置basePackageClasses时,basePackageClasses属性取值为一个类或者接口的完全限定名数组,
 * 表示spring容器会去把数组中存在的类或者接口的实现类装配成bean
 * basePackages和basePackageClasses可以同时存在,spring会进行区分,不会重复配置生成多个对象
 * basePackages等同于value,查看源码可知,是通过别名来实现的
*/
@Configuration
@ComponentScan(value = {"com.wsjy"})
//@ComponentScan(basePackages = {"com.wsjy"},basePackageClasses = {SubjectFactory.class})
public class ApplicationConfig {
}
修改测试类
public class Test {
	public static void main(String[] args) {
  //1、通过反射生成spring核心容器
 	ApplicationContext ac = new AnnotationConfigApplicationContext(ApplicationConfig.class);
     //2、根据bean标签id获取spring核心容器管理的bean实例
    	Subject sub = (Subject) ac.getBean("subject");
    	System.out.println(sub);
   	}
   }
运行测试类,与使用核心配置文件得到的结果是相同的。

注入数据

基本类型和String

spring注入数据时,如果是基本数据类型或String时,使用@Value注解来实现。

需求:上例中的subject对象可以创建,但对象属性为null,使用@Value注解为属性赋值。

修改src/com/wsjy/domain/Subject.java

@Component
public class Subject implements Serializable {
    //@value只有一个value属性,可省略不写
    @Value("1")
    private Integer subNo;
    @Value("html")
    private String subName;
}

注:使用@Value注解为成员变量赋值,是通过反射实现的,无需提供setter方法。

在容器中注册过的bean

spring注入在容器中注册过的bean时,可以使用自动装配@Autowired或@Resource ,该注解可以在spring容器中自动寻找与被注解成员变量类型相同的bean装配到该变量。

@Autowired:默认按类型注入,相同类型的bean有多个时,需要进行处理,否则报异常。

@Resource:默认按名称注入,如果名称对应的bean与属性类型不匹配,报异常,如果没有找到相应名称,则按类型注入,需要进行处理,否则报异常。

需求:为Student类的Classes(班级)属性注入Classes对象。

步骤1:创建班级类Classes.java

@Component
public class Classes {
    @Value("1")
    private Integer cid;
    @Value("java4")
    private String cname;
    @Value("28")
    private Integer studentNumber;
    //使用SpEL(spring的EL表达式)设置java.sql.Date类型的值
    @Value("#{T(java.sql.Date).valueOf('2019-8-4')}")
    private Date openingDate;

    @Override
    public String toString() {
        return "Classes{" +
                "cid=" + cid +
                ", cname='" + cname + '\'' +
                ", studentNumber=" + studentNumber +
                ", openingDate=" + openingDate +
                '}';
    }
}

步骤2:修改Student.java

@Component
public class Student implements Serializable {
    @Value("3")
    private Integer sid;
    @Value("tom")
    private String sname;
    @Value("1")
    private Integer ssex;
    @Value("23")
    private Integer sage;
    @Value("重庆渝中")
    private String saddress;
    @Value("#{T(java.sql.Date).valueOf('1997-1-1')}")
    private Date sbirthday;
    @Autowired
    private Classes cls;

    @Override
    public String toString() {
        return "Student{" +
                "sid=" + sid +
                ", sname='" + sname + '\'' +
                ", ssex=" + ssex +
                ", sage=" + sage +
                ", saddress='" + saddress + '\'' +
                ", sbirthday=" + sbirthday +
                ", cls=" + cls +
                '}';
    }
}

运行测试类代码,可以看到Student类的Classes类型属性cls已被自动装配了。

使用自动装配@Autowired是有歧义的。按照spring的建议,大部分情况下会使用面向接口编程,但一个接口可能会有多个实现类。多个实现类在注册到spring容器时,就出现了类型相同,而id值不相同的情况,此时通过自动装配为成员变量赋值,会出现多个类型相同的bean,此时spring不知如何选择,会报异常。

同理,抽象类的子类也可能有多个,此时自动装配时如果使用了父类类型,会出现相同的问题。

需求:使用Classes类的子类JavaClasses或UiClasses实现Student类cls属性的自动装配。

步骤1:修改班级类Classes.java

public abstract class Classes {
    public Integer cid;
    public String cname;
    public Integer studentNumber;
    public Date openingDate;

    @Override
    public String toString() {
        return "Classes{" +
                "cid=" + cid +
                ", cname='" + cname + '\'' +
                ", studentNumber=" + studentNumber +
                ", openingDate=" + openingDate +
                '}';
    }
}

步骤2:创建Classes类的子类

JavaClasses.java

@Component
public class JavaClasses extends Classes {
    @Value("1")
    public Integer cid;
    @Value("java4")
    public String cname;
    @Value("28")
    public Integer studentNumber;
    @Value("#{T(java.sql.Date).valueOf('2019-8-4')}")
    public Date openingDate;
    
    /*toString*/
}

UiClasses.java

@Component
public class UiClasses extends Classes {
    @Value("2")
    public Integer cid;
    @Value("ui3")
    public String cname;
    @Value("32")
    public Integer studentNumber;
    @Value("#{T(java.sql.Date).valueOf('2019-8-10')}")
    public Date openingDate;
    
    /*toString*/
}

此时直接运行测试类,会报异常No qualifying bean of type 'com.wsjy.domain.Classes' available: expected single matching bean but found 2: javaClasses,uiClasses,其含义是预期只会找到1个匹配,但实际找到了2个匹配,spring不知道如何处理,抛出异常。

要解决自动装配@Autowired产生的歧义,有以下两种方式:@Primary,@Qualifier。

@Primary:该注解代表首要的,它会告诉spring,如果spring容器中相同类型的bean出现了多个时,优先使用用哪个bean进入注入。

修改JavaClasses.java

@Component
//标注优先注入的bean
@Primary
public class JavaClasses extends Classes {
	......
}

运行测试类,会发现JavaClasses被注入。

@Qualifier:该注解会在出现类型相同的bean时,告诉spring,应该注入由@Qualifier指定名称的bean;

修改Student.java

@Component
public class Student implements Serializable {
    ......
    @Autowired
    //@Qualifier的value属性指定的是bean在spring容器中注册的id值
    @Qualifier("uiClasses")
    private Classes cls;
	......
}

运行测试类,会发现UiClasses被注入。

注:同时出现@Primary,@Qualifier时,以@Qualifier指定的为准。而且实际开发过程中,一般不会使用@Primary,更多会使用@Qualifier。因为@Primary只能解决优先级问题,无法解决选择性的问题。

@Resource注解代替@Autowired和@Qualifier。

修改Student.java

@Component
public class Student implements Serializable {
    ......
    //@Resource的name属性指向bean在spring容器中注册的id值
    @Resource(name="javaClasses")
    private Classes cls;
	......
}

运行测试类,会发现JavaClasses被注入。

注:@Resource依赖于javax.annotation-api-1.2.jar,若项目中没有导入对应依赖,无法使用。

@Value、@Autowired、@Qualifier还可以用装配带参构造方法。

修改Student.java

@Component
public class Student implements Serializable {

    private Integer sid;
    private String sname;
    private Integer ssex;
    private Integer sage;
    private String saddress;
    private Date sbirthday;
    private Classes cls;

    public Student(@Value("10") Integer sid,@Value("张三丰") String sname,
                   @Value("1") Integer ssex, @Value("120") Integer sage,
                   @Value("武当山") String saddress, @Value("#{T(java.sql.Date).valueOf(\"1600-5-5\")}") Date sbirthday,
                   @Qualifier("uiClasses") Classes cls) {
        this.sid = sid;
        this.sname = sname;
        this.ssex = ssex;
        this.sage = sage;
        this.saddress = saddress;
        this.sbirthday = sbirthday;
        this.cls = cls;
    }
	/*toString*/
}

运行测试类,Student类对象相关属性的值被输出。

注:装配构造方法参数时,@Qualifier无需与@Autowired配合即可使用(如果装配到成员变量,@Qualifier必须与@Autowired配合使用)。
如果只使用@Autowired,程序装配失败,因为有多个同类型的bean存在。如果没有使用@Primary指定会优先装配的bean时,应使用@Qualifier选择性的进行装配。

复杂类型(集合类型)

使用注解无法注入复杂类型,只能通过XML才能实现。

控制作用域

spring通过@Scope注解来控制bean的作用域,该注解的value属性用于指定作用域。

修改Student.java

@Component
@Scope("prototype")
public class Student implements Serializable {
   ......
}

生命周期相关

与bean的生命周期相关的两个注解:@PostConstruct,@PreDestroy。@PostConstruct对应于<bean>标签的init-method属性,@PreDestroy对应于<bean>标签的destroy-method属性。

注:@PostConstruct,@PreDestroy依赖于javax.annotation-api-1.2.jar,若项目中没有导入对应依赖,无法使用。

修改Subject.java

@Component
public class Subject implements Serializable {
    ......
    
    @PostConstruct
    public void init(){
        System.out.println("subject对象被创建了。。。");
    }
    @PreDestroy
    public void destroy(){
        System.out.println("subject对象被销毁了。。。");
    }
    
    ......
}

使用注解替换核心配置文件

spring提供了自定义配置类来替换spring核心配置文件的功能。

需求:使用注解实现自定义配置类替换spring核心配置文件。

spring核心配置文件

<?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">
    <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName" value="java:comp/env/mydb"/>
    </bean>
</beans>

步骤1:在项目中导入commons-dbcp-1.4.jar和commons-pool-1.5.4.jar

步骤2:创建自定义配置类src/config/ApplicationConfig.java

@Configuration
@ComponentScan(value = {"com.wsjy"})
//@ComponentScan(basePackages = {"com.wsjy"},basePackageClasses = {SubjectFactory.class})
public class ApplicationConfig {
    //获取DBCP数据源,要使用该技术,需要在项目导入commons-dbcp和commons-pool依赖。
    @Bean(name="dataSource")
    public DataSource getDataSource(){
        Properties p=new Properties();
        p.setProperty("driver","com.mysql.jdbc.Driver");
        p.setProperty("url","jdbc:mysql://localhost:3306/taotao");
        p.setProperty("username","root");
        p.setProperty("password","root");
        DataSource ds=null;
        try {
            ds= BasicDataSourceFactory.createDataSource(p);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ds;
    }
}

@Configuration注解的类会被spring认为是一个配置类,相当于创建了一个核心配置文件。

@ComponentScan表示进行扫描,有两个配置项:basePackages\basePackageClasses。

  • 当不写任何配置项时,默认扫描被注解的ApplicationConfig所在包中的所有被@Component注解的类;
  • 当配置basePackages时,basePackages属性取值为一个包的完全限定名数组,表示spring容器会把数组中存在的包下所有被@Component注解的类装配成bean;
  • 当配置basePackageClasses时,basePackageClasses属性取值为一个类或者接口的完全限定名数组,表示spring容器会把数组中存在的类或者接口的实现类装配成bean;

basePackages和basePackageClasses可以同时存在,spring会进行区分,不会重复配置生成多个对象。basePackages等同于value,查看源码可知,是通过别名来实现的。

@Bean注解用于将某个方法返回的对象注册到spring容器中,一般用于第三方类库的类(比如数据库连接池等)。该注解有两个等效的配置项:value和name,是一个String类型的数组,其值用于设定注册到spring容器的bean名称,如果不设,默认将当前方法名作为注册到spring容器中的bean的id。

步骤3:修改测试类

public class Test {
	public static void main(String[] args) {
     //1、通过反射生成spring核心容器
    	ApplicationContext ac = new AnnotationConfigApplicationContext(ApplicationConfig.class);
     //2、根据bean标签id获取spring核心容器管理的bean实例
    DataSource ds = ac.getBean("dataSource", DataSource.class);
        try {
            Connection conn = ds.getConnection();
            PreparedStatement ps = conn.prepareStatement("select * from t_subject");
            ResultSet rs = ps.executeQuery();
            while (rs.next()) {
                System.out.print(rs.getInt("sub_no")+"\t");
                System.out.println(rs.getString("sub_name"));
            }
            rs.close();
            ps.close();
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

使用注解实现的自定义配置类代替了核心配置文件,因此获取spring容器的方式也需进行变化,需要使用AnnotationConfigApplicationContext对象来获取spring容器,其构造方法是一个可变长度的参数数组。

另外,@Configuration可以省略,如果不配,则必须将自定义配置类作为参数传递给AnnotationConfigApplicationContext的构造方法,否则spring不认为该类为配置类,就不会去扫描类中的注解。

项目中可以存在多个配置文件,比如在项目的主配置文件中存放公共配置,某些具体的配置(比如说获取数据库连接)则会单独创建一个配置文件。非主配置文件上一般都会使用@Configuration注解,因为在使用AnnotationConfigApplicationContext获取容器时使用参数过多,会造成代码阅读困难,且容易遗漏。

存在多个配置文件时,也可以使用@Import注解,将非主配置类导入主配置类。@Import注解只有一个value属性可以配置,是一个Class类型的数组,用于指定其他配置类的字节码。

需求:创建多个配置文件,使用@Import将对应配置导入主配置类。

步骤1:创建配置类src/config/JdbcConfig.java,将ApplicationConfig中获取数据源的代码转移到本类中。

@Configuration
public class JdbcConfig {
    @Bean(name="dataSource")
    public DataSource getDataSource(){
        Properties p=new Properties();
        p.setProperty("driver","com.mysql.jdbc.Driver");
        p.setProperty("url","jdbc:mysql://localhost:3306/taotao");
        p.setProperty("username","root");
        p.setProperty("password","root");
        DataSource ds=null;
        try {
            ds= BasicDataSourceFactory.createDataSource(p);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ds;
    }
}

步骤2:修改配置类src/config/ApplicationConfig.java

@Configuration
@ComponentScan(value = {"com.wsjy","config"})
@Import(JdbcConfig.class)
public class ApplicationConfig {

}

运行测试类,可以正常运行。

上例中的JdbcConfig类中通过硬编码,将获取数据源的jdbc属性写在源码中,不合理。一般会使用软编码,即用属性文件保存jdbc连接所需要的值,可以使用@PropertySource注解读取属性文件获取对应的值。

需求:使用Properties文件保存jdbc连接属性。

步骤1:创建属性文件src/jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/taotao
jdbc.username=root
jdbc.password=root

步骤2:修改src/config/JdbcConfig.java

@Configuration
//使用@PropertySource读取属性文件,值为属性文件路径加名称
//classpath为关键字,表明属性文件在类路径下
//ignoreResourceNotFound用于设置当属性文件未找到时使用的策略,
//值为true时,spring没找到属性文件会忽略,
//值为false时,spring没找到属性文件会抛出异常
@PropertySource("classpath:jdbc.properties",ignoreResourceNotFound = true)
public class JdbcConfig {
    //使用@Value注解通过spring的EL表达式获取属性文件中的值
    //表达式为属性文件中的key值
    //注意:读取属性文件使用$,而使用SpEL则使用的是#
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean(name="dataSource")
    public DataSource getDataSource(){
        Properties p=new Properties();
        p.setProperty("driver",driver);
        p.setProperty("url",url);
        p.setProperty("username",username);
        p.setProperty("password",password);
        DataSource ds=null;
        try {
            ds= BasicDataSourceFactory.createDataSource(p);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ds;
    }
}

装配的混合使用

对bean的装配,使用XML和注解都可实现。在实际开发过程中,应如何选择?

从注解角度出发来说,非第三方类(即自己开发的类),使用注解能够极大简化程序开发,而一旦使用了第三方类,注解开发其实并没有带来方便和快捷,反而出现了层次不明,类与类关系不清晰的问题。


学习笔记,整理不易。你的支持,我的动力!

更多学习资料,IT系列课程,请关注vx公众号:豆萌萌 网课大咖

为您提供全网最全的学习资料

更有面试题整理,金三银四冲刺,IT电子书籍等

你需要的,我恰好有,愿意推荐给你哦!


下一章的内容,是关于AOP以及声明式事务。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值