1、简介
- Spring:春天,给软件行业带来了春天
- 2002年,Spring的锥形,`interface 21`问世,以此为基础,经过重新设计和不断丰富内涵,于2004年3月24发布了1.0正式版
- Rod Johnson,创始人,悉尼大学音乐系博士
1.1、优点
- 免费,开源,轻量级,非入侵式
- 控制反转(IOC),面向切面编程(AOP)
- 支持事务
总结一句话:Spring是一个轻量级的IOC与AOP的框架!
1.2、maven依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
2、IOC
也称DI,
同一概念的不同描述
说到IOC,给人的感觉就是控制反转,对象的维护概念不明确,因此提出了DI作为补充
控制反转,即将对象的控制权交由容器管理
DI
,依赖注入,以前的程序在编译器互相引用,强耦合,DI就是将依赖关系从程序中移到配置文件,实现运行期依赖关系的动态注入
2.1、XML装配
<!--id:bean名称;class:bean路径-->
<bean id="student" class="com.qhit.pojo.Student">
<!--普通类型注入;name:属性名称;value:属性值;-->
<!--注:此为set注入,需存在set方法-->
<property name="name" value="Candy"/>
<!--ref为引用类型,值为容器中bean名称-->
<property name="address" ref="address"/>
<property name="books">
<!--数组注入-->
<array>
<!--引用注入;bean为bean名-->
<ref bean="mySchool"/>
<value>红楼</value>
<value>三国</value>
<value>西游</value>
<value>水浒</value>
</array>
</property>
<property name="hobbies">
<list>
<value>听歌</value>
<value>刷B站</value>
<value>看电影</value>
<value>编程</value>
</list>
</property>
<property name="card">
<!--Map注入;key为键;value为值-->
<map>
<entry key="idcard" value=""/>
</map>
</property>
<property name="info">
<!--Properties注入;key为键;-->
<props>
<prop key="idcard">111111222222223333</prop>
</props>
</property>
</bean>
<bean id="address" class="com.qhit.pojo.Address">
<!--此为构造注入,需存在对应有参-->
<!--name:形参名;value:形参值-->
<constructor-arg name="name" value="Monster"/>
</bean>
<bean id="book" class="com.qhit.pojo.Book"/>
2.2、注解装配
2.2.1、环境
jdk1.5支持的注解,Spring2.5就支持了。
The introduction of annotation-based configuration raised the question of whether this approach is “better” than XML.f
要使用注解须知:
-
导入约束. context约束
-
配置注解的支持 context:annocation-config/
-
导入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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annocation-config/> </beans>
2.2.2、注解介绍
创建Bean
注:组件,与xml中的bean标签等效,需添加注解扫描
<!--添加注解扫描后,注解支持可省略-->
<context:component-scan base-package="com.qhit.xxx"/>
@Controller
(控制层)
@Service
(服务层)
@Repository
(持久层)
@Compoent
:当不能确定组件所在位置时使用;
注:四者等价,置于类首
属性注入
@Autowired
- 先按类型装配,当Spring容器中存在多个相同类型的Bean,再按名称进行装配。
- 可修饰方法,变量,构造函数
- required属性为false,当匹配不到Bean时,不会报错,允许为null,默认为true。
- 用于方法: spring会在项目启动的过程中,自动调用一次加了@Autowired注解的方法,我们可以在该方法做一些初始化的工作。
- 用于集合: 会自动把泛型所指对象装配其中
@Qualifier(value = “xxx”)
- 配合Autowired使用,当Autowired注解ByType和ByName均失败时,按照Qualifier的value属性指定值进行ByName。
- 可修饰方法构造入参,变量。
@Resourse
- 先ByName后ByType,若无则报错
- 有name和type属性,指定则独断
- java的注解:
javax.annotation.Resource
@Value(xxx)
- 值注入(基本数据类型)
@Primary
- 置于类首,若属性装配候选者有多个,则此类优先
科普:
@Nullable:
字段标记了这个注解,说明该字段可为null
2.3、二者优缺
2.4、 JavaConfig取代XML
配置类
package com.qhit.config;
import com.qhit.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration /*配置类,与xml等效;被Component注释,注册为Bean*/
@ComponentScan("com.qhit.pojo") /*扫描包下加了Component注解的类,会新建一个Bean,Bean名即类名*/
@Import(OtherConfig.class) /*导入其它配置类,合而为一*/
public class JavaConfig {
@Bean /*注册为Bean,与xml中bean标签等效,方法名即是id,返回值是class*/
public User getUser(){
return new User();/*与ComponentScan注解同用时,若此类被Component修饰,相当于创建了两个Bean*/
}
}
实体类
package com.qhit.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class User {
@Value("Candy")
private String name;
public String getName() {
return name;
}
}
测试类
import com.qhit.config.JavaConfig;
import com.qhit.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MyTest {
@Test
public void Test1(){
ApplicationContext context = new AnnotationConfigApplicationContext(JavaConfig.class);
User user = (User) context.getBean("getUser");
System.out.println(user.getName());
}
}
3、代理模式
3.1、静态代理
3.1.1 实现
接口
public interface Rent {
/*
真实角色与代理角色共同实现
由代理直接调用真实角色
*/
void rent();
}
真实角色
public class People{
//客户类
public void findHouse(Rent rent){
System.out.println("客户找房子···");
rent.rent();
}
}
代理类
public class Proxy implements Rent{
private Host host;
public Proxy(Host host) {
this.host = host;
}
@Override
public void rent() {
System.out.println("一些工作。。。");
host.rent();
}
}
客户类
public class People{
//客户类
public void findHouse(Rent rent){
System.out.println("客户找房子···");
rent.rent();
}
}
测试类
public class MyTest {
public static void main(String[] args) {
Proxy proxy = new Proxy(new Host());
People p = new People();
p.findHouse(proxy); //客户找中介,中介找房东
}
}
3.1.2 优
- 代理模式能将代理对象与真实被调用的目标对象分离。
- 一定程度上降低了系统的耦合度,扩展性好。
- 可以起到保护目标对象的作用。
- 可以对目标对象的功能增强。
3.1.3 缺
- 代理模式会造成系统设计中类的数量增加。
- 在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢。
- 增加了系统的复杂度。
3.2、动态代理
- 与静态代理i角色一样
- 基于反射,无需编写代理类
- 代理类动态生成
- 分为两大类:基于接口(jdk);基于类(cglib)
3.2.1、JDK代理
实现
- 需要了解两个类:
Proxy
(用于动态生成代理类);InvocationHandle
(处理程序)
接口
public interface User {
public User add();
public User delete();
public User update();
public User select();
}
实体类
public class UserImpl implements User{
@Override
public User add() {
System.out.println("添加了一个元素");
return null;
}
@Override
public User delete() {
System.out.println("删除了一个元素");
return null;
}
@Override
public User update() {
System.out.println("更新了一个元素");
return null;
}
@Override
public User select() {
System.out.println("查询元素");
return null;
}
}
代理生成类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyInvocationHandle implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
public Object getProxy() { //生成代理类
/* 传参:
* 1、类加载器,用于类的加载
* 2、被代理对象的接口,只需要方法名进行绑定,无需了解细节
* 3、当前类,用于代理类与处理程序类的绑定
* */
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName()); // 润色
Object rtn = method.invoke(target, args); // 通过反射调用方法,传入调用者与参数
return proxy;
}
public void log(String arg) {
System.out.println("执行了" + arg + "方法");
}
}
invoke方法中proxy参数的作用
测试类
public class MyTest {
public static void main(String[] args) {
UserImpl user = new UserImpl();
MyInvocationHandle handle = new MyInvocationHandle();
handle.setTarget(user);
User proxy = (User) handle.getProxy();
proxy.add().delete().update().select();
}
}
)
优
- 使真是角色的操作更加纯粹,不用去关注一些公共业务
- 公共业务就交给代理,实现了业务的分工
- 公共业务发生扩展时,方便集中管理,可维护性高,扩展性高
- 一个动态代理类代理一个接口,一般就是对应的一类业务
4、AOP
4.1、什么是AOP?
AOP(Aspect Oriented Programming)面向切面编程。通过预编译方式和运行期动态代理实现程序和功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生泛型,利用AOP
可以对业务
逻辑的各个部分
进行隔离
,从而使得业务
逻辑各部分之间的耦合度降低
,提高
程序的可重用性
,同时提高了开发的效率
。
注:AOP的本质就是动态代理,将通用功能运行期动态横切进被代理的方法中。
4.2、AOP在Spring中的作用
横切关注点
:跨越应用程序多个模块的方法或功能,即是,与我们业务逻辑无关的,但我们需要关注的部分,就是横切关注点,如日志,安全,缓存,事务等···切面
(Aspect):横切关注点被模块化的特殊对象,即,它是一个类通知
(Advice):切面必须完成的工作,它是类中的一个方法目标
(Target):被通知对象代理
(Proxy):向目标对象应用通知之后创建的对象切入点
(PointCut):切面通知执行的“地点”的定义连接点
(JointPoint):与切入点匹配的执行点
AOP面向切面横向开发
4.3、使用Spring实现AOP
接口
package com.qhit.service;
public interface UserService {
public void add();
public void delete();
public void update();
public void select();
}
被代理类
package com.qhit.service.impl;
import com.qhit.service.UserService;
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("添加元素");
}
@Override
public void delete() {
System.out.println("删除元素");
}
@Override
public void update() {
System.out.println("更新元素");
}
@Override
public void select() {
System.out.println("查询元素");
}
}
4.3.1、方式一:使用Spring的API接口
前置通知
package com.qhit.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class BeforeLog implements MethodBeforeAdvice {
//前置通知
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + target.getClass().getName() + "类的" + method.getName() + "方法");
}
}
后置通知
package com.qhit.log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {
//后置通知
@Override
public void afterReturning(Object rtn, Method method, Object[] args, Object target) throws Throwable {
System.out.print("执行了" + method.getName() + "方法");
System.out.println("返回了:" + rtn);
}
}
配置类
<?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">
<bean id="user" class="com.qhit.service.impl.UserServiceImpl"/>
<bean id="afterLog" class="com.qhit.log.AfterLog"/>
<bean id="beforeLog" class="com.qhit.log.BeforeLog"/>
<!--方式一:使用原生的Spring API接口-->
<aop:config>
<!--切入点:要执行的位置 expression:execution(修饰词 返回值 类名 方法名 参数)-->
<aop:pointcut id="pointcut" expression="execution(* com.qhit.service.impl.UserServiceImpl.*(..))"/>
<!--执行环绕增强-->
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut" />
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut" />
</aop:config>
</beans>
测试类
package ocm.qhit.test;
import com.qhit.service.UserService;
import com.qhit.service.impl.UserServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
@org.junit.Test
public void Test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
UserService user = context.getBean("user", UserService.class);
user.add();
}
}
运行结果
4.3.2、方式二:自定义来实现AOP
自定义切面
package com.qhit.diy;
public class DiyPointCut {
public void before(){
System.out.println("前置通知");
}
public void after(){
System.out.println("后置通知");
}
}
配置类
<?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">
<bean id="user" class="com.qhit.service.impl.UserServiceImpl"/>
<bean id="diy" class="com.qhit.diy.DiyPointCut" />
<!--方式二:自定义类-->
<aop:config>
<!--自定义类,ref为切面-->
<aop:aspect ref="diy">
<!--切入点-->
<aop:pointcut id="pointcut" expression="execution(* com.qhit.service.*.*(..))"/>
<!--通知-->
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut" />
</aop:aspect>
</aop:config>
</beans>
测试类
package ocm.qhit.test;
import com.qhit.service.UserService;
import com.qhit.service.impl.UserServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
@org.junit.Test
public void Test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
UserService user = context.getBean("user", UserService.class);
user.add();
}
}
运行结果
4.3.3、方式三:注解实现AOP
切面
package com.qhit.annotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class AnnotationPointCut {
@Before("execution(* com.qhit.service.impl.*.*(..))")
public void before(){
System.out.println("前置通知");
}
@After("execution(* com.qhit.service.impl.*.*(..))")
public void after(){
System.out.println("后置通知");
}
//环绕增强中,我们可以传入一个参数,代表我们要获取处理切入的点
@Around("execution(* com.qhit.service.impl.*.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
//执行方法
Object proceed = jp.proceed();
System.out.println("环绕后");
}
}
配置类
<?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">
<bean id="user" class="com.qhit.service.impl.UserServiceImpl"/>
<!--方式三:注解-->
<bean id="annotation" class="com.qhit.annotation.AnnotationPointCut" />
<!--注解支持 jdk(默认 proxy-target-class = "false") true为cglib-->
<aop:aspectj-autoproxy proxy-target-class = "false"/>
</beans>
测试类
package ocm.qhit.test;
import com.qhit.service.UserService;
import com.qhit.service.impl.UserServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
@org.junit.Test
public void Test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
UserService user = context.getBean("user", UserService.class);
user.add();
}
}
运行结果
5、参考文献
- 浅谈IOC–说清楚IOC是什么 ivan820819的博客
- 深度理解依赖注入(Dependence Injection),EagleFish(邢瑜琨), 博客园, 2007.
- Object Builder ApplicationBlock ,吕震宇,博客园, 2006. [浅谈IOC–说清楚IOC是什么]