Spring是一个分层的java SE/EE 全站轻量级框架,它以IoC(Inversion of Control 控制反转)和AOP(Aspect Oriented Programming面向切面编程)为内核,使用基本的javaBean来完成以前只能由EJB(Enterprise Java Beans,Java企业Bean)完成的工作。
Spring 开发所需的Jar包分为两部分,具体如下。这里以Spring 4.3.6为例
下载地址:http://repo.spring.io/simple/libs-release-local/org/springframework/spring/4.3.6.RELEASE/
文件名称:spring-framework-4.3.6.RELEASE-dist
在libs目录中,有四个Spring的基础包,分别对应Spring核心容器的四个模块:
Spring核心容器还需要依赖commons.logging的jar包,地址:http://commons.apache.org/proper/commons-loggingdownload_logging.cgi
Spring框架主要功能是通过其核心容器来实现,Spring框架提供了两种核心容器:BeanFactory和ApplicationContext。
BeanFactory 由 org.springframework.beans.facytory.BeanFactory 接口定义,是基础类型的 loC 容器.BeanFactory是一个管理Bean工厂,主要负责初始化各种Bean和调用它们的生命周期方法。
BeanFactory提供的几个实现类中,最长常用的是org.springframework.beansfactory.xml.XmIBeanFactory,该类会根据xml的配置文件中的定义来装配Bean.
BeanFactory beansFactory=new XmlBeanFactory(new FileSystemResource("F:/applicationContext.xml"));
ApplicationContext是BeanFactory的子接口,是另一种Spring核心容器。它由 org.springframework.context.ApplicationContext 接口定义,不仅包含了BeanFactory 的所有功能,还添加了对国际化、资源访问、事件传播等方面的支持。
创建ApplicationContext接口实例,通常有2中方法:
String xmlPath= "applicationContext.xml";
ClassPathXmlApplicationContext applicationContext =new ClassPathXmlApplicationContext(xmlPath);
ApplicationContext applicationContext=new FileSystemXmlApplicationContext(String configLocation);
与 ClassPathXmlApplicationContext 有所不同的是,FileSystemXmlApplicationContext 不再从类路径 中读取配置文件,而是通过参数指定配置文件的位置,例如 “0:/workspaces/appIicationContext.xm 1”。
<!… 指定 Spring 配置文件的位置,多个配置文件时,以逗号分隔一〉
>
<param-name>contextConfigLocationparam-name>
<!一 Spring 将加载 spring 目录下的 applicationContext .xml 文件一 〉
>
classpath:spring/applicationContext.xml
param-value>
<context-param>
<!一指定以 ContextLoaderListener 方式启动 Spring 容器 一〉
>
<listener-class>
org.springframework.web.context.ContextLoaderListener
listener-class>
listener>
创建 Spring 容器后,就可以获取 Spring 容器中的 Bean。Spring 获取 Bean 的实例通常采用以下两种方法。
public interface UserDao {
public void say();
}
public class UserDaoImpl implements UserDao{
@Override
public void say() {
System.out.println("userDao say hello World!");
}
}
<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-4.3.xsd">
<bean id="userDao" class="cn.edu.sicau.ioc.UserDaoImpl"/>
beans>
@Test
public void funcIoc1(){
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("cn/edu/sicau/ioc/applicationContext.xml");
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.say();
}
依赖注入和反向控制的含义相同,是从两个不同角度描述同一个概念。
当一个Java对象(调用者)需要调用另一个Java对象(被调用者)时,在传统模式下,调用者通常采用“new 被调用者”的代码来创建对象,
流程图语法(https://mermaidjs.github.io/flowchart.html)
使用Spring框架后,对象的实例不再由调用者创建,而是由Spring容器来创建。控制权由应用程序转移到了Spring容器,控制权发生了反转,这就是Spring的控制反转。
从Spring容器的角度来看,Spring容器负责将被依赖对象赋值给调用者的成员变量,这相当于为调用者注入了它的依赖实例,这就是Spring的依赖注入。
IoC容器使用setter方法注入被依赖的实例
public interface UserService {
public void say();
}
public class UserServiceImpl implements UserService{
private UserDao UserDao;
//setter方法
public void setUserDao(UserDao UserDao) {
this.UserDao = UserDao;
}
@Override
public void say() {
this.UserDao.say();
System.out.println("UserService say Hello World!");
}
}
<bean id="userService" class="cn.edu.sicau.ioc.UserServiceImpl">
<property name="userDao" ref="userDao"/>
bean>
@Test
public void funcIoc2(){
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("cn/edu/sicau/ioc/applicationContext.xml");
UserService userService = (UserService) applicationContext.getBean("userService");
userService.say();
}
IoC使用构造方法注入被依赖的实例
对应的在配置文件中
<constructor-arg name="UserDao" ref="userDao"/>
Spring容器支持XML和Properties两种格式的配置文件,其中xml配置最常用。XML配置文件中根元素是,包含多个子元素,每个子元素定义了一个Bean。的常用属性及子元素如下:
[外链图片转存失败(img-eIgcoQkR-1564703784760)(assets/spring-425e31a0.png)]
实例化Bean有三种方式,分别:构造器实例化、静态工厂实例化、实例工厂实例化。
构造器实例化指通过Bean对应类中的默认无参构造函数方法来实例化Bean。
案例
public class Bean1(){
}
<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-4.3.xsd">
<bean id="bean1" class="cn.edu.sicau.ioc.Bean1"/>
beans>
这里测试类就省略
通过创建一个静态工厂的方法来创建Bean实例,其中Bean配置的class属性不再是Bean实例的实现类,而是静态工厂类,同时使用factory-method属性来指定所创建的静态工厂方法。
public class MyStaticFactory {
public static Bean2 createBean(){
return new Bean2();
}
}
<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-4.3.xsd">
<bean id="bean2" class="cn.edu.sicau.instance.static_factory.MyStaticFactory"
factory-method="createBean"/>
beans>
@Test
public void func(){
ClassPathXmlApplicationContext applicationContext=new
ClassPathXmlApplicationContext("cn/edu/sicau/instance/static_factory/Bean2.xml");
Bean2 bean2 = (Bean2) applicationContext.getBean("bean2");
System.out.println(bean2);
}
在配置文件中实例化Bean,不使用class属性直接指向实例化类,而是通过factory-bean属性指向配置的实例工厂,然后使用factory-method属性确定使用工厂中的那个方法。
实例
public class Factory {
//创建Bean3实例的方法
public Bean3 createBean(){
return new Bean3();
}
}
<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-4.3.xsd">
<bean id="myFactory" class="cn.edu.sicau.instance.factory.Factory"/>
<bean id="bean3" factory-bean="myFactory" factory-method="createBean"/>
beans>
Spring容器常用三种Bean的装配方式,如基于XML的装配、基于注解装配、自动装配(其中基于注解最常用)。
Spring提供了2种XML的装配方式:设值注入(Setter Injection)和构造注入(Constructor Injection).
在实例化Bean的过程中,Spring首先会调用Bean的默认构造函数实例化Bean对象,然后通过反射的方式调用setter方法来注入属性值。所以设值注入要求一个Bean必须满足一下两点:
实例:两种方式同时演示
public class User {
private String username;
private Integer password;
private List list;
/*
无参构造函数
设值注入
*/
public User(){
super();
}
/*
有参构造函数
构造注入
*/
public User(String username, Integer password, List list) {
this.username = username;
this.password = password;
this.list = list;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(Integer password) {
this.password = password;
}
public void setList(List list) {
this.list = list;
}
}
<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-4.3.xsd">
<bean id="user1" class="cn.edu.sicau.ioc.User">
<constructor-arg index="0" value="张三"/>
<constructor-arg index="1" value="123"/>
<constructor-arg index="2">
<list>
<value>"constructor1"value>
<value>"constructor2"value>
<value>"constructor3"value>
list>
constructor-arg>
bean>
<bean id="user2" class="cn.edu.sicau.ioc.User">
<property name="username" value="李四"/>
<property name="password" value="456"/>
<property name="list" >
<list>
<value>"setListValue1"value>
<value>"setListValue2"value>
list>
property>
bean>
beans>
@Test
public void funcIoc3(){
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext("cn/edu/sicau/ioc/UserBean.xml");
User user1 = (User) applicationContext.getBean("user1");
User user2 = (User) applicationContext.getBean("user2");
System.out.println(user1);
System.out.println(user2);
}
常用注解
实例
Spring 4.0 以上版本使用上面的代码对指定包中的注解进行扫描前,需要先向项目中导入 Spring AOP 的 JAR 包 spring-aop-4.3.6.RELEASE.jar,否则程序在运行时会报出 “java.lang.NoClassDefFound Error: orglspringframeworklaop/TargetSource” 错误。
public interface UserDao {
void save();
}
@Repository("userDao")
public class UserDaoImpl implements UserDao{
@Override
public void save() {
System.out.println("userDao ……save……");
}
}
使用 @Repository 注解将 UserDaolmpl 类标识为 Spring 中的 Bean,其写法相当于配置文件中 的编写
public interface UserService {
void save();
}
//标识Bean
@Service("userService")
public class UserServiceImpl implements UserService{
//这相当于配置文件中 的写法
@Resource(name = "userDao")
private UserDao userDao;
@Override
public void save() {
userDao.save();
System.out.println("userService …… save ……");
}
}
//标识Bean
@Controller("userController")
public class UserController {
//
<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
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:component-scan base-package="cn.edu.sicau.annotation"/>
beans>
配置文件中注释的部分和
@Test
public void func(){
ClassPathXmlApplicationContext applicationContext=new
ClassPathXmlApplicationContext("cn/edu/sicau/annotation/bean4.xml");
UserController userController = (UserController) applicationContext.getBean("userController");
userController.save();
}
Spring 的元素中包含一个autowire属性,我们可以通过设置autowire 的属性值来自动装配Bean。所谓自动装配,就是将一个 Bean 自动地注入到其他 Bean的Property中。autowire属性有5个值,如下:
[外链图片转存失败(img-sQlDgBJD-1564703784765)(assets/spring-f2a9bd76.png)]
实例
<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
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<bean id="userDao" class="cn.edu.sicau.annotation.UserDaoImpl"/>
<bean id="userService" class="cn.edu.sicau.annotation.UserServiceImpl" autowire="byName"/>
<bean id="userController" class="cn.edu.sicau.annotation.UserController" autowire="byName"/>
beans>
AOP采用横向抽取机制,将分散在各个方法中的重复代码抽取出来,在编译或运行时,再将这些代码应用到需要执行的地方。这里抽取出来的功能代码可以理解是一个个切面,如图:
[外链图片转存失败(img-7dtv2tIP-1564703784767)(assets/spring-6249cd84.png)]
AOP术语
[外链图片转存失败(img-YFtx3lcg-1564703784769)(assets/spring-bc3a56e9.png)]
AOP中的代理就是由AOP框架动态生成一个对象,该对象可以作为目标对象使用。Spring中的动态代理,可以是JDK动态代理,也可以是CGLIB代理。
JDK 动态代理是通过 java.lang. reflect. Proxy 类来实现的,我们可以调用 Proxy 类的newProxylnstanceO方法来创建代理对象。
实例
public interface UserDao {
void addUser();
void deleteUser();
}
public class UserDaoImpl implements UserDao{
@Override
public void addUser() {
System.out.println("增加用户");
}
@Override
public void deleteUser() {
System.out.println("删除用户");
}
}
public class MyAspect {
//前置增强方法
public void check_permission(){
System.out.println("检查权限");
}
//后置增强方法
public void log(){
System.out.println("打印日志");
}
}
public class JdkProxy implements InvocationHandler {
//申明目标接口类
private UserDao userDao;
public Object createProxy(UserDao userDao){
this.userDao=userDao;
//1. 类加载器
ClassLoader classLoader = JdkProxy.class.getClassLoader();
//2. 被代理对象实现的所有接口
Class[] clazz = userDao.getClass().getInterfaces();
//3. 使用代理类进行增强
return Proxy.newProxyInstance(classLoader,clazz,this);
}
/**
* 所有目标代理类的方法调用,都会交由invoke来处理
* @param proxy 被代理后的对象
* @param method 将要被执行的方法信息
* @param args 执行方法时需要的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//声明切面
MyAspect myAspect=new MyAspect();
//前置增强
myAspect.check_permission();
//在目标类上调用方法,并传入参数
Object invoke = method.invoke(userDao, args);
//后置增强
myAspect.log();
return invoke;
}
}
@Test
public void func(){
//创建代理对象
JdkProxy jdkProxy=new JdkProxy();
//创建目标对象
UserDao userDao=new UserDaoImpl();
//从代理对象中获取增强后的目标对象
userDao= (UserDao) jdkProxy.createProxy(userDao);
userDao.addUser();
userDao.deleteUser();
}
测试结果
[外链图片转存失败(img-j7DfO9Zq-1564703784772)(assets/spring-99a83983.png)]
和JDK动态代理比较,JDK动态代理有一定的局限性(动态代理的对象必须实现一个或多个接口),如果对没有实现接口的类进行代理,可以使用CGLIB代理。
实例
public class UserDao {
public void addUser() {
System.out.println("增加用户");
}
public void deleteUser() {
System.out.println("删除用户");
}
}
public class CGLibProxy implements MethodInterceptor {
//代理方法
public Object createProxy(Object target){
//创建动态增强类对象
Enhancer enhancer=new Enhancer();
//设置代理对象的父类
enhancer.setSuperclass(target.getClass());
//增加回调函数
enhancer.setCallback(this);
//返回创建的代理类
return enhancer.create();
}
/** proxy CGlib 根据指定父类生成的代理对象
* method 拦截的方法
* args 拦截方法的参数数组
* methodProxy 方法的代理对象,用于执行父类的方法
*/
@Override
public Object intercept(Object o, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
//创建界面对象
MyAspect myAspect=new MyAspect();
//前置增强
myAspect.check_permission();
//目标方法执行
Object invoke = methodProxy.invokeSuper(o, objects);
//后置增强
myAspect.log();
return invoke;
}
}
测试类省略
ProxyFactoryBean是FactoryBean接口的实现类,FactoryBean负责实例化一个 Bean,而ProxyFactoryBean 负责为其他Bean创建代理实例。在Spring 中,使用ProxyFactoryBean是创建 AOP 代理的基本方式 。
ProxyFactoryBean常用可配置属性,如下图:
[外链图片转存失败(img-BU5QaX8p-1564703784774)(assets/spring-5a8bd2da.png)]
实例:环绕通知
在核心包的基础上再添加spring-aop-4.3.6.RELEASE.jar 和aopalliance-1.0.jar( http://mvnrepository.com/artifact/aopalliance/aopalliance/1.0" )
public class MyAspect implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
check_permission();
//执行目标方法
Object proceed = methodInvocation.proceed();
log();
return proceed;
}
public void check_permission(){
System.out.println("检查权限");
}
public void log(){
System.out.println("打印日志");
}
}
<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-4.3.xsd">
<bean id="userDao" class="cn.edu.sicau.jdk.UserDaoImpl"/>
<bean id="myAspect" class="cn.edu.sicau.factory.MyAspect"/>
<bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="cn.edu.sicau.jdk.UserDao"/>
<property name="target" ref="userDao"/>
<property name="interceptorNames" value="myAspect"/>
<property name="proxyTargetClass" value="true"/>
bean>
beans>
@Test
public void func(){
String xmlPath="cn/edu/sicau/factory/applicationContext.xml";
ClassPathXmlApplicationContext applicationContext =
new ClassPathXmlApplicationContext(xmlPath);
UserDao userDaoProxy = (UserDao) applicationContext.getBean("userDaoProxy");
userDaoProxy.addUser();
userDaoProxy.deleteUser();
}
Spring的Java配置方式是通过 @Configuration 和 @Bean 这两个注解实现的: