Spring核心概念

初始Spring

在学习Spring之前让我们先了解一下企业级应用.企业级应用是指那些为商业组织,大型企业而创建并部署的解决方案及应用,这些大型企业级应用的结构复杂,涉及的外部资源众多,事务密集,数据规模大,用户数量多.有较强的安全性考虑和较高的性能要求,而Spring就是用来做解决这些问题的

Spring是一个轻量级的框架,是当前的主流框架,它能使现有技术更加易用,推进编码最佳实践

Spring坚持一个原则:不重新发名轮子 Spring由大约20个模块组成。
分成六个部分:Core Container、DataAccess/Integration、Web、AOP、Instrumentation、Test。
Spring Core是框架的最基础的部分,提供了IOC特性
Spring Context为企业级开发提供了便利和集成的工具
Spring AOP是基于Spring Core的符合规范的面向切面编程的实现。
Spring JDBC提供了JDBC的抽象层,简化了JDBC编码
Spring ORM对市面上流行的ORM框架提供了支持
Spring Web 为Spring在Web应用程序中的使用提供了支持

Spring IOC

理解控制反转
控制反转(Inversion of Control,IoC),也称为依赖注入(Dependency Injection,DI),是面向对象编程中的一种设计理念,用来降低代码之间的耦合度
首先考虑什么是依赖,例如:在A类方法中,实例化了B类的对象并调用其方法以完成特定的功能,我们就
说A类依赖于B类,下面我们通过示例理解一下.

定义一个接口

public interface UserDao{
    /**
    *保存用户信息的方法
    */
    public void save(User user);
}

接口的实现类

    pulbic class UserDaoImpl implements UserDao{
        public void save(User user){
            System.out.print("保存用户信息到数据库");
        }
    }

用户的业务类

public class UserServiceImpl implements UserService{
    //实例化所依赖的UserDao对象
    private UserDao dao=new UserDapImpl();
    //调用UserDao的方法保存用户信息
    dao.save(User);
}

以上代码所示,UserServiceImpl对UserDaoImpl存在依赖关系,这样的代码很常见,但是存在一个严重的问题,就是这个两个类是高度耦合,如果我们有新的需求需要替换UserDao的实现类,导致UserServiceImpl里面的代码也要随之发生改变,这种代码不具有优良的可扩展性和可维护性,甚至在开发中难以测试

我们可以利用简单的工厂和工厂模式的思路解决此类问题

public class UserDaoFactory{
    public static UserDao getInstance(){
        //具体实现过程省略
    }
}
public class UserServiceImpl implements UserService{
    //通过工厂获取所依赖的用户DAO对象
    private UserDao dao=UserDaoFactory.getInstance();
    public void addNewUser(User user){
        //调用用户DAO的方法保存用户信息
        dao.save(user);
    }
}

这里的用户DAO工厂类UserDaoFactory就体现了"控制反转的思想":UserServiceImpl不再依赖自身的代码去获得所依赖的具体DAO对象,而是把这一工作转交给了第三方--UserDaoFactory,从而避免了和具体UserDao实现类之间的耦合

下面我们用Spring IOC 来实现

首先我们要下载Spring的jar包
jar包下载地址 我这边是用的版本是4.3.18
解压后的文件几个重要文件夹的意思:
docs:该文件夹下包含Spring的相关文档,包括API参考文档,开发手册
libs:该文件夹下存放Spring各个模块的jar文件,每个模块均提供三项内容,开发所需的jar文件,以"-javadoc"后缀表示的API和以"-sources"后缀表示的源文件
schema:配置Spring的某些功能时需要用到的schema文件,对于已经集成了Spring的IDE环境(如MyEclipse,IDEA),这些文件不需要专门导入

我们将jar导入到项目中

为项目添加log4j.properties文件,用来控制日志输出 文件放到resources里面

# 下面的属性配置中,所有日志的输出级别是info,输出源是con 
log4j.rootLogger=info,error,con
#定义输出源的输出位置是控制台
log4j.appender.con=org.apache.log4j.ConsoleAppender
#定义输出日志的布局采用的类 
log4j.appender.con.layout=org.apache.log4j.PatternLayout
#定义日志输出布局
log4j.appender.con.layout.ConversionPattern=%d{MM-dd HH:mm:ss}{%p}%c%n -%m%n

编写HelloSpring类

package cn.springdemo;

/**
 * 第一个Spring,输出"Hello,Spring!"。
 * 
 * @author 安心
 */
