Spring总结
Spring介绍
Spring是一个开放源代码的的设计层面框架,他解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用。
Spring的特点
- 方便解耦,简化开发
通过Spring提供的IoC容器,我们可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度耦合。 - AOP编程支持
通过Spring提供的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能都可以用AOP轻易的实现。 - 声明式事务的支持
在Spring中,可以从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率以及质量。 - 方便程序测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,在Spring里,测试不再是昂贵的操作,而是随手可做的事情。例如:Spring对Junit4的支持,可以通过注解方便的测试Spring程序。 - 集成各种优秀的框架
Spring可以降低各种框架的使用难度,Spring提供了对各种优秀框架(Struts,Hibernate,Hessian,Quartz)的支持。 - 降低了Java EE API的使用难度
Spring组织架构
Spring Core(核心模块):提供可框架的基本组成部分,包括控制反转(IOC)和依赖注入(DI)等功能,主要作用是对Java中所有的对象进行管理。
AOP:提供了面向切面编程的实现,让你可以自定义拦截器,切点。
JDBC:提供了一个JDBC抽象层,消除了繁琐的JDBC编码和数据库厂商特有的错误代码解析。用于简化JDBC。
Web:提供了针对Web开发的集成特性,例如:文件上传,利用servlet listeners进行ioc容器的初始化和针对Web的ApplicationContext。
Test:主要为测试提供支持
Messaging
总结:Spring总共有大约20个模块,这些组件被分别整合在,核心容器(core container)、AOP(Aspect Oriented Programming)和设备支持(Instrumentation)、数据访问与集成(Data Access/Integration)、Web、消息(Massaging)、Test 这六个模块中。
Spring中的IOC
IOC是 Inverse of Control的简写,意思是控制反转。是降低对象之间耦合关系的设计思想,也就是说将所有的对象统统交给Spring容器进行管理。
DI是 Dependency Injection的缩写,意思是依赖注入,指的是创建对象实例的时候,同时为这个对象注入它的属性。
如何通过IOC对代码进行优化
- 添加Spring依赖包
<!-- Spring的核心工具包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!--在基础IOC功能上提供扩展服务,还提供许多企业级服务的支持,有邮件服务、 任务调度、远程访问、缓存以及多种视图层框架的支持-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!-- Spring IOC的基础实现,包含访问配置文件、创建和管理bean等 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!-- Spring context的扩展支持,用于MVC方面 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!-- Spring表达式语言 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
- 创建一个application.xml配置文件,在配置文件中通bean 标签创建对象:
<!--application.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">
</beans>
<bean id="对象名" class="类的完整路径">
<property name="属性名" ref="对象的id值"></property>
</bean>
- 修改一下实现类,这是service层里的实现类就不再通过new关键字来创建了,而是从容器中通过自己调用的set方法得到这个信息
- 在测试类中加载配置文件。
ApplicationContext app=new ClassPathXmlApplicationContext("application.xml");
Users users=(Users)app.getBean("对象名");
注意:给属性赋值不需要加载配置文件,Spring会自动调用set方法进行赋值。
总结:如果当我们需要修改或者切换一个实现类的时候,此时我们没办法修改Java代码,因为在实际编译过程中Java代码都是字节码文件,是不允许修改的,但是配置文件不管是在编译前还是编译后都是一样的,我们都可以直接修改,所以我们只需要修改class的路径,将其更换为新的实现类就可以实现切换了,这也就是Spring框架实现解耦的效果。
bean标签的属性介绍
属性 | 说明 |
---|---|
class | 指定bean对应类的全路径 |
name | name是bean对应对象的一个标示 |
scope | 执行bean对象创建模式和生命周期 scope="singleton"和 scope=“prototype” |
id | id是bean对象的唯一标识,不能添加特别字符 |
lazy-init | 是否延迟加载,默认值:false。true为延迟加载对象,当对象被调用的时候才会加载。测试的时候通过getbean()方法获取对象。lazy-init="false"默认值,不延迟,无论对象是否被使用,都会立即创建对象,只需要加载配置文件即可。注意:测试的时候只留下id和class属性。 |
init-method | 只需要加载配置文件即可对象初始化方法 |
destroy-method | 对象销毁的方法 |
注意:
Spring中默认的scope是单例模式创建对象(每次不创建新的对象,都使用同一个对象),如果需要修改创建对象的模式则需要将scope改为socpe=“prototype”。
对象创建的方法
(1) 无参构造
(2) 有参构造
public Person(String name , Car car){
this.name = name;
this.car = car;
System.out.println("Person的有参构造方法:"+name+car);
}
<bean name="person" class="com.xzk.spring.bean.Person">
<constructor-arg name="name" value="rose"/>
<constructor-arg name="car" ref="car"/>
</bean>
(3) 静态方法创建对象(静态工厂模式)
首先创建一个静态方法
public class PersonFactory {
public static Person createPerson(){
System.out.println("静态工厂创建Person");
return new Person();
}
}
创建一个Person的对象然后调用createPerson()方法
<bean name="pf" class="com.PersonFactory" factory-method="createPerson" />
(4) 非静态工厂方法
首先同样是先创建一个非静态方法
public class Users{
public Person createPerson1(){
System.out.println("非静态工厂创建Person");
return new Person();
}
}
接下来创建一个对象u2,然后用u2来调用他的非静态方法createPerson1(),它返回的对象是u3。
<bean id="u2" class="com.bean.Users"></bean>
<bean id="u3" factory-bean="u2" factory-method="createPerson1"></bean>
bean的生命周期
- Bean的实例化方式可以通过调取bean的有参构造、无参构造、静态方法、非静态方法来创建。
- 进行属性的赋值,例如:在Users里对name和age的值进行绑定。
- 绑定完成之后,通过调取setBeanName()方法来把bean标签里id的对象名和当前这个对象进行关联。
- 关联完成之后它会把这个对象交给BeanFactory工厂进行管理。
- 管理完成之后将会继续交由ApplicationContext(配置文件的类型其实就是ApplicationContext这个类型)来进行管理,由它来调取getBean的方法来调用对象。
- 调取预初始化方法(实际上是Spring中的另一个概念AOP主要的实现机制),也可以称之为前置增强。
- 调取afterPropertiesSet()方法,为了对对象进行一些配置。
- 配置完成之后就是调取定制的初始化方法。(也就是之前提到的bean标签中init-mathod的属性来指定初始化方法)
- 调用后初始化方法,当后置增强执行完之后,它会判断bean标签中scope的值到底是哪一个来判断是单例还是多例
- 如果是单例(singleton),那么就会交由Spring缓冲池来进行bean的管理,如果是多例(prototype) 就将准备就绪的bean交给调用者
- 销毁,第一种是对象的销毁方法,也就是对象生命周期中的销毁方法destpry(),另外一种就是bean中的标签destroy-method这个属性所指定的方法。
DI注入值
分类:一种是调取属性set方法赋值,第二种是使用构造方法赋值。
set注入值
基本类型值注入
<property name="name" value="jeck" />
引用类型值注入
<property name="car" ref="car"></property>
构造注入
1.通过name属性,按照参数名赋值
public Person(String name , Car car){
this.name = name;
this.car = car;
System.out.println("Person的有参构造方法:"+name+car);
}
<bean name="person" class="com.xzk.spring.bean.Person">
<constructor-arg name="name" value="rose"/>
<constructor-arg name="car" ref="car"/>
</bean>
2.通过index属性,按照参数索引注入
<bean name="person2" class="com.xzk.spring.bean.Person">
<constructor-arg name="name" value="helen" index="0"></constructor-arg>
<constructor-arg name="car" ref="car" index="1"></constructor-arg>
</bean>
3.type注入:type注入指的就是类型的注入
<bean name="person2" class="com.xzk.spring.bean.Person">
<constructor-arg name="name" value="988" type="java.lang.Integer">
</constructor-arg>
注意:一般情况下是不需要写type这种格式的
spel表达式
<bean name="car" class="com.xzk.spring.bean.Car" >
<property name="name" value="mime" />
<property name="color" value="白色"/>
</bean>
<!--利用spel引入car的属性 -->
<bean name="person1" class="com.xzk.spring.bean.Person" p:car-ref="car">
<property name="name" value="#{car.name}"/>
<property name="age" value="#{person.age}"/>
</bean>
p命名空间注入值
使用p:属性名 完成注入,走set方法
- 基本类型值: p:属性名=“值”
- 引用数据类型:p:属性名-ref=“bean名称”
实现步骤:配置文件中添加命名空间
xmlns:p="http://www.springframework.org/schema/p"
实例:
<bean id="u6" class="com.entity.Users" p:age="30" p:name="李四" p:student- ref="stu1"></bean>
复杂类型注入
<!-- 数组变量注入 -->
<property name="arrs">
<list>
<value>数组1</value>
<!--引入其他类型-->
<ref bean="car"/>
</list>
</property>
<!-- 集合变量赋值-->
<property name="list">
<list>
<value>集合1</value>
<!--集合变量内部包含集合-->
<list>
<value>集合中的集合1</value>
<value>集合中的集合2</value>
<value>集合中的集合3</value>
</list>
<ref bean="car" />
</list>
</property>
<!--map赋值 -->
<property name="map">
<map>
<entry key="car" value-ref="car" />
<entry key="name" value="保时捷" />
<entry key="age" value="11"/>
</map>
</property>
<!-- properties赋值 -->
<property name="properties">
<props>
<prop key="name">pro1</prop>
<prop key="age">111</prop>
</props>
</property>
自动注入(由程序自动给属性赋值)
autowire:
no 不自动装配
byName 属性名=id名,调取set方法赋值
byType 属性的类型和id对象的类型相同,当找到多个同类型的对象时报错,调取set方法赋值
constructor 构造方法的参数类型和id对象的参数类型相同,当没有找到的时候报错。调取构造方法赋值。
配置全局自动装配:
<beans default-autowire="constructor/byName/byType/no">
注解实现IOC
实现步骤:
(1) 配置文件中添加约束
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
(2) 配置注解扫描:指定扫描包下所有类中的注解,扫描包时,会扫描包的所有子孙包。
<!--扫描包设置-->
<context:component-scan base-package="com.xzk.spring.bean"></context:component- scan>
(3) 注解
添加在类名上:
@Component("对象名")
@Service("person") // service层
@Controller("person") // controller层
@Repository("person") // dao层
@Scope(scopeName="singleton") //单例对象
@Scope(scopeName="prototype") //多例对象
注意:注解的方式并不需要set方法
添加在属性上:
@Value("属性值")
private String name;
@Autowired //如果一个接口类型,同时有两个实现类,则报错,此时可以借助
@Qualifier("bean name")
private Car car;
//说明:@Resource 是java的注释,但是Spring框架支持,@Resource指定注入哪个名称的对象
//@Resource(name="对象名") == @Autowired + @Qualifier("name")
@Resource(name="baoma")
private Car car;
添加在方法上:
@PostConstruct //等价于init-method属性
public void init(){
System.out.println("初始化方法");
}
@PreDestroy //等价于destroy-method属性
public void destroy(){
System.out.println("销毁方法");
}
AOP介绍
AOP(Aspect Oriented Programming)即面向切面编程。在不改变原程序的基础上增加新的功能。应用在权限认证、日志、事务。
AOP的作用在于分离系统中的各个关注点,将核心的关注点和横切关注点分离开来。
AOP的实现机制
AOP之所以可以实现面向切面编程是因为它的核心设计模式是动态代理设计模式:
- JDK的动态代理:针对实现了接口的类产生代理。InvocationHandler接口
- CGlib的动态代理:针对没有实现接口的类产生代理,应用的是底层的字节码增强技术,生成当前类的子类对象,MethodIntercepter接口。
JDK动态代理实现
- 创建接口和对应实现类
public interface UserService {
public void login();
}
//实现类
public class UserServiceImpl implements UserService {
public void login(){}
}
- 创建动态代理,实现InvocationHandler接口
public class agency implements InvocationHandler {
private UserService target; //目标对象
public agency(UserService target){
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//本方法中的其他输出输入增强
//proxy 代理方法被调用的代理实例
System.out.println("方法触发了");
//执行被代理类 原方法
Object invoke = method.invoke(target, args);
System.out.println("执行完毕了");
return invoke;
}
}
测试
@Test
public void test1(){
//测试JDK动态代理技术
UserService us = new UserServiceImpl();
agency ag = new agency(us);
//这里不能转换成一个实际的类,必须是接口类型
UserService uservice = (UserService)
Proxy.newProxyInstance(us.getClass().getClassLoader(), us.getClass().getInterfaces(),ag);
uservice.login();
}
CGlib实现代理
- 使用JDK创建代理有一个限制,它只能为接口创建代理实例。
- 第二个入参interfaces就是需要代理实例实现接口列表。
- 对于没有通过接口定义业务方法的类,如何动态创建代理实例?CGlib作为替补,填补了这一缺口。
- CGlib采用底层字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类的方法调用并顺势织入横切逻辑。
实现步骤:
- 添加依赖包
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
- 创建普通类
public class Users{
public void login(){}
}
- 创建CGlib代理器
class CgProxy implements MethodInterceptor {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("输出语句1");
//参数:Object为由CGLib动态生成的代理类实例,Method为上文中实体类所调用的被代理的方法
//引用,Object[]为参数值列表,MethodProxy为生成的代理类对方法的代理引用。
Object obj= methodProxy.invokeSuper(o,objects);
System.out.println("输出语句2");
}
}
- 测试
public static void main(String[] args) {
//1.创建真实对象
Users users = new Users();
//2.创建代理对象
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(users.getClass());
enhancer.setCallback(new CglibProxy());
Users o = (Users) enhancer.create();//代理对象
o.login();
}
总结:Spring中同时使用了这两种方式,底层会自动判断使用哪一种方式。
两种代理的区分方式:
- jdk动态代理生成的代理类和委托类实现的是同一个接口。
- cglib动态代理中生成的字节码更加复杂,生成的代理类是委托类的子类,且不能处理被final修饰的方法。
- jdk采用反射机制调用委托类的方法,cglib采用类似索引的方式直接调用委托类的方法。
Spring中使用aop
使用流程:
- 添加jar包
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
- 添加项目原有的调用过程
- 创建增强类(本质上就是一个普通类)
// 前置通知:目标方法运行之前调用 aop:before
//后置通知(如果出现异常则不会调用):在目标方法运行之后调用 aop:after-returning
//环绕通知:在目标方法之前和之后都调用 aop:around
//最终通知(无论是否有异常都会调用):在目标方法运行之后调用 aop:after
//异常增强:程序出现异常的时候执行(要求:程序代码中不要处理异常)aop:after-throwing - 添加aop命名空间
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
- 设置配置文件
<!--1.创建目标类对象-->
<bean name="userService" class="com.xzk.spring.service.UserServiceImpl"/> <!--2.配置增强类对象-->
<bean name="myAdvice" class="com.xk.spring.aop.MyAdivce"/>
<!-- 3.配置将增强织入目标对象-->
<aop:config>
<aop:pointcut id="pc"
expression="execution(* com.xzk.spring.service.ServiceImpl.*.*
(..))"/>
<aop:aspect ref="myAdvice">
<aop:before method="before" pointcut-ref="pc"/>
<aop:after-returning method="afterReturning" pointcut-ref="pc"/>
<aop:around method="around" pointcut-ref="pc"/>
<aop:after-throwing method="afterException" pointcut-ref="pc"/>
<aop:after method="after" pointcut-ref="pc"/>
</aop:aspect>
</aop:config>
注意:(1)环绕增强需要使用ProceedingJoinPoint作为参数(2)注意标签顺序。
切入点方法的定义
表达式匹配规则举例:
public * addUsers(com.pb.entity.User): " * "表示匹配所有类型的返回值
示例:
public int addUser(User u);
public String addUser(User u);
public void *(com.pb.entity.User):“ * ”表示匹配所有方法名。
示例:
public void selectUser(User u);
public void a(User u);
public void addUser (…): “ … ”表示匹配所有参数个数和类型。
示例:
public void addUser(int a)
public void addUser(int b,int c)
* com.pb.service.*.*(…):匹配com.pb.service 包下所有类的所有方法。
示例:
public void com.pb.service.A.a();
public String com.pb.service.B.a();
* com.pb.service…*(…):匹配com.pb.service 包及子包下所有类的所有方法
如何获得切入点信息
通过JoinPoint对象获取信息
System.out.println("切入点对象:"+jp.getTarget().getClass().getSimpleName());
System.out.println("切入点方法:"+jp.getSignature());
System.out.println("切入点的参数:"+jp.getArgs()[0]);
特殊的前置增强–>Advisor前置增强实现步骤
- 创建增强类,必须实现MethodBeforeAdvice接口
- 修改application.xml文件
(1)创建增强类的对象
(2)定义增强和切入点的关系
<aop:config>
<!-- 表达式是被切入的方法的表达式 -->
<aop:pointcut expression="execution(* biz.impl.*.*(..))" id="mypoint"/>
<aop:advisor advice-ref="增强类对象的id" pointcut-ref="切入点对象的id"/>
</aop:config>
使用AspectJ依赖注解开发
Spring AOP注解方式:
- 增强类也需要创建对象(使用@Component)
- 要启动扫描注解包的代码:
<context:component-scan base-package="com.xk"></context:component-scan>
(1)除了启动spring的注解之外,还要启动aspectj的注解方式。
<aop:aspectj-autoproxy/>
(2)在切面类(增强类)上添加:@Aspect
(3)定义一个任意方法
@Pointcut("execution(* com.*.*(..))")
public void anyMethod(){}
注意:为什么要定义一个任意方法?因为@pointcut要求放在一个方法
(4)用法
@Pointcut("execution(* com.*.*(..))")
public void anyMethod() {
}
@Before("anyMethod()")
public void log(JoinPoint) {
System.out.println("myAspect....log....before");
}
@Around("anyMethod()")
public void aroundTest(ProceedingJoinPoint pjp) {
System.out.println("around...before....");
try {
pjp.proceed();
}
//执行目标方法
catch (
Throwable e) {
e.printStackTrace();
}
System.out.println("around...after....");
}
AOP注解的顺序问题: