Spring学习笔记
IOC
控制反转,将原本程序中手动创建UserService对象的控制权,交由Spring框架管理
简单说,就是把创建UserService对象控制权被反转到了Spring框架
DI 依赖注入
- 就是在Spring创建这个对象的过程中,将这个对象所依赖的属性注入进去
Bean标签的一些常用的属性
-
id和name
- 一般情况下,装配一个bean的时候,通过指定一个id属性作为Bean的名称
- id属性在ioc容器中必须的唯一的
- 如果bean的名称中有特殊字符,就需要使用name属性
-
class
- class用于设置一个类的全路径,用Spring来生成这个类的实例和反射
由scope配置
-
singleton是默认值,单例
- prototype,每一次都会创建一个新的实例,也就是说是多例的
Spring的注解方式注入
-
@Component、@Repository、@Service、@Comtroller等都是相当于在配置文件中对<bean></bean>中的id的配置
@Component("userService")//这就相当于是<bean></bean>的id; <bean id="userService" class="com.imooc.ioc.demo5.UserService">
-
如果是普通类型用@Value,这个注解给key设值
/* 正常这时候得需要一个set方法 然后在配置文件中使用<property name="name" value="张三"></property>进行设值 */ @Value("米饭") private String something; /* 如果提供了set方法,这时候注解需要添加在set方法前 */ @Value("米饭") public void setSomething(String something) { this.something = something; }
如果是对象类型的话用一下方法,例如:UserDao的注解
@Autowired
@Qualifier("userDao")
private UserDao userDao;
@Repository("userDao")
public class UserDao {
public void save(){
System.out.println("Dao层保存数据");
}
}
//其中的@Qualifier("userDao")和@Repository("userDao")中的userDao名称一定要一致,否则会报错
//
@Resource(name = "userDao")就相当于@Autowired @Qualifier("userDao") //Resource也是强制用名称注入
Spring的其他注解
@Component("bean1")
public class Bean1 {
@PostConstruct
public void init(){
System.out.println("initBean......");
}
public void say(){
System.out.println("sayBean.......");
}
@PreDestroy
public void destory(){
System.out.println("destoryBean..........");
}
=======================================================================================
@Component("bean2")
@Scope("prototype")
public class Bean2 {
}
======================================================================================
@Test
public void demo3(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Demo2 bean1 = (Demo2) applicationContext.getBean("demo2");
Demo2 bean2 = (Demo2) applicationContext.getBean("demo2");
System.out.println(bean1 == bean2);
}
===================================================================================
当bean2的Scope为默认值singleton的为单例的 ,bean1 == bean2的值为true;
当bean2的Scope为prototype的为多例的 ,bean1 == bean2的值为false;
传统xml配置与注解配置混合使用
public class ProduceService {
@Resource(name = "categoryDao")
private CategoryDao categoryDao;
@Resource(name = "productDao")
private ProductDao productDao;
// public void setCategoryDao(CategoryDao categoryDao) {
// this.categoryDao = categoryDao;
// }
//
// public void setProductDao(ProductDao productDao) {
// this.productDao = productDao;
// }
public void save(){
System.out.println("ProduceServicede的save()方法被执行了");
categoryDao.save();
productDao.save();
}
}
=========================================================================================
<context:annotation-config/>
<bean id="produceService" class="com.imooc.demo3.ProduceService">
<!-- <property name="categoryDao" ref="catrgoryDao"></property>-->
<!-- <property name="productDao" ref="productDao"></property>-->
</bean>
<bean id="productDao" class="com.imooc.demo3.ProductDao">
</bean>
<bean id="categoryDao" class="com.imooc.demo3.CategoryDao"></bean>
<<context:annotation-config/>>这个是启动属性注入
-
正常是通过set方法
public void setCategoryDao(CategoryDao categoryDao) { // this.categoryDao = categoryDao; // } // // public void setProductDao(ProductDao productDao) { // this.productDao = productDao; // }
-
然后使用xml配置文件配置
<property name="categoryDao" ref="catrgoryDao"></property> <property name="productDao" ref="productDao"></property>
-
-
启用<<context:annotation-config/>>后,可以通过注解方式来代替先创建set方法然后在xml配置文件中配置
@Resource(name = "categoryDao") private CategoryDao categoryDao; @Resource(name = "productDao") private ProductDao productDao;
Spring AOP
public class UserDaoImpl implements UserDao{
public void save(User user){
public void checkPrivilege(){
checkPrivilege()
...保存用户
}
public void update(User user){
...更新用户
}
public void delete(User user){
...删除用户
}
public List find( ){
...查询用户
}
public void checkPrivilege(){
//检查权限
}
}
- 这个类中保存用户时,调用checkPrivilege()方法检查权限。但是这只是一个类,如果有很多个类,要再每一个来中都写这么一个方法然后调用时非常麻烦的
-
传统方法:纵向继承
public class BaseDaoImpl{ public void checkPrivilege(){ } } public class UserDaoImpl extends BaseDaoImpl{ public void save(User user){ checkPrivilege() //保存用户 } }
没有使用AOP的方式:
定义一个父类的实现类,父类中有一个检查权限的方法
让其他的类继承这个父类的实现类,子类中可以有父类中的方法,直接调用checkPrivilege()检查权限
-
使用AOP的动态代理方法:采用横向抽取机制
- SpringAOP使用纯java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式想目标类织入增强代码
- AOP的相关术语:
public class UserDaoImpl implements UserDao{ public void save(User user){ public void checkPrivilege(){ checkPrivilege() ...保存用户 } public void update(User user){ ...更新用户 } public void delete(User user){ ...删除用户 } public List find( ){ ...查询用户 } public void checkPrivilege(){ //检查权限 } }
-
Joinpoint(连接点):指的是可以拦截到的点
- 增删改查这些方法都可以被增强,这些方法成为是连接点
Pointcut(切入点):指的是真正被拦截到的点
指想对save()方法进行增强(做权限校验),save方法称为是切入点
-
Advice(通知):拦截后要做的事:
- 对save()方法进行权限校验,权限校验的方法称之为通知
- 通知有前置和后置以及环绕通知
- 前置通知:比如说想在save方法之前做权限校验,这就叫前置通知
- 后置通知:比如说要在delete方法做一个日志记录,这就叫后置通知
- 环绕通知:在之前和之后都会做一些事情
- 对save()方法进行权限校验,权限校验的方法称之为通知
Target(目标):被增强的对象,例如:UserDaoImpl
Weaving(织入):将Advice应用到Target的过程
将权限校验应用到UserDaoImpl的save()方法中的整个过程
-
Proxy(代理对象):被应用了增强后,产生的一个代理对象
- Aspect(切面):就是切入点和通知的组合
JDK的动态代理
- 定义一个接口
public interface UserDao {
public void save();
public void delete();
public void update();
public void find();
}
- 接口的实现类
public class UserDaoImpl implements UserDao{
public void save() {
System.out.println("保存用户");
}
public void delete() {
System.out.println("删除用户");
}
public void update() {
System.out.println("修改用户");
}
public void find() {
System.out.println("查询用户");
}
}
-
编写一个动态代理类
/* 这个构造方法的作用是获取到需要增强的对象 */ public MyjdkProxy(UserDao userDao){ this.userDao = userDao; } ===================================================================================== public Object createProxy(){//用createProxy()这个方法产生一个代理,首选jdk的动态代理,因为jdk的动态代理可以对实现了接口的类实现代理 /* 传入的参数:类加载器userDao.getClass().getClassLoader();实现的接口userDao.getClass().getInterfaces();第三个参数是一个接口InvocationHandler */ Object proxy = Proxy.newProxyInstance(userDao.getClass().getClassLoader(),userDao.getClass().getInterfaces(),this); return proxy; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("save".equals(method.getName())){ System.out.println("权限校验"); return method.invoke(userDao,args); } return method.invoke(userDao,args); } ==================================================================================== /* 编写测试类,使用代理 */ UserDao userDao = new UserDaoImpl(); UserDao proxy = (UserDao)new MyjdkProxy(userDao).createProxy(); proxy.save(); proxy.delete(); proxy.update(); proxy.find(); }
CGLIB动态代理
- 底层:生成了一个类来继承目标类
对于不使用接口的业务层,无法使用jdk的动态代理
CGLIB采用非常底层字节码技术,可以为一个类创建字类,解决无接口代理问题
public Object createProxy(){
//1.创建一个CGLIB的核心类
Enhancer enhancer = new Enhancer();
//2.设置父类
enhancer.setSuperclass(object.getClass());
//3.设置回调
enhancer.setCallback(this);
//4.生成代理
Object proxy = enhancer.create;
return proxy;
}
-
代理知识总结:
Spring在运行期,生成动态代理对象,不需要特殊的编译器
-
Spring AOP的底层就是通过jdk的动态代理或者CGLIB动态代理技术,为目标Bean执行横向织入
若目标对象实现了若干接口,spring使用jdk的动态代理:java.lang.reflect.Proxy类代理
若目标对象没事实现任何的接口,spring使用CGLIB库生成目标对象的子类
程序中应优先对接口创建代理,以便于程序的解耦合以及后期的维护
-
标记为final的方法,不能被代理,因为无法进行覆盖重写:因为她没有办法去重写父类或者父接口上面的方法
- JDK的动态代理,是针对接口生成子类,接口中的方法不能使用final修饰
- CGLIB是针对目标类生成子类,因此类或方法不能使用final
Spring只支持方法连接点,不提供属性连接点
一般切面的代码案例
- 定义StudentDao接口
public interface StudentDao {
public void save();
public void find();
public void update();
public void delete();
}
-
接口的实现类,实现了接口StudentDao
public class StudentDaoImpl implements StudentDao{ public void save() { System.out.println("save执行了。。。。。。。"); } public void find() { System.out.println("find执行了。。。。。。。"); } public void update() { System.out.println("update执行了。。。。。。"); } public void delete() { System.out.println("delete执行了。。。。。。"); } }
-
定义一个一般切面Advisor
public class MyBeforeAdvice implements MethodBeforeAdvice{ public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println("前置通知======================"); } }
-
配置文件中配置
<!-- 配置目标类 --> <bean id="studentDao" class="com.imooc.aop.demo3.StudentDaoImpl"></bean> <!--前置通知类型--> <bean id="myBeforeAdvice" class="com.imooc.aop.demo3.MyBeforeAdvice"></bean> <!--Spring AOP产生代理对象--> <bean id="studentDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <!--配置目标类--> <property name="target" ref="studentDao"></property> <!--实现的接口--> <property name="proxyInterfaces" value="com.imooc.aop.demo3.StudentDao"></property> <!--采用拦截的名称--> <property name="interceptorNames" value="myBeforeAdvice"></property> </bean>
Spring带有切入点的切面配置
- 我们需要代理的对象
public class CustomerDao {
public void find(){
System.out.println("查询用户...");
}
public void save(){
System.out.println("保存用户...");
}
public void update(){
System.out.println("修改用户");
}
public void delete(){
System.out.println("删除用户");
}
- 通知Advice
/*
环绕通知时最强大的一种通知类型,它可以阻止目标方法的执行
*/
public class MyArroundAdvice implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("环绕前增强...");
Object obj = invocation.proceed();//用来执行目标方法,比如说原来的方法是执行保存,这句哈的意思就相当于执行保存的操作
System.out.println("环绕后增强...");
return obj;
}
- 配置
<!--配置目标类-->
<bean id="customerDao" class="com.imooc.aop.demo4.CustomerDao"></bean>
<!--配置通知-->
<bean id="myArroundAdvice" class="com.imooc.aop.demo4.MyArroundAdvice"></bean>
<!--一般的切面是使用整个通知来作为切面的,因为要对目标类某个方法或某些方法进行增强就需要配置一个带有切入点的切面-->
<bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!--pattern中配置正则表达式:.代表任意字符;*代表任意次数-->
<property name="patterns" value=".*save.*,.*delete.*" ></property>
<!--应用哪种通知-->
<property name="advice" ref="myArroundAdvice"></property>
</bean>
<!--配置产生代理-->
<bean id="customerDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="customerDao"></property>
<property name="proxyTargetClass" value="true"></property>
<property name="interceptorNames" value="myAdvisor"></property>
</bean>
</beans>
自动创建代理
BeanNameAutoProxyCreate举例
基于Bean名称的自动代理的创建方式,根据目标类的名称产生代理
-
先把前几个例子中的接口,接口实现类,通知等拷贝过来
public class CustomerDao { public void find(){ System.out.println("查询用户..."); } public void save(){ System.out.println("保存用户..."); } public void update(){ System.out.println("修改用户"); } public void delete(){ System.out.println("删除用户"); } }
public interface StudentDao { public void save(); public void find(); public void update(); public void delete(); }
public class StudentDaoImpl implements StudentDao { public void save() { System.out.println("save执行了。。。。。。。"); } public void find() { System.out.println("find执行了。。。。。。。"); } public void update() { System.out.println("update执行了。。。。。。"); } public void delete() { System.out.println("delete执行了。。。。。。"); } }
public class MyBeforeAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("前置通知======================");
}
}
public class MyArroundAdvice implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("环绕前增强...");
Object obj = invocation.proceed();//用来执行目标方法,比如说原来的方法是执行保存,这句哈的意思就相当于执行保存的操作
System.out.println("环绕后增强...");
return obj;
}
}
-
配置文件配置
<!--配置目标类--> <bean id="studentDao" class="com.imooc.aop.demo5.StudentDaoImpl"></bean> <bean id="customerDao" class="com.imooc.aop.demo5.CustomerDao"></bean> <!--配置增强,Advice--> <bean id="myArroundAdvice" class="com.imooc.aop.demo5.MyArroundAdvice"></bean> <bean id="myBedoreAdvice" class="com.imooc.aop.demo5.MyBeforeAdvice"></bean> <!--自动代理--> <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames" value="*Dao"></property> <property name="interceptorNames" value="myBedoreAdvice"></property> </bean> </beans>
- 这种自动代理有缺陷,比如说只想增强其中的save方法该怎么做到呢,这是基于Bean名称的方式又不太合适了
DefalutAdvisorAutoProxyCreator举例
默认的切面的自动代理的方式创建,这种方式可以基于切面的信息自动产生代理
-
前几个步骤与上一个方法类似,只是在配置文件中不同
<!--配置目标类--> <bean id="studentDao" class="com.imooc.aop.demo6.StudentDaoImpl"></bean> <bean id="customerDao" class="com.imooc.aop.demo6.CustomerDao"></bean> <!--配置增强,Advice--> <bean id="myArroundAdvice" class="com.imooc.aop.demo6.MyArroundAdvice"></bean> <bean id="myBedoreAdvice" class="com.imooc.aop.demo6.MyBeforeAdvice"></bean> <!--配置一个切面--> <bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="pattern" value="com\.imooc\.aop\.demo6\.CustomerDao\.save"></property> <property name="advice" ref="myArroundAdvice"></property> </bean> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"> </bean>
- 配置了一个切面,因为是基于切面的信息自动产生代理
- 在切面配置中,<property name="pattern" value="com.imooc.aop.demo6.CustomerDao.save"></property>;这一步就是根据类中的方法来增强,由于是正则表达式,所有需要对.进行转义\
AspectJ的注解AOP开发
- AspectJ简介
- AspectJ是一个基于Java语言的AOP框架
- Spring2.0以后新增了对AspectJ切点表达式的支持
- @AspectJ是AspectJ1.5新增的功能,通过JDK5注解技术,允许直接在Bean类中定义切面
-
新版本的Spring框架,都建议使用AspectJ方式来开发AOP
为目标类,定义切面类
public class PeoductDao {
public void save(){
System.out.println("保存商品");
}
public void delete(){
System.out.println("删除商品");
}
public void update(){
System.out.println("修改商品");
}
public void findOne(){
System.out.println("查询一个商品");
}
public void findAll(){
System.out.println("查询所有的商品");
}
/*
切面类
*/
@Aspect
public class MyAspect {
@Before(value = "execution(* com.imooc.aspectJ.demo1.PeoductDao.save(..))")
public void before(){
System.out.println("前置通知");
}
<!--开启AspectJ的注解开发,自动代理-->
<aop:aspectj-autoproxy/>
<bean id="peoductDao" class="com.imooc.aspectJ.demo1.PeoductDao"></bean>
<!--定义切面-->
<bean class="com.imooc.aspectJ.demo1.MyAspect"></bean>
详细介绍Before前置通知
@AfterReturing 后置通知
@AfterReturning(value = "execution(* com.imooc.aspectJ.demo1.PeoductDao.update(..))",returning = "re")
public void afterReturing(Object re){//为什么要用Object,因为这个返回值有可能是任意类型的返回值,所以用Object;
System.out.println("后置通知=============" + re);
}
//为什么要用Object,因为这个返回值有可能是任意类型的返回值,所以用Object;
@AfterReturing 异常抛出通知
@Around环绕通知
- 重点:如果不调用joinPoint.proceed();方法的话,目标方法会被拦截,即delete不会执行
通过@Pointcut为切点命名
在每个通知内定义切点,会造成工作量大,不易维护,对于重复的切点,可以使用@Pointcut进行定义
切点方法:private void 无参数方法。方法名为切点名;因为这个方法在类中没有什么实际的意义,也不用在别的方法中调用它,所以定义为private
当通知多个切点时,可以使用||进行连接
基于AspectJ的xml方式的AOP开发
通过配置的方式完成AOP的开发
public interface CustomerDao { public void save(); public void findOne(); public void fingAll(); public void update(); public void delete(); }
public class CustomerDaoImpl implements CustomerDao { public void save() { System.out.println("保存客户"); } public void findOne() { System.out.println("查找一个客户"); } public void fingAll() { System.out.println("查找所有的客户"); } public void update() { System.out.println("修改客户"); } public void delete() { System.out.println("删除客户"); }
<!--xml配置方式完成AOP的开发--> <!--配置目标类--> <bean id="customerDao" class="com.imooc.aspectJ.demo2.CustomerDaoImpl"></bean>