public class HelloSpring {
	// 定义who属性,该属性的值将通过Spring框架进行设置
	private String who = null;
	/**
	 * 定义打印方法,输出一句完整的问候。
	 */
	public void print() {
		System.out.println("Hello," + this.getWho() + "!");
	}
	/**
	 * 获得 who。
	 * 
	 * @return who
	 */
	public String getWho() {
		return who;
	}
	/**
	 * 设置 who。
	 * 
	 * @param who
	 */
	public void setWho(String who) {
		this.who = who;
	}
}

在resources下面创建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
	http://www.springframework.org/schema/beans/spring-beans.xsd">
	<!-- 通过bean元素声明需要Spring创建的实例。该实例的类型通过class属性指定,并通过id属性为该实例指定一个名称,以便在程序中使用 -->
	<bean id="helloSpring" class="cn.springdemo.HelloSpring">
		<!-- property元素用来为实例的属性赋值,此处实际是调用setWho()方法实现赋值操作 -->
		<property name="who">
			<!-- 此处将字符串"Spring"赋值给who属性 -->
			<value>Spring</value>
		</property>
	</bean>
</beans>

在Spring配置文件中,使用元素来定义Bean(也称为组件)的实例,这个元素有两个常用属性:一个是id,表示定义的Bean实例的名称,另一个是class,表示定义的Bean实例的类型
经验:
(1) 使用元素定义一个组件时,通常需要使用id属性为其指定一个用来访问的唯一的名称。如果想为Bean指定更多的别名,可以通过name属性指定,名称之间使用逗号、分号或空格进行分隔
(2) 在本例中,Spring为Bean的属性赋值是通过调用属性的setter方法实现的,这种做法称为“设值注入”,而非直接为属性赋值。若属性名为who,但是setter方法名为
setSomebody(),Spring配置文件中应写成name="somebody"而非name="who"。所以在为属性和setter访问器命名时,一定要注意遵循JavaBean的命名规范。

编写测试方法

 public void helloSpring() {
        // 通过ClassPathXmlApplicationContext实例化Spring的上下文
        ApplicationContext context = new ClassPathXmlApplicationContext(
                "applicationContext.xml");
        // 通过ApplicationContext的getBean()方法,根据id来获取bean的实例
        HelloSpring helloSpring = (HelloSpring) context.getBean("helloSpring");
        // 执行print()方法
        helloSpring.print();
    }

测试代码中的ApplicationContext是一个接口,负责读取Spring配置文件,管理对象的加载,生成,维护Bean对象与Bean对象之间的依赖关系,负责Bean的生命周期等,ClassPathXmlApplicationContext是ApplicationContext接口的实现类,用于从classpath路径中读取Spring配置文件

Spring AOP

理解面向切面
面向切面编程(Aspect Oriented Programmin ,AOP)是软件编程发展到一定阶段的产物,AOP一般适用于具有横切逻辑的场合,如访问控制、事务管理、性能检测等。
面向切面编程,简单的说就是在不改变原程序设计的基础上位代码段增加新的功能,对代码段进行增强处理。设计思想来源于代理设计模式。

直接调用对象的方法

在代理模式中可以为该对象设置一个代理对象,代理对象为fun()提供一个代理方法,当通过代理对象的fun()方法调用原方法的fun()方法时,就可以在代理方法中添加新的功能,也就是所谓的增强处理。
通过代理对象调用方法

AOP相关术语
增强处理(Advice)
前置增强
后置增强
环绕增强、异常抛出增强、最终增强等类型
1、横切关注点
对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
2、切面(aspect)
类是对物体特征的抽象,切面就是对横切关注点的抽象
3、连接点(joinpoint)
被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
4、切入点(pointcut)
对连接点进行拦截的定义
5、通知(advice)
所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
6、目标对象
代理的目标对象
7、织入(weave)
将切面应用到目标对象并导致代理对象创建的过程
8、引入(introduction)
在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段

下面我们写一个使用Spring AOP实现日志输出也就是我们的log4j
1.我们先在项目中加入jar包
spring-aop 这个jar包 这个jar包给我们提供了AOP的实现,同时,Spring AOP还依赖AOP Alliance(aopalliance-1.0.jar)和AspectJ(aspectjweaver-1.6.9.jar)项目中的组件

接下来编写业务类UserServiceImpl


/**
 * 用户业务类,实现对User功能的业务管理
 */
