文章目录
spring是一种思想,主要分为ioc(控制反转、依赖注入)和 aop(面向切面)。
1 IOC
1.1 IOC理解
1、IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。我们平常都是通过new来创建的对象,这样就会非常的耦合,例如我们在实现类里调用一个mysql的dao,那么就需要在impl里new mysqlDao();如果这时候增加了一个orcleDao,那么就需要改成new oracleDao()的;高度耦合;为了实现解耦操作,我们不在自己new对象,由spirng来帮我们创建与管理对象。就像是找对象,之前是我们自己主动地去找,现在我们将自己的信息都提供给婚假所,我需要什么样的对象,她就给我介绍什么样的对象,不再关心去找,由主动找变为被动接受。所谓的正转就是new的方式;反转则是由容器来帮忙创建及注入依赖对象,被动给予的方式。
1.2 DI理解
IoC和DI由什么关系呢?其实它们是同一个概念的不同角度描述。
DI是Dependency Injection,依赖注入,ioc的另外一种体现的方式,依赖注入是利用反射来实现的。
1.2.1 依赖注入
1.2.1.1 set注入
依赖注入的常用方式: set注入、构造器注入、注解注入
Set和构造器,通过xml配置文件,标签注入,通过和标签。
set的方式注入,实体类里被注入的属性要有set方法。
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="address" class="com.qiuhao.pojo.Address">
<property name="address" value="西安"/>
</bean>
<bean id="student" class="com.qiuhao.pojo.Student">
<!--第一种,普通值注入,value-->
<property name="name" value="wind"/>
<!--第二种,Bean注入,ref-->
<property name="address" ref="address"/>
<!--数组-->
<property name="books">
<array>
<value>红楼梦</value>
<value>西游记</value>
<value>水浒传</value>
<value>三国演义</value>
</array>
</property>
<!--List-->
<property name="hobbys">
<list>
<value>听歌</value>
<value>敲代码</value>
<value>看电影</value>
</list>
</property>
<!--Map-->
<property name="card">
<map>
<entry key="身份证" value="111111222222223333"/>
<entry key="银行卡" value="1321231312312313123"/>
</map>
</property>
<!--Set-->
<property name="games">
<set>
<value>LOL</value>
<value>COC</value>
<value>BOB</value>
</set>
</property>
<!--null-->
<property name="wife">
<null/>
</property>
<!--Properties-->
<property name="info">
<props>
<prop key="driver">20190525</prop>
<prop key="url">男</prop>
<prop key="username">root</prop>
<prop key="password">123456</prop>
</props>
</property>
</bean>
</beans>
1.2.1.2 命名空间注入
p命名空间注入,可以直接注入属性的值:property
c命名空间注入,通过构造器注入:construct-args
p命名空间和c命名空间,都需要在头文件中导入约束文件。
<?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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--p命名空间注入,可以直接注入属性的值:property-->
<bean id="user" class="com.qiuhao.pojo.User" p:name="wind" p:age="18"/>
<!--c命名空间注入,通过构造器注入:construct-args-->
<bean id="user2" class="com.qiuhao.pojo.User" c:age="18" c:name="wind" scope="prototype"/>
</beans>
P(属性: properties)命名空间 , 属性依然要设置set方法。
1.2.1.3 构造器注入
无参构造器注入
public class User {
private String name;
public User(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("name="+name);
}
}
<?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">
<bean id="user" class="com.qiuhao.pojo.User">
<property name="name" value="无参构造器注入"/>
</bean>
</beans>
有参构造器注入
public class UserT {
private String name;
public UserT(){
System.out.println("UserT被创建了");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("name="+name);
}
}
<!-- 第一种根据index参数下标设置 -->
<bean id="userT" class="com.qiuhao.pojo.UserT">
<!-- index指构造方法 , 下标从0开始 -->
<constructor-arg index="0" value="有参构造器"/>
</bean>
<!-- 第二种根据参数名字设置 -->
<bean id="userT" class="com.qiuhao.pojo.UserT">
<!-- name指参数名 -->
<constructor-arg name="name" value="有参构造器"/>
</bean>
<!-- 第三种根据参数类型设置 -->
<bean id="userT" class="com.qiuhao.pojo.UserT">
<constructor-arg type="java.lang.String" value="有参构造器"/>
</bean>
1.2.2.4 注解注入
1、 在spring配置文件中引入context文件头
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:annotation-config/>
注解注入有
1、@Autowired 和 @Resource
@Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配
@Qualifier不能单独使用。
2、@Component 标记为一个组件,使用在类上
可以不用提供set方法,直接在直接名上添加@value(“值”)
@Component衍生了三个注解
@Repository 标记dao
@Controller 标记controller
@Service 标记service
配置文件需要扫描这些注解
<!--指定要扫描的包,这个包下的注解就会生效-->
<context:component-scan base-package="com.qiuhao"/>
<context:annotation-config/>
在spring4之后,想要使用注解形式,引入aop的包。在配置文件当中,引入一个context约束。
<?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>
3、基于java类进行配置
@Configuration //代表这是一个配置类,该注解里也由 @Component实现
@Bean 有了@Bean,就不用在类上专门再指定 @Component 组件了
如果完全使用了配置类方式去做,我们就只能通过 AnnotationConfig 上下文来获取容器,通过配置类的class对象加载!
public class MyTest {
public static void main(String[] args) {
//如果完全使用了配置类方式去做,我们就只能通过 AnnotationConfig 上下文来获取容器,通过配置类的class对象加载!
ApplicationContext context = new AnnotationConfigApplicationContext(QiuConfig.class);
User getUser = (User) context.getBean("user");
User getUser1 = context.getBean("user",User.class);
System.out.println(getUser == getUser1);
System.out.println(getUser.getName());
}
}
对比下加载配置文件的方式
public static void main(String[] args) {
//获取ApplicationContext;拿到Spring的容器
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//容器在手,天下我有,需要什么,就直接get什么!
UserService userService = (UserServiceImpl) context.getBean("UserServiceImpl");
userService.getUser();
}
1.3 Bean的作用域
bean的作用域有5种:@scope
1、singleton:Spring 容器内只存在一个 Bean 实例 单例模式 是系统默认值
2、prototype:每次调用都会创建一个 Bean 也就是每次 getBean()就相当于是 new Bean()的操作,prototype 的作用域需要慎重考虑 因为每次创建和销毁都会造成很大的性能开销 WEB 环境下的作用域:
3、request:每次 HTTP 请求都会创建一个 Bean
4、session: HTTP Session 共享一个 Bean 实例
5、global-session:用于 portlet 容器,因为每个 portlet 有单独的 session,globalsession 提供一个全局性的 http session
通过ioc容器获取的对象,默认是单例模式,也就是singleton,user1==user2为true,如果指定为prototype,则相当于new了新的对象,为false
2 AOP
aop面向切面,代理模式:静态代理、动态代理,23种设计模式之一。
2.1 静态代理
可以使得我们的真实角色更加纯粹,不再去关注一些公共的事情,公共的业务由代理来完成,实现了业务的分工,公共业务发生扩展时变得更加集中和方便。缺点是类多了,多了代理类,工作量变大了,开发效率降低。
例如,你和婚庆公司都实现了结婚marry接口,你原来结婚需要婚礼前订酒店,安排现场,结婚,婚后收拾,数钱等操作。这样你就和结婚这件事情非常的耦合;那么如果交给婚庆公司处理呢,你只需要专注的结婚就行了,其他的婚礼前后的事就交给婚庆公司去做,这样婚庆公司就代理你,替你做了一些事情。这个时候如果另外一个人要结婚,那么他也只需要关心结婚这件事情就是了。那么如果这个人和你的结婚场面要求不一样,他要求的更加的高级,那么这个时候就需要一个成本更高的更高级的婚庆公司。每个人的需求不同,那么就需要不同的级别的代理。那么这里会出现疑惑,为什么一个婚庆公司只能实现一个水平的服务,不能开展多个不同价位的需求的服务呢?这个地方我只能说,婚庆公司也是实现的结婚这个接口,实现结婚这一个方法,这个结婚的方法只有一个marry,那么如果要开展多个不同需求层次的服务,那么这个结婚的方法就的新增marry2,marry3,marry4,来达到不同价位的服务。常规说法是说静态代理,一个对象一个代理,如果对象多了,那么代理类也就会随之增多,要么就是在一个代理类中新增其他的处理方法marry2,marry3,marry4。
package cn.qiuhao.javabasics.proxy.mine;
/**
*
* @Description: 静态代理
* @author: qiuhao
* @date: 2021年3月16日
*
*/
public class StaticProxy {
public static void main(String[] args) {
// TODO Auto-generated method stub
WeddingCompany weddingCompany = new WeddingCompany(new You());
weddingCompany.happyMarry();
}
}
class You implements Marry{
@Override
public void happyMarry() {
System.out.println("高高兴兴的结婚");
}
}
class WeddingCompany implements Marry{
private Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void happyMarry() {
System.out.println("婚礼前的准备");
target.happyMarry();
System.out.println("婚礼后的收拾");
}
}
interface Marry{
void happyMarry();
}
静态代理的缺点,接口类型变动,就需要一个代理类,100个不同的接口,就要100个代理类,或者一个代理类实现100个接口?显得很笨重。
其实这样并不能很好的说明一个代理只能为一个类服务,一个代理也可以为多个类服务啊?因为这里例子举的不好。
如果现在有一个吃饭的接口,代理proxy实现吃饭的接口,那么我们用代理proxy来记录这件事情的开始时间和结束时间,实际上就是后续的aop切入方法体的开始与结尾。又有一个学习的接口,那么在代理proxy里也实现学习的接口,记录开始和结束时间。有多少接口,那么代理proxy就要实现多少接口,也就是方法更多,要么就是多增加不同的代理类proxy。假如说这些要做的事情有1000个,那么就要有1000个不同的接口,代理类proxy全部实现这1000个接口去代理不同的对象 或者 来1000个这样的代理类。但是他们还只是做了一件事情,那么就是记录的开始和结束时间,所以这就是静态代理的弊端。
package cn.qiuhao.javabasics.proxy.mine;
/**
* @ClassName: StaticProxy1.java
* @Description:
* @author qiuhao
* @date 2021年3月25日
*
*/
public class StaticProxy1 {
public static void main(String[] args) {
Eat eat = new Person();
Proxy proxy1 = new Proxy(eat);
proxy1.eat();
Study study = new Person();
Proxy proxy2 = new Proxy(study);
proxy2.study();
}
}
class Proxy implements Eat, Study{
private Eat eat;
private Study study;
public Proxy(Eat eat) {
this.eat = eat;
}
public Proxy(Study study) {
this.study = study;
}
@Override
public void eat() {
System.out.println("开始时间");
eat.eat();
System.out.println("结束时间");
}
@Override
public void study() {
System.out.println("开始时间");
study.study();;
System.out.println("结束时间");
}
}
class Person implements Eat, Study{
@Override
public void eat() {
System.out.println("一日三餐,三餐四季");
}
@Override
public void study() {
System.out.println("坚持每天学习");
}
}
interface Eat{
public void eat();
}
interface Study{
public void study();
}
2.2 动态代理
动态代理是利用反射来动态的创建代理,动态的去创建与接口相关的代理。然后在代理类中的记录开始时间和结束时间。那么问题来了,动态的代理的也是特定接口的实现方法的开始时间和结束时间啊!那么我们可以不代理特定接口啊,我们代理object,在invoke方法里记录开始时间和结束时间,调用Method方法类的
method.invoke(object, args);这样就能实现任何的接口的实现方法,都能记录它的开始时间和结束时间。
那么既然可以代理所有的接口,即object,那么在静态代理里,代理类能不能代理object呢,因为代理要实现接口,但是我们又不知道要去实现哪个接口,总不能去实现object接口吧!如果真的去实现object接口,那岂不是要实现它里面的所有的方法。那么如果不实现object接口呢,直接通过构造器定义一个object的成员变量,那么该如何去调用方法呢。
package cn.qiuhao.javabasics.proxy.mine;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @ClassName: DymincProxy.java
* @Description: 动态代理
* @author qiuhao
* @date 2021年3月26日
*
*/
public class DynamicProxy {
public static void main(String[] args) {
//只需要改动接口,就可以动态的代理
Play play = new Student();
ProxyInvocationHandler pih1 = new ProxyInvocationHandler(play);
//动态代理类 代理类pih的ClassLoader,代理的接口, 代理类pih
Play proxy1 = (Play) Proxy.newProxyInstance(pih1.getClass().getClassLoader(),
play.getClass().getInterfaces(),pih1);
proxy1.play();
Drive drive = new Student();
ProxyInvocationHandler pih2 = new ProxyInvocationHandler(drive);
//动态代理类 代理类pih的ClassLoader,代理的接口, 代理类pih
Drive proxy2 = (Drive) Proxy.newProxyInstance(pih2.getClass().getClassLoader(),
drive.getClass().getInterfaces(),pih2);
proxy2.drive();
}
}
//定义一个类实现 InvocationHandler,动态代理类
class ProxyInvocationHandler implements InvocationHandler{
//代理的接口对象
private Object target;
public ProxyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始时间----");
method.invoke(target, args);
System.out.println("结束时间----");
return null;
}
}
class Student implements Play, Drive{
@Override
public void play() {
System.out.println("开心的玩耍。");
}
@Override
public void drive() {
System.out.println("学会驾车。");
}
}
interface Play{
public void play();
}
interface Drive{
public void drive();
}
可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
公共的业务由代理来完成 . 实现了业务的分工 ,
公共业务发生扩展时变得更加集中和方便 .
一个动态代理 , 一般代理某一类业务
一个动态代理可以代理多个类,代理的是接口!
2.3 AOP实现
Aop的三种实现
spring 需要引入
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
2.3.1 通过spring api实现
接口 UserService
public interface UserService {
public void add();
public void delete();
public void update();
public void search();
}
实现类 UserServiceImpl
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("增加了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void update() {
System.out.println("更新了一个用户");
}
public void select() {
System.out.println("查询了一个用户");
}
}
前置增强
public class Log implements MethodBeforeAdvice {
//method: 要执行的目标对象的方法
//args: 参数
//target: 目标对象
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了");
}
}
后置增强
public class AfterLog implements AfterReturningAdvice {
//returnValue;返回值
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了"+method.getName()+"方法,返回结果为:"+returnValue);
}
}
最后去spring的文件中注册 , 导入约束,并实现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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册bean-->
<bean id="userService" class="com.qiuhao.service.UserServiceImpl"/>
<bean id="log" class="com.qiuhao.log.Log"/>
<bean id="afterLog" class="com.qiuhao.log.AfterLog"/>
<!--方式一:使用原生Spring API接口 -->
<!--配置aop:需要导入aop的约束-->
<aop:config>
<!--切入点:expression:表达式,execution(要执行的位置! * * * * *) -->
<aop:pointcut id="pointcut" expression="execution(* com.qiuhao.service.UserServiceImpl.*(..))"/>
<!--执行环绕增加!-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
Test
public class MyTest {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) context.getBean("userService");
userService.search();
}
}
2.3.2 自定义类实现aop
public class DiyPointCut {
public void before(){
System.out.println("========方法执行前=========");
}
public void after(){
System.out.println("========方法执行后=========");
}
}
注册xml
<!--第二种方式自定义实现-->
<!--注册bean-->
<bean id="diy" class="com.qiuhao.config.DiyPointcut"/>
<!--aop的配置-->
<aop:config>
<!--第二种方式:使用AOP的标签实现-->
<aop:aspect ref="diy">
<aop:pointcut id="diyPonitcut" expression="execution(* com.qiuhao.service.UserServiceImpl.*(..))"/>
<aop:before pointcut-ref="diyPonitcut" method="before"/>
<aop:after pointcut-ref="diyPonitcut" method="after"/>
</aop:aspect>
</aop:config>
Test
public class MyTest {
@Test
public void test(){
ApplicationContext context = new
ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService)
context.getBean("userService");
userService.add();
}
}
2.3.3 使用注解实现
@Aspect //标注这个类是一个切面
public class AnnotationPointCut {
@Before("execution(* com.qiuhao.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("=====方法执行前======");
}
@After("execution(* com.qiuhao.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("=====方法执行后======");
}
//在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点;
@Around("execution(* com.qiuhao.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
Signature signature = jp.getSignature();//获得签名
System.out.println("signature:"+signature);
Object proceed = jp.proceed(); //执行方法
System.out.println("环绕后");
System.out.println(proceed);
}
}
在Spring配置文件中,注册bean,并增加支持注解的配置
<!--方式三-->
<bean id="annotationPointCut" class="com.qiuhao.diy.AnnotationPointCut"/>
<!--开启注解支持! JDK(默认 proxy-target-class="false") cglib(proxy-target-class="true")-->
<aop:aspectj-autoproxy/>
直接使用注解@@Component 将 AnnotationPointCut 注入到spring
<!--开启注解的支持, 扫描注解-->
<context:component-scan base-package="com.qiuhao"/>
<context:annotation-config/>
<aop:aspectj-autoproxy/>