文章目录
Spring IoC
官方文档https://spring.io/projects/spring-framework
Spring框架的体系结构如下,灰色为核心模块,使用Spring时并不是所有模块都需要导入,但最基本的应导入5个模块,其中包含核心容器依赖的commons模块
<!-- Spring的基础包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
不使用控制反转的业务流程
UserDao 接口
public interface UserDao {
void getUser();
}
UserDaoImpl 实现类
public class UserDaoImpl implements UserDao {
public void getUser() {
System.out.println("CURD");
}
}
UserService 接口
public interface UserService {
void getUser();
}
UserServiceImpl 实现类
public class UserServiceImpl implements UserService {
//应用程序掌握着被关联对象的控制权,对象如果UserDao的实现类不止一个,且需要切换的话,就得在这里手动修改
private UserDao userDao = new UserDaoImpl(); // 关联关系
public void getUser() {
userDao.getUser(); // 调Dao层对应实体类的CURD方法
}
}
即使是使用了依赖注入,不通过IoC容器的话也不算控制反转:
控制反转
本质是所有对象的声明周期均交给IoC容器管理,控制权既不在依赖对象也不在被依赖对象
1.编写ApplicationContext.xml文件
这个文件可以看成是IoC容器
<?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">
<!-- 开始依赖注入 -->
<!-- 第一种方式,setter注入 -->
<!-- id唯一标识bean,相当于映射类的别名 -->
<bean id="mysql" class="com.chen.dao.UserMysqlImpl" />
<bean id="sqlserver" class="com.chen.dao.UserSqlServerImpl">
<!-- 无参构造函数实例化 -->
<property name="userDao" ref="mysql"/>
</bean>
<!-- 如果有属性是复杂类型,用相应的标签显式表达出来: -->
<bean id="Xxx" class="com.chen.dao.Xxx">
<!--注入数组-->
<property name="books">
<array>
<value>红楼梦</value>
<value>水浒传</value>
</array>
</property>
<!--注入map-->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key="a ref" value-ref="myDataSource"/>
</map>
</property>
<!--注入list-->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!--更多复杂类型注入请参考文档,constructor-arg标签无法完成上述功能-->
</bean>
<!-- 第二种方式,构造函数注入 -->
<bean id="UserServiceImpl" class="com.chen.service.UserServiceImpl">
<!-- 使用有参构造函数实例化时有三种给属性赋值的方式 -->
<!-- 按变量名赋值 -->
<!-- ref : 创建好的对象 value : 具体值,基本数据类型-->
<constructor-arg name="userDao" ref="mysql"/>
<!-- 按类型赋值 -->
<constructor-arg type="int" value="1"/>
<!-- 按参数列表中的索引赋值 -->
<constructor-arg index="0" value="mysql"/>
</bean>
<!-- ------------------------------------------------------------------------ -->
<!-- 起别名 -->
<!-- name属性值是已注册好的bean的id -->
<alias name="UserServiceImpl" alias="userNew"/>
<!-- bean标签的name属性起多个别名,逗号或空格分隔 -->
<bean id="mysql" class="com.chen.dao.UserMysqlImpl" name = "别名1,别名2" />
<!-- ------------------------------------------------------------------------ -->
<!-- 导入合并多个Spring配置文件 -->
<import resource="bean.xml"/>
<import resource="bean2.xml"/>
<import resource="bean3.xml"/>
</beans>
所以Bean实例化的方式如下:
-
构造函数方法
以上代码就是属于构造方法实例化。
使用property 标签是首先调无参构造函数实例化bean,然后调set方法注入属性值。
使用constructor-arg标签是调有参构造函数实例化bean并同时注入属性值。
C命名空间和P命名空间是在ApplicationContext.xml头部加入对应的一条配置信息,然后就不需要property 和constructor-arg标签,而是直接在bean标签中完成值的注入,但是这样只能注入简单类型的值,不推荐。
-
静态工厂方法
-
实例工厂方法
2.使用这些bean
public class MyTest {
public static void main(String[] args) {
//获取applicationContext对象 拿到spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
// 将想要拿到bean的id作为参数放进去
UserServiceImpl userServiceImpl = (UserServiceImpl) context.getBean("UserServiceImpl");
userServiceImpl.getUserService();
}
}
Bean的作用域
有6种,常用的是单例和原型
<!--单例模式(Spring默认机制)-->
<bean id="user2" class="com.zyq.pojo.User" scope="singleton"/>
<!--原型模式:每次从容器中get的时候,都会产生一个新对象!-->
<bean id="user2" class="com.zyq .pojo.User" scope="prototype"/>
<!--其余的request、session、application、这些只能在web开发中用到!-->
Bean的装配方式
xml装配
(见上述控制反转部分第1点)
基于注解的装配
- 导入约束 xmlns:context=“http://www.springframework.org/schema/context”
- 添加支持 http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd" - 开启注解支持的声明 <context:annotation-config />
- 在配置文件中注册Bean(只是不需要手动装配Bean标签里面的东西了)
- 在实体类的属性上方添加注解
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean id="dog" class="com.zyq.pojo.Dog"/>
<bean id="cat" class="com.zyq.pojo.Cat"/>
<bean id="people" class="com.zyq.pojo.People"/>
</beans>
ByType:根据类属性的类型,去匹配容器中相同类型的bean
ByName:根据类属性的名字,去匹配容器中id相同名字的bean
@Autowired
不依赖set方法
默认ByType方式去找bean,如果容器中有多个同类型bean,会ByName装配,找不到就报错
@Autowired(required = false) 表示装配时此属性可以为null,默认ture即不能为null
@Qualifier
@Qualifier(“Xxx”) 会去找id为Xxx的bean来装配,这种方式属于指名道姓地去找
为类成员属性装配时必须配合 @Autowired使用,不可以单独使用
但是在给方法参数注入时可以单独使用
@Autowired //如果单纯一个@Autowired 注解则表示找类型为IAccuntDao的,如果有两个类型为IAccuntDao的,则接着匹配类型为IAccuntDao而且名字为accountDao的【缺点:要改变量名指定】
@Qualifier("accountDao2") //加上这个注解直接找类型为IAccuntDao而且名字为accountDao的
private IAccuntDao accountDao;
@Resource
默认ByName方式去找bean,找不到再ByType
java自带的注解方式不属于spring
@Resource(name = “Xxx”) 类似@Qualifier(“Xxx”)
自动装配
不依赖set方法,自动装配只能装自定义类型的属性对象,Java内带的还需要手动注入
在bean标签中使用属性 ”autowire“ ,值取byName或byType:
public class People {
// 有两个自定义类的属性
private Cat cat;
private Dog dog;
private String name;
}
ApplicationContext.xml:
<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat" class="com.kuang.pojo.Cat"/>
<!-- byName方式会使Spring寻找一个与需要自动装配的属性同名的 bean -->
<bean id="people" class="com.zyq.pojo.People" autowire="byName">
<!-- 属于基本类型的属性对象还是需要手动装配 -->
<property name="name" value="zyq"/>
</bean>
<!-- byType方式会自动在容器上下文中查找,和自己对象属性 类型相同的bean,要求容器中只存在这一个属性类型的 bean,否则Spring不知道该用容器中的哪个来注入-->
<bean id="people" class="com.kuang.pojo.People" autowire="byType">
<property name="name" value="zyq"/>
</bean>
如果容器中有多个同类型的bean或者待注入对象中有多个同类型属性对象的话,自动装配有一定的使用限制。
使用注解开发
pom导入包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>6.0.6</version>
</dependency>
配置文件中添加声明
<!--指定注解扫描包 配置文件被加载时会扫面以下路径的包,被添加@Controller @Service等注解的类会被自动注册,配置文件中注册Bean的操作-->
<context:component-scan base-package="com.zyq.pojo"/>
在实体类上以注解方式注册Bean
@Component//等价于<bean id="user" class="com.chen.pojo.User"/>,实际开发应使用@Controller @Service @Repository对应不同的层,功能完全一样
public class User {
//
@Value("chenfuyou") //等价于这句话<property name="name" value="chenfuyou"/> ,一般给基本类型注入值
public String name;
}
用注解装配(基本类型的属性不能叫装配)
//见“Bean的装配方式”内容
作用域注解
@Scope("prototype") //singleton
Spring AOP
静态代理
每个代理类和目标类之间存在一个依赖关系,并且每一个目标对象都会对应一个代理对象。不直接访问目标对象,而是通过代理对象调用目标对象的一些功能,在此基础上代理类还可以增加自定义操作。
静态代理实现步骤:
- 定义一个接口及其实现类
- 创建一个代理类同样实现这个接口
- 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法,并且可以在目标方法执行前后做一些自己想做的事情
动态代理
JDK动态代理 1.实现目标类接口 2.实现InvocationHandler接口 3. 利用目标类和处理类实例生成代理类实例
interface Foo{
void foo();
}
//实现接口
public class RealFoo implements Foo{
@Override
public String ping(String name){
System.out.println("ping");
return "pong";
}
//实现InvocationHandler接口
public class MySellHandler implements InvocationHandler {
private Object target = null;
public MySellHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//some operation...
res = method.invoke(target, args);//执行目标方法
//some operation...
return res;
}
}
public class MainShop {
public static void main(String[] args) {
//创建代理类,使用 Proxy
//1、创建目标对象
UsbSell factory = new UsbKingFactory();
//2、创建 InvocationHandler 对象
InvocationHandler handler = new MySellHandler(factory);
//3、创建代理对象
UsbSell proxy = (UsbSell) Proxy.newProxyInstance(factory.getClass().getClassLoader(),
factory.getClass().getInterfaces(),
handler);
//通过代理对象执行方法
float price = proxy.sell(3);
System.out.println("通过动态代理对象调用方法:"+price);
}
}
CGLIB动态代理
相关概念
横切关注点:从每个方法中抽取出来的同一类非核心业务,例如日志,事务,权限
连接点:把某个类的所有方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点,它是类中客观存在的,不管有没有通知切进来
切入点:被注入上通知的连接点
目标对象:被插入切面的方法
通知 每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法
- 前置通知:在被代理的目标方法前执行
- 返回通知:在被代理的目标方法成功结束后执行(寿终正寝)
- 异常通知:在被代理的目标方法异常结束后执行(死于非命)
- 后置通知:在被代理的目标方法最终结束后执行(盖棺定论)
- 环绕通知:使用…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
切面 封装通知方法的类
代理 向目标对象应用通知(切面)之后创建的代理对象
pom导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>6.0.6</version>
</dependency>
AOP的实现方式
使用spring的API接口
public class Log implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println(method.getClass().getName()+"类,执行了"+method.getName()+"方法");
}
}
public class AfterLog implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了"+method.getName()+"方法"+returnValue);
}
}
配置步骤略
自定义类实现
编写自定义切面类
public class DiyAspect {
public void before(){
System.out.println("========方法执行前=========");
}
public void after(){
System.out.println("========方法执行后=========");
}
}
配置切面,ApplicationContext.xml中:
/*expression中的表达式指定切入到哪个方法,后追问号的为可选项。
execution="访问修饰符?返回类型 类路径?方法名(参数) 异常类型?"
返回值类型、包名、类名、方法名、参数可以使用星号* 代表任意
包名与类名之间一个点.代表当前包下的类,两个点..表示当前包及其子包下的类
参数列表可以使用两个点..表示任意个数,任意类型的参数列表
*/
execution(public void com.itheima.aop.Target.method())
execution(void com.itheima.aop.Target.*(..))
execution(* com.itheima.aop.*.*(..))
execution(* com.itheima.aop..*.*(..))
execution(* *..*.*(..))
<!--注册相关bean -->
<bean id="userServiceImpl" class="com.hardy.service.UserServiceImpl"/>
<bean id="diyAspect" class="com.zyq.utils.DiyAspect"/>
<!-- aop配置的根标签 -->
<aop:config>
<!-- 指定切面,ref将一个Bean引用成切面 -->
<aop:aspect id="xxx" ref="diy">
<!-- 指定切入点,放在切面外面则是全局切入点,所有切面都可以用。-->
<aop:pointcut id="point" expression="execution(* com.hardy.service.UserServiceImpl.*(..))"/>
<!-- 指定通知,method的值是切面类中的方法名 -->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
<aop:around method="around" pointcut-ref="point"/>
<aop:after-returning method="afterReturning" pointcut-ref="point"/>
<aop:after-throwing method="afterException" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
使用注解
编写自定义切面类
@Aspect //标注这个类是一个切面
public class AnnotationPointCut {
@Pointcut("execution(* com.hardy.service.UserServiceImpl.*(..))")
public void pointcut(){}
@Before("pointcut()")
public void before(){
System.out.println("========方法执行前=========");
}
@After("pointcut()")
public void after(){
System.out.println("========方法执行后=========");
}
//在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点
@Around("pointcut()")
public void around(ProceedingJoinPoint pj) throws Throwable {
System.out.println("========环绕前=========");
Signature signature = pj.getSignature();//获得签名
System.out.println("signature:"+signature);//打印调用的方法
//执行方法
Object proceed = pj.proceed();
System.out.println("========环绕后=========");
}
}
配置
<!--注册bean -->
........
<!--开启注解支持 -->
<aop:aspectj-autoproxy/>
Spring 事务管理
待完善