public class UserServiceImpl implements UserService {

	// 声明接口类型的引用,和具体实现类解耦合
	private UserDao dao;

	// dao 属性的setter访问器,会被Spring调用,实现设值注入
	public void setDao(UserDao dao) {
		this.dao = dao;
	}

	public void addNewUser(User user) {
		// 调用用户DAO的方法保存用户信息
		dao.save(user);
	}
}

该项目中有一个addNewUser()方法,实现添加用户的业务,接下来就以AOP的方式为该方法添加日志功能
编写增强类

public class UserServiceLogger {
	private static final Logger log = Logger.getLogger(UserServiceLogger.class);

	public void before(JoinPoint jp) {
	    log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().getName()
	            + " 方法。方法入参:" + Arrays.toString(jp.getArgs()));
	}
	
	public void afterReturning(JoinPoint jp, Object result) {
	    log.info("调用 " + jp.getTarget() + " 的 " + jp.getSignature().getName()
	            + " 方法。方法返回值:" + result);
	}
}

before()方法也就是在我们代码执行之前运行,afterReturning()在我们执行代码之后运行.Join Point类型的参数,Spring会自动注入该实例,通过getTarget()方法可以得到被代理的目标对象,getSignature()方法返回被代理的目标方法,getArgs()方法返回传递给目标方法的参数数组

在Spring配置文件中对相关组件进行声明

<bean id="dao" class="dao.impl.UserDaoImpl"> </bean>
<bean id="service" 
class="service.impl.UserServiceImpl">
    <property name="userDao" ref="dao"></property>
</bean>
<bean id="theLogger" class="aop.UserServiceLogger"></bean>

接下来在Spring配置文件中进行AOP相关的配置,首先定义切入点
定义切入点

<?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
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="dao" class="com.jikedaquan.spring.dao.impl.UserDaoImpl"> </bean>
    <bean id="service" class="service.impl.UserServiceImpl">
        <property name="userDao" ref="dao"></property>
    </bean>
    <bean id="theLogger" class="aop.UserServiceLogger"></bean>
    <aop:config>
        <!--定义一个切入点表达式,并命名为“pointcut”-->
        <aop:pointcut id="pointcut" 
        expression="execution(public void addNewUser(entity.User))"/> 
    </aop:config>
</beans>

与AOP相关的配置都放在aop:config标签中,如配置切入点的标签aop:pointcut
execution是切入点指示符,切入点表达式支持模糊匹配

  • public * addNewUser(entity.User):* 表示匹配所有类型的返回值
  • public void * (entity.User):* 表示匹配所有方法名。
  • public void addNewUser(..):.. 表示匹配所有参数个数和类型
    • com.service.* .*(..):匹配com.service包下所有类的所有方法
    • com.service..* .(..):匹配com.service包及其子包下所有类的所有方法 (前面句号就是 不知道为啥 *显示不出来 应该是Markdown的某个命令符号把)

大家可以根据自己的需求来设置切入点的匹配规则,当然配置的关键字还有很多,我就不一一介绍了,如果有兴趣可以查看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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="dao" class="com.jikedaquan.spring.dao.impl.UserDaoImpl"> </bean>
    <bean id="service" class="service.impl.UserServiceImpl">
        <property name="userDao" ref="dao"></property>
    </bean>
    <bean id="theLogger" class="aop.UserServiceLogger"></bean> 
    <aop:config>
        <!--定义一个切入点表达式,并命名为“pointcut”-->
        <aop:pointcut id="pointcut" expression="execution(public void addNewUser(entity.User))"/>
        <!--引用包含增强方法的Bean-->
        <aop:aspect ref="theLogger">
            <!--将before()方法定义为前置增强并引用pointcut切入点-->
            <aop:before method="before" pointcut-ref="pointcut"></aop:before>
            <!--将afterReturning()方法定义为后置增强并引用pointcut切入点-->
            <aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"></aop:after-returning>
        </aop:aspect>  
       </aop:config>
</beans>

编写测试

ApplicationContext context=new 
ClassPathXmlApplicationContext("applicationContext.xml");
UserService service= (UserService) context.getBean("service");
User user =new User();
service.addNewUser(null);

下一篇文章还有很详细的讲解IOC和AOP一些功能,不足的地方欢迎大家提出意见
by安心

posted @ 2018-11-09 14:56  安心。  阅读(658)  评论(0编辑  收藏  举报