Spring思想养成
为了解决企业级的轻量级开发Spring,减少模块与模块之间的耦合性——具体就是帮助开发人员创建类对象,管理对象之间的关系
核心技术
AOP
核心容器(存储的是java对象)
IoC(Invasion of Control)控制反转 思想
把对象的创建,赋值,管理工作交给代码之外的容器实现,也就是对象的创建是其他外部资源完成的
控制:创建对象,对象属性的赋值,对象之间的关系管理。
反转:把创建对象的权限转移给代码之外的容器处理实现,容器代替开发人员
正转:由开发人员在代码中,使用new 构造方法创建对象,开发人员主动管理对象。
为什么要使用IoC:减少对代码的改动,也能实现不同的功能 (解耦合)
容器:可以是一个服务器软件,也可以是一个框架(spring)
java中创建对象的方式:
- 构造方法
- 反射
- 序列化
- IoC
- 克隆
- 动态代理
IoC的技术实现
DI是IoC的技术实现
DI(Dependency Injection)依赖注入 只需要在程序中提供要使用的对象名称即可,至于对象何时在容器中创建,赋值,查找都由容器内部实现
spring使用的di来实现ioc功能,spring底层创建对象,使用的是反射机制
IoCDemo
创建maven项目
导入依赖
<!-- 测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
</dependencies>
创建接口和实现类
package com.dhf.service;
public interface DoService {
void doSome();
}
package com.dhf.service.impl;
import com.dhf.service.DoService;
public class DoServiceImpl implements DoService {
@Override
public void doSome() {
System.out.println("执行了doSome方法---------");
}
}
使用主动创建对象的方法
@Test
public void iocTest(){
DoService doService = new DoServiceImpl();
doService.doSome();
}
能正常调用对象的方法。
使用IoC控制反转技术容器创建对象
先配置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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
spring 的配置文件
1.beans 根标签,java的对象都转化成bean放在beans中
2.spring-beans.xsd 约束文件
-->
<!--
声明bean,创建某个对象
id:对象的自定义名称,唯一
class:类的全限定名称(不能是接口,spring使用的是反射机制创建对象,必须使用类)
spring完成的是 DoService doService = new DoServiceImpl();
spring把创建好的对象放入map中,spring框架中有一个map存放对象的
springMap.put(id的值,对象)
一个bean声明一个对象
-->
<bean id="doService" class="com.dhf.service.impl.DoServiceImpl"/>
</beans>
spring的使用
@Test
public void iocTest(){
//使用ApplicationContext 创建容器对象。
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
//通过context 取出里面的id对应的bean
DoService doService = (DoService) context.getBean("doService");
doService.doSome();
}
得到一样的结果,这就是通过容器来创建对象的全过程。
注入分类
- set注入(设置注入);spring调用类的set方法,在set方法可以实现属性的赋值。
<!-- 给属性赋值(注入就是赋值)
简单类型:java的八个基本类型和String都是简单类型
1.set注入 spring只是调用了set方法
1)简单类型注入
<bean class="xxx" id="xxxx">
<property name="属性1" value="值"/>
<property name="属性2" value="值"/>
</bean>
2)引用类型注入
<bean class="xxx" id="xxxx">
<property name="属性1" ref="其他bean id"/>
</bean>
-->
<bean class="pojo.Student" id="student" scope="singleton">
<property name="name" value="小猪"/>
<property name="age" value="1"/>
</bean>
还可以在java程序中加入注解赋值(可以加在属性上 也可以加在set方法上面)同时在spring配置文件中需要加入< context:annotation-config /> 启动注解
(1)< context:annotation-config />:仅能够在已经在已经注册过的bean上面起作用。对于没有在spring容器中注册的bean,它并不能执行任何操作。
(2)< context:component-scan base-package=“XX.XX”/> :除了具有上面的功能之外,还具有自动将带有@component,@service,@Repository等注解的对象注册到spring容器中的功能。
思考2:如果同时使用这两个配置会不会出现重复注入的情况呢?
答案:因为< context:annotation-config />和 < context:component-scan>同时存在的时候,前者会被忽略。如@autowire,@resource等注入注解只会被注入一次!
private String name;
@Value("3")
private Integer age;
@Value("小猪")
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
- 构造方法注入:spring调用类的有参构造方法,创建对象在构造方法中赋值
<!--
2.构造注入 spring调用有参构造方法
使用<constructor-arg>标签 一个标签表示构造方法一个参数 value
name:构造方法的形参名
index:参数位子 0,1,,,,,
value:形参是简单类型就用value
ref:形参是引用类型就用ref
-->
<bean class="pojo.Student" id="student" scope="singleton">
<!-- <property name="name" value="小猪"/>-->
<!-- <property name="age" value="1"/>-->
<constructor-arg name="name" value="小猪"/>
<constructor-arg name="age" value="4"/>
</bean>
注解创建对象
使用注解的步骤
- 加入maven依赖spring-context,在加入spring-context同时加入了spring-aop的依赖
- 在类中加入spring的注解(
@Component,通用的 创建对象
@Repository,持久层 dao访问数据库
@Service,业务层 service添加业务,事务管理
@Controller,控制层 controller接收用户提交的参数,显示请求的处理结果
@Value,简单类型赋值
@Autowried,spring框架提供的引用类型赋值,默认byType
@Resource ,jdk提供的引用类型赋值,默认byName
) - 在spring配置文件中加入组件扫描标签,说明注解在你的项目中的位置
< context:component-scan base-package="***" />
AOP面向切面编程
底层就是动态代理(可参考链接: jdk动态代理的理解.)可以使用jdk,sglib两种代理。
AOP就是动态代理的规范化,把动态代理的实现步骤,方式定义好了,让开发人员使用统一 的方式使用动态代理。
动态代理的作用:
1)在目标资源不改变的情况下,增加功能。
2)减少代码的重复。
3)专注业务逻辑代码
4)解耦合,让业务功能和日志、事务等非业务功能分离。
理解面向切面编程:
1)需要再分析项目功能时,找出切面。
2)合理安排切面的执行时间(在目标方法前还是方法后)。
3)合理安排切面的执行位置在哪个类,哪个方法增强功能。
术语:
1)Aspect:切面,表示增强的功能,完成一个非业务功能,常见的切面功能,日志,事务,权限验证,参数检查,统计信息。
2)JionPoint:连接点,连接切面与业务方法的位置,就是某类的业务方法。
3)Pointcut:切入点,指多个连接点的集合。
4)目标对象:给哪个类的方法添加功能,这个类就是目标对象
5)Advice:通知,通知表示切面功能执行的时间。在目标方法之前还是之后还是环绕。
一个切面有三个要素:
1)切面的功能代码,切面干什么。
2)切面的执行位置,Piontcut表示。
3)切面的执行时间,Advice表示。
aop的实现
aspectJ:一个开源专门做aop的框架
aspectJ实现aop的两种方式:
1)xml 的配置文件 :主要用于配置全局事务
2)注解 比较广泛 aspectJ有五个注解
1)切面的执行时间 advice(通知,增强)aspectJ有五个注解
1. @Before
2. @AfterReturning
3. @Around
4. @AfterThrowing
5. @After
2)表示切面的位置,常用的语法
五种固定语法.(".."出现在类名中时,后面必须加*,表示包、子包下的所有类)
1. 指定切入点为任意公共方法:
execution(public * * (..))
2. 指定切入点为”set”开头的任意方法:
execution(* set*(..))
3. 指定切入点为所有包下的service子包下的所有类(接口)中所有方法:
execution(* *..service.*.*(..) )
4. 指定切入点为service包下的任意类的任意方法:
execution(* com.xyz.service.*.*(..))
5. 指定切入点为service包和子包下的任意类的任意方法:
execution(* com.xyz.service..*.*(..))
使用xml配置文件 动态代理Demo
定义目标类接口和实现类
package com.dhf.service;
public interface AspectJservice {
void doSome(String name);
}
package com.dhf.service.imp;
import com.dhf.service.AspectJservice;
public class AspectJserviceImpl implements AspectJservice {
@Override
public void doSome(String name) {
System.out.println(name+" 调用了这个类");
}
}
定义切面类(增强功能集合)
package com.dhf.advice;
import java.util.Date;
public class Aspectadvice {
public void beferDate(){
System.out.println("系统当前时间: "+new Date());
}
}
applicationconfig.xml配置文件(使用配置文件实现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
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 创建service目标类对象-->
<bean id="sspectJservice" class="com.dhf.service.imp.AspectJserviceImpl" />
<!-- 创建切面类对象-->
<bean class="com.dhf.advice.Aspectadvice" id="aspectadvice"/>
<!-- 配置aop-->
<aop:config >
<!-- 定义切面 干什么-->
<aop:aspect ref="aspectadvice" id="myaspect">
<!-- 定义切入点 在哪干-->
<aop:pointcut id="point" expression="execution(* *..service.*.doSome(..))"/>
<!-- 定义切入方法的哪个位置 什么时间-->
<aop:before method="beferDate" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
</beans>
可使用注解的方式代替 aop的配置
在切面类上完成所有的配置
package com.dhf.advice;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
/**
* @Aspect 告诉容器 这是一个切面类
*/
@Aspect
public class Aspectadvice {
/**
* @Before 告诉容器 这是前置通知
* execution(* *..service.imp.AspectJserviceImpl.doSome(..)) 前置通知的切入点
*/
@Before("execution(* *..service.imp.AspectJserviceImpl.doSome(..))")
public void beferDate(){
System.out.println("系统当前时间: "+new Date());
}
}
在配置文件做修改
<?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
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 创建service目标类对象-->
<bean id="sspectJservice" class="com.dhf.service.imp.AspectJserviceImpl" />
<!-- 创建切面类对象-->
<bean class="com.dhf.advice.Aspectadvice" id="aspectadvice"/>
<!-- 自动代理-->
<aop:aspectj-autoproxy/>
</beans>
最终的结果都是
注意:
切面的同时方法中可以有参数:JoinPoint
joinPoint :业务方法,要加入切面的业务方法
作用:
1.可以在通知方法中获取方法执行的信息,例如方法名称,方法实参
2. 如果在切面中需要用到方法的信息可以加入JionPiont
3. 这个参数是框架赋值的,必须是参数中的第一个位置。
/**
* @Before 告诉容器 这是前置通知
* execution(* *..service.imp.AspectJserviceImpl.doSome(..)) 前置通知的切入点
*/
@Before("execution(* *..service.imp.AspectJserviceImpl.doSome(..))")
public void beferDate(JoinPoint jp){
//获取方法的完整定义
System.out.println("方法的完整定义:"+jp.getSignature());
//获取方法的名称
System.out.println("方法的名称: "+jp.getSignature().getName());
Object[] args = jp.getArgs();
for (Object obj:args){
System.out.println("参数="+obj);
}
System.out.println("系统当前时间: "+new Date());
}
}
<!-- 自动代理 即使目标类有接口,也使用Cglib 执行时效率高一些-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
持续更新中…