简介
Spring 是一个轻量级的java开发框架,其核心是ioc(控制反转)和AOP(面向切面)
IOC
ioc 是一种思想,有多种实现方式。
如在类A中需要另一个类B时我们往往通过new来创建
public class A{
public B b=new B();
}
而这样在我们的需求发生改变时我们我们就需要改变原有的代码
public class A{
public B b=new C();
}
这时主动权在我们程序员手中,我们new了什么对象,用户就只能使用什么对象 当用户的需求改变时我们需要去修改我们的代码
因此我们做了这样的修改
public class A{
public B b;
public void set(B b){
this.b=b;
}
}
这样用户需要什么对象都可以自己传入,控制权在用户手上
这是很简单的一种控制反转的方法,spring中控制ioc的方法是DI(依赖注入)
依赖注入的本质是通过set注入,因此类中需要有对应的set方法
在spring中对象的创建销毁等都是由spring容器帮我们做的,我们只需告诉spring我们需要哪个对象即可
如A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。(引自网络)
Spring的使用
- 首先我们需要导入一些相关的依赖包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.1</version>
</dependency>
- 因为在spring中我们将对象的创建销毁等交给了spring容器因此我们需要将我们的对象交给spring容器 因此我们需要编写配置文件beans.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-2.5.xsd">
//我们将每个实体类作为bean注入到spring的容器中
//id 作为bean的唯一标识,class为spring指明其代表的类
<bean id="Test2" class="pojo.User">
//property 相当于给对象的属性赋值,需要类中有相应的set方法
//name 代表属性名,value代表要赋予的值
// 当属性为一个对象时可通过 ref=""来赋值,ref引用的是spring容器中的对象
<property name="test" value="testHello"/>
</bean>
</beans>
- 当我们在编写好配置文件后,我们如何获取?
public static void main(String[] args) {
//加载配置文件,在加载Beans.xml文件时会自动创建所有bean的实例,默认单例模式
ApplicationContext Context = new ClassPathXmlApplicationContext("Beans.xml");
//从spring中根据id获取类对象
Test2 testHello2 = (Test2) Context.getBean("Test2");
}
关于配置文件
IOC创建对象的方式
- 默认通过无参构造创建
- 当要使用有参构造时有三种方法
//下标指定
<bean id="user" class="cn.sxt.vo.User">
<!-- index指构造方法参数下标 -->
<constructor-arg index="0" value=""></constructor-arg>
</bean>
//类型指定
<bean id="user" class="cn.sxt.vo.User">
<!-- type指参数类型,同类型参数按顺序传 -->
<constructor-arg type="java.lang.String" value=""></constructor-arg>
</bean>
<bean id="user" class="cn.sxt.vo.User">
<!-- name指参数名称 -->
<constructor-arg name="name" value=""></constructor-arg>
<!--类型为对象时用ref引用-->
<constructor-arg ref="user" ></constructor-arg>
</bean>
别名
<alias name="exampleBean" alias="bean"/>
name 也是别名,更高级,可以同时取多个
<bean id="uers" class="" name="user,u1,u2;u3"></bean>
为id为users的实体类配置别名为user,u1,u2,u3 分隔符可以用空格,逗号,分号等
import 导入配置文件,将多个配置文件合并
<import resource="bean.xml"></import>
依赖注入
构造器注入
在上面 "IOC创建对象的方式" 中已经说过了
set注入
<bean id="Test2" class="pojo.Test2">
<!--bean注入-->
<property name="test" ref="testHello"/>
<!--数组-->
<property name="books">
<array>
<value>红楼梦</value>
<value>西游记</value>
<value>水浒传</value>
</array>
</property>
<!--set-->
<property name="games">
<set>
<value>coc</value>
<value>lol</value>
</set>
</property>
<!--list-->
<property name="hobby">
<list>
<value>swing</value>
<value>run</value>
<value>basketball</value>
</list>
</property>
<!--map-->
<property name="cards">
<map>
<entry key="身份证" value="123456"></entry>
<entry key="银行卡" value="13456"></entry>
</map>
</property>
<!--空值设置-->
<property name="name">
<null/>
</property>
<!--properties注入-->
<property name="pop">
<props>
<prop key="键">值</prop>
<prop key="学号">134656</prop>
</props>
</property>
</bean>
拓展注入
p注入:直接set注入
导入命名空间
xmlns:p="http://www.springframework.org/schema/p"
<bean id="testHello" class="pojo.Test" p:str="d">
</bean>
c注入:通过构造器注入
xmlns:c="http://www.springframework.org/schema/c"
<bean id="testHello" class="pojo.Test" c:str="c">
</bean>
作用域
| Scope | Description |
|---|---|
| singleton | 在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,bean作用域范围的默认值。 |
| prototype | 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()。 |
| request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于web的Spring WebApplicationContext环境。 |
| session | 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean。该作用域仅适用于web的Spring WebApplicationContext环境。 |
| application | 限定一个Bean的作用域为ServletContext的生命周期。该作用域仅适用于web的Spring WebApplicationContext环境。 |
修改作用域
<bean id="testHello" class="pojo.Test" scope="singleton"></bean>
自动装配
autowrite
<bean id="dog" class="Dog"></bean>
<bean id="cat" class="Cat"></bean>
<bean id="people" class="people" autowire="byType"></bean>
autowrite=""
byName 通过id进行匹配,通过set方法注入,id需与set的参数名一致(需要保证beanid唯一)
byType 通过class匹配,通过set注入,要保证该bean所需和属性类型一样(保证bean的class唯一)
constructor 与byType类似,但适用于构造函数参数。如果容器中不存在构造函数参数类型的一个 bean,则将引发致命错误。
default
no (默认)无自动装配。 Bean 引用必须通过ref元素定义。对于大型部署,建议不要更改默认设置,因为明确指定协作者可以提供更好的控制和清晰度。在某种程度上,它记录了系统的结构。
注解实现
前提
<!--1. 添加约束-->
<?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 ">
</beans>
<!--2. 开启注解支持-->
<context:annotation-config/>
@Autowired
可直接在属性上使用,无需定义set方法
也可在set方法上使用
通过byType方式匹配,如果有多个则通过byName匹配,根据变量名与beanid
public class people {
@Autowired
private Dog dog;
//required 默认为true 为false则该属性可以为空
@Autowired(required = false)
private Cat cat;
}
当有多个类型相同的bean时可通过@Qualifier(value=“beanid”)类唯一指定一个bean
public class people {
@Autowired
@Qualifier(value=“dog1”)
private Dog dog;
//required 默认为true 为false则该属性可以为空
@Autowired(required = false)
private Cat cat;
}
@Resource(name = "")
与Autowrited相似,但优先使用byname
public class TestServiceImpl {
@Resource(name= "userDao")
private UserDao userDao;
}
注解开发
前提
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
<context:annotation-config/>
<!--指定扫描的包,该包下的注解生效-->
<context:component-scan base-package="java"/>
@Component
@Component //等价于<bean id ="test" class= "Test"/> id默认为类名的小写
@Scope("singleton") //等价于于<bean id ="test" class= "Test" scope="singleton"/>
public class Test{
//@Value()可以放在属性或set方法,()内为赋值
@Value("xxxxx")//等价于 <property name="name" value="xxxx"/>
public String name;
}
衍生注解
mvc模式
dao【@Repository】
service【@Service】
controller【@Controller】
功能同Component一样
@Configuration
@Configuration本身也是一个Component
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component//说明Configuration也是一个组件
public @interface Configuration {
boolean proxyBeanMethods() default true;
}
@Configuration有两种方式来注册bean,一种是通过 @ComponentScan("")和@Component 配合来扫描注册,一种是通过@Bean
@Configuration //本身也是一个组件
@ComponentScan("")//配置扫描的包
@Import(Config2.class)//导入其他配置
public class Config {
@Bean //等价于注册一个bean id为方法名,class为返回值的类型
public User getUser(){
return new User();
}
}
注意:使用 @Configuration 后通过 new AnnotationConfigApplicationContext(Config.class); 来实例化容器
public class Mytest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext();
User u=context.getBean("getUSer",User.class);
System.out.println(u.getName());
}
}
AOP(面向切面编程)
-
是OOP(面向对象编程)的延续,主要应用在日志切面、事务控制切面、权限控制切面、异步切面和缓存切面,利用AOP可以对任务解耦,通过重用性,提升效率。
-
只是一种编程范式,AOP并没有规定说,实现AOP协议的代码,要用什么方式去实现。
-
编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面。我们将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,在需要时将其织入到对应的切面中,这样我们修改这些日志记录等代码时不会影响核心业务的进行
-
在spring中通过动态代理的方式实现了AOP
代理模式
意图:为其他对象提供一种代理以控制对这个对象的访问。
角色:
-
抽象角色︰一般会使用接口或者抽象类来解决(租房)
-
真实角色︰被代理的角色(房东)
-
代理角色︰代理真实角色(中介),代理真实角色后,我们一般会做一些附属操作(收取中介费)。
-
客户: 访问代理对象的人
作用:
中介隔离作用:将客户与真实对象(房东)隔离,其特征是代理类和委托类实现相同的接口。(租房)
开闭原则,增加功能:当我们需要增加功能(收取中介费)时,只需修改代理类无需对真实对象进行修改。
缺点:
- 由于在客户和真实角色之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
- 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
我们有多种不同的方式来实现代理。如果按照代理创建的时期来进行分类的话, 可以分为两种:静态代理、动态代理。
静态代理
在程序运行前,由程序员创建或特定工具自动生成源代码并对其编译生成.class
代理角色和真实角色的关系在运行前就确定了。
缺点:1. 一个真实角色对应一个代理角色,代码量翻倍
1.创建一个抽象类(租房)
public interface Rent {
void rent();
}
- 创建真实对象(房东)
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东出租");
}
}
- 创建代理对象(中介)
public class Proxy implements Rent{
private Host host;
public Proxy(Host host) {
this.host = host;
}
public Proxy() {
}
@Override
public void rent() {//代理Host
host.rent();
getmoney();
}
public void getmoney(){
System.out.println("收取中介费");
}
}
- 创建客户
public class Client {
public static void main(String[] args) {
Proxy proxy=new Proxy(new Host());
proxy.rent();//通过代理使用Host的方法
}
}
动态代理
由jdk创建代理角色,我们不用手工写了,代理的真实角色是可变的
可以分为两大类:
-
基于接口的动态代理
-
基于类的动态代理 这里我们主要了解JDK基于接口的动态代理
动态代理的步骤:
- 实现InvocationHandler,实现invoke方法
- 在invoke方法中,编写原本代理类(中介)的功能
- 在客户端通过Proxy类获取代理类
- 通过代理类调用方法
//1.实现InvocationHandler,实现invoke方法
public class ProxyIH implements InvocationHandler {
private Object target;//真实对象
public ProxyIH(Object target){
this.target=target;
}
@Override
// proxy jdk创建的代理对象
// Method 真实对象的方法
// agrs 真实对象方法的参数
// 以上均由jdk提供
//2. 在invoke方法中,编写原本代理类(中介)的功能
public Object invoke(Object proxy, Method method, Object[] args){
// 调用传入对象target的方法,args为方法参数
method.invoke(target,args);//执行目标方法(租房)
System.out.println("代理执行");//代理类拓展的功能
return null;
}
}
一个代理类需要:
- 真实对象------> h.getClass().getClassLoader()
- 继承抽象接口------> h.getClass().getInterfaces()
- 实现代理方法 ------>proxyIH
public class Mytest {
public static void main(String[] args) {
//创建真实对象
Rent h= new Host();
//根据真实对象创建InvocationHandler
InvocationHandler proxyIH = new ProxyIH(h);
// 获取代理对象
// 3. 在客户端通过Proxy类获取代理类
Rent o = (Rent)Proxy.newProxyInstance(h.getClass().getClassLoader(), h.getClass().getInterfaces(), proxyIH);
//4. 通过代理类调用方法
o.rent();
}
}
//-------------------运行结果----------------
房东出租
代理执行
至此假设我们有多个真实对象我们只需变更Rent h= new Host();就可以获得不同的代理对象, 不需要我们手动创建,由jdk根据反射创建,代理的真实角色是可变的
AOP的使用
AOP 也是一种思想,有多种实现方式。在spring中通过动态代理实现
有几个概念需要了解
-
Advice(增强):指你想要给目标对象增加的功能
-
Target(目标对象):织入 Advice 的目标对象.。
-
Joint point(连接点):即spring允许你插入Advice的地方,如每个方法执行的前后,抛出异常的前后等,即我们可以插入Advice的地方
-
Pointcut(切点):当一个类中有多个方法,而我们只想要在其中一个或几个方法的前后插入Advice,那么我们可以通过切点来指定哪个方法要被插入,即切点是我们要插入Advice的地方
-
Aspect(切面) Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
-
Weaving(织入):将 Aspect 和其他对象连接起来并创建 Adviced object 的过程,
导入依赖
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
在spring配置文件中添加以下代码
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
有三种实现方式**
UserserviceImpl
public class UserserviceImpl implements Userservice{
public void add() {
System.out.println("add");
}
}
通过SpringAPI实现
定义要织入的操作
//执行前插入的操作
public class BeforeLog implements MethodBeforeAdvice {
//目标对象的方法,参数,目标对象
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass()+"的"+method.getName()+"被执行了");
}
}
//执行后插入的操作
public class AfterLog implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass()+"的"+method.getName()+"被执行并返回:"+returnValue);
}
}
在spring配置文件中定义切点
<bean id="BeforeLog" class="log.BeforeLog" ></bean>
<bean id="AfterLog" class="log.AfterLog"></bean>
<!--在UserserviceImpl类的任意方法及其参数都是一个可以插入代码的点 poincut-->
<aop:config>
<!-- 目标位置:execution( 修饰词 返回值 类名 方法名 参数-->
<aop:pointcut id="pointcut" expression="execution(* server.UserserviceImpl.*(..))"/>
<!--执行环绕增加-->
<!--active-ref指定要插入的操作,pointcut-ref指定切点-->
<!--将log类中的方法插入到UserserviceImpl方法执行之前执行-->
<aop:advisor advice-ref="BeforeLog" pointcut-ref="pointcut" ></aop:advisor>
<aop:advisor advice-ref="AfterLog" pointcut-ref="pointcut"></aop:advisor>
</aop:config>
运行结果:
class server.UserserviceImpl的add被执行了
add
class server.UserserviceImpl的add被执行并返回:null
自定义类实现
public class Diypointcut {
public void before(){
System.out.println("before exe");
}
public void after(){
System.out.println("after out");
}
}
<bean id="Diypointcut" class="Diypointcut"></bean>
<aop:config>
<!--选择自定义类-->
<aop:aspect ref="Diypointcut">
<!--设置切点-->
<aop:pointcut id="point" expression="execution(* server.UserserviceImpl.*(..))"/>
<!--选择执行的方法和切点-->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
测试结果:
before exe
add
after out
注解实现
在spring配置文件中开启注解支持
<bean id="anno" class="AnnoPointcut"></bean>
<aop:aspectj-autoproxy/>
@Aspect //标注为切面
public class AnnoPointcut {
@Before("execution(* server.UserserviceImpl.*(..))")
public void before(){
System.out.println("before anno");
}
@After("execution(* server.UserserviceImpl.*(..))")
public void after(){
System.out.println("after anno");
}
//Around 环绕,即在方法执行前后
@Around("execution(* server.UserserviceImpl.*(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("around before");
Object proceed = joinPoint.proceed();
System.out.println("around after");
}
}
运行结果:
around before
before anno
add
after anno
around after
AOP 小结
运用动态代理的方式将一些重复的功能插入到核心业务的前后,对原有代码毫无入侵